stat2025 commited on
Commit
7bfbf6a
·
verified ·
1 Parent(s): 55a8c99

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +142 -23
index.html CHANGED
@@ -28,7 +28,6 @@
28
  --shadow: 0 18px 60px rgba(0,0,0,.35);
29
  --radius:18px;
30
 
31
- --ok:#22c55e;
32
  --warn:#f59e0b;
33
  --bad:#ef4444;
34
  }
@@ -169,6 +168,27 @@
169
  border-color: rgba(245,158,11,.28);
170
  }
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  .tableWrap{
173
  overflow:auto;
174
  border-radius: var(--radius);
@@ -235,6 +255,7 @@
235
  <div class="wrap">
236
  <div class="hero">
237
  <p class="creditLine">تصميم وإعداد: نوف الناصر</p>
 
238
  <h1 class="titleBar">إدارة حالات الإقرارات وتوثيق الاستلام</h1>
239
  <p class="subNote">
240
  أدخل البيانات ثم اختر الحالة. يتم الحفظ تلقائيًا ولا تختفي السجلات عند إغلاق الصفحة إلا بالحذف.
@@ -267,6 +288,7 @@
267
  </select>
268
  </div>
269
 
 
270
  <div id="respondedBlock" style="grid-column:1/-1; display:none;">
271
  <div class="formGrid">
272
  <div style="grid-column:1/-1">
@@ -299,17 +321,16 @@
299
  <input id="date" type="date" />
300
  </div>
301
 
302
- <div style="grid-column:1/-1">
303
- <label>التوقيع اليدوي</label>
304
-
305
- <div class="signatureBox">
306
- <canvas id="signaturePad"></canvas>
307
- </div>
308
-
309
- <div class="sigActions">
310
- <button type="button" class="tinyBtn" id="clearSignature">مسح التوقيع</button>
311
- </div>
312
- </div>
313
 
314
  <div class="noteBox" id="respondedHelp">
315
  عند اختيار <b>استجاب</b> يُفضّل تحديد (مركز/فرع مستقل/مفردة)، ثم تُكمل بيانات الاستلام إن توفرت.
@@ -336,6 +357,7 @@
336
  </div>
337
  </div>
338
 
 
339
  <div class="card">
340
  <h3>إدارة السجلات <span class="badge">بحث + حذف</span></h3>
341
 
@@ -353,6 +375,7 @@
353
  </div>
354
  </div>
355
 
 
356
  <div class="card" style="margin-top:14px;">
357
  <h3>الجدول</h3>
358
 
@@ -382,7 +405,7 @@
382
  </div>
383
 
384
  <script>
385
- const STORAGE_KEY = "eq_records_v2";
386
 
387
  const el = {
388
  company: document.getElementById("company"),
@@ -394,7 +417,6 @@
394
  mobile: document.getElementById("mobile"),
395
  email: document.getElementById("email"),
396
  date: document.getElementById("date"),
397
- signature: document.getElementById("signature"),
398
  extraNote: document.getElementById("extraNote"),
399
  photoNote: document.getElementById("photoNote"),
400
 
@@ -410,6 +432,13 @@
410
  deleteAllBtn: document.getElementById("deleteAllBtn"),
411
  };
412
 
 
 
 
 
 
 
 
413
  let records = [];
414
  let editId = null;
415
 
@@ -491,7 +520,7 @@
491
  if(el.receiver) el.receiver.value = "";
492
  if(el.mobile) el.mobile.value = "";
493
  if(el.email) el.email.value = "";
494
- if(el.signature) el.signature.value = "";
495
  }
496
  }
497
 
@@ -504,10 +533,10 @@
504
  if(el.mobile) el.mobile.value = "";
505
  if(el.email) el.email.value = "";
506
  el.date.value = todayISO();
507
- if(el.signature) el.signature.value = "";
508
  el.extraNote.value = "";
509
  editId = null;
510
  el.saveBtn.textContent = "💾 حفظ";
 
511
  updateConditionalUI();
512
  }
513
 
@@ -520,11 +549,23 @@
520
  if(el.mobile) el.mobile.value = rec.mobile || "";
521
  if(el.email) el.email.value = rec.email || "";
522
  el.date.value = rec.date || todayISO();
523
- if(el.signature) el.signature.value = rec.signature || "";
524
  el.extraNote.value = rec.extraNote || "";
525
  editId = rec.id;
526
  el.saveBtn.textContent = "✅ حفظ التعديل";
527
  updateConditionalUI();
 
 
 
 
 
 
 
 
 
 
 
 
 
528
  window.scrollTo({ top: 0, behavior: "smooth" });
529
  }
530
 
@@ -534,7 +575,7 @@
534
  return records.filter(r => {
535
  const hay = [
536
  r.company, r.cr, statusLabel(r.status), respondedTypeLabel(r.respondedType),
537
- r.receiver, r.mobile, r.email, r.date, r.signature, r.note, r.extraNote
538
  ].join(" ").toLowerCase();
539
  return hay.includes(q);
540
  });
@@ -557,7 +598,7 @@
557
  <td>${escapeHtml(r.mobile)}</td>
558
  <td>${escapeHtml(r.email)}</td>
559
  <td>${escapeHtml(r.date)}</td>
560
- <td>${escapeHtml(r.signature)}</td>
561
  <td>${escapeHtml(r.note || "")}${r.extraNote ? " | " + escapeHtml(r.extraNote) : ""}</td>
562
  <td>
563
  <button class="tinyBtn edit" data-action="edit" data-id="${r.id}">تعديل</button>
@@ -583,6 +624,78 @@
583
  return "";
584
  }
585
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  function save(){
587
  const company = sanitize(el.company.value);
588
  if(!company){
@@ -599,7 +712,6 @@
599
  const mobile = normalizeMobile(mobileRaw);
600
  const email = el.email ? sanitize(el.email.value) : "";
601
  const date = el.date.value || todayISO();
602
- const signature = el.signature ? sanitize(el.signature.value) : "";
603
  const extraNote = sanitize(el.extraNote.value);
604
 
605
  if(email && !isValidEmail(email)){
@@ -623,7 +735,7 @@
623
  mobile: status === "RESPONDED" ? mobile : "",
624
  email: status === "RESPONDED" ? email : "",
625
  date: status === "RESPONDED" ? date : (date || todayISO()),
626
- signature: status === "RESPONDED" ? signature : "",
627
  note,
628
  extraNote,
629
  updatedAt: new Date().toISOString()
@@ -685,7 +797,7 @@
685
  "رقم الجوال": r.mobile,
686
  "البريد الإلكتروني": r.email,
687
  "التاريخ": r.date,
688
- "التوقيع": r.signature,
689
  "ملاحظة": (r.note || "") + (r.extraNote ? " | " + r.extraNote : "")
690
  }));
691
 
@@ -701,10 +813,12 @@
701
  { wch: 18 },
702
  { wch: 28 },
703
  { wch: 14 },
704
- { wch: 22 },
705
  { wch: 42 },
706
  ];
707
 
 
 
708
  const wb = XLSX.utils.book_new();
709
  XLSX.utils.book_append_sheet(wb, ws, "الإقرارات");
710
  XLSX.writeFile(wb, `EQ_${todayISO()}.xlsx`);
@@ -736,6 +850,11 @@
736
  load();
737
  updateConditionalUI();
738
  render();
 
 
 
 
 
739
  </script>
740
  </body>
741
  </html>
 
28
  --shadow: 0 18px 60px rgba(0,0,0,.35);
29
  --radius:18px;
30
 
 
31
  --warn:#f59e0b;
32
  --bad:#ef4444;
33
  }
 
168
  border-color: rgba(245,158,11,.28);
169
  }
170
 
171
+ /* ====== التوقيع اليدوي ====== */
172
+ .signatureBox{
173
+ width:100%;
174
+ height:180px;
175
+ background:#ffffff;
176
+ border-radius:14px;
177
+ border:2px dashed rgba(0,178,223,.6);
178
+ overflow:hidden;
179
+ position:relative;
180
+ }
181
+ .signatureBox canvas{
182
+ width:100%;
183
+ height:100%;
184
+ display:block;
185
+ touch-action:none;
186
+ }
187
+ .sigActions{
188
+ margin-top:8px;
189
+ text-align:left;
190
+ }
191
+
192
  .tableWrap{
193
  overflow:auto;
194
  border-radius: var(--radius);
 
255
  <div class="wrap">
256
  <div class="hero">
257
  <p class="creditLine">تصميم وإعداد: نوف الناصر</p>
258
+ <p class="titleTop">"بيان استلام"</p>
259
  <h1 class="titleBar">إدارة حالات الإقرارات وتوثيق الاستلام</h1>
260
  <p class="subNote">
261
  أدخل البيانات ثم اختر الحالة. يتم الحفظ تلقائيًا ولا تختفي السجلات عند إغلاق الصفحة إلا بالحذف.
 
288
  </select>
289
  </div>
290
 
291
+ <!-- يظهر فقط عند استجاب -->
292
  <div id="respondedBlock" style="grid-column:1/-1; display:none;">
293
  <div class="formGrid">
294
  <div style="grid-column:1/-1">
 
321
  <input id="date" type="date" />
322
  </div>
323
 
324
+ <div style="grid-column:1/-1">
325
+ <label>التوقيع اليدوي</label>
326
+ <div class="signatureBox">
327
+ <canvas id="signaturePad"></canvas>
328
+ </div>
329
+ <div class="sigActions">
330
+ <button type="button" class="tinyBtn" id="clearSignature">مسح التوقيع</button>
331
+ </div>
332
+ </div>
333
+ </div>
 
334
 
335
  <div class="noteBox" id="respondedHelp">
336
  عند اختيار <b>استجاب</b> يُفضّل تحديد (مركز/فرع مستقل/مفردة)، ثم تُكمل بيانات الاستلام إن توفرت.
 
357
  </div>
358
  </div>
359
 
360
+ <!-- إدارة السجلات -->
361
  <div class="card">
362
  <h3>إدارة السجلات <span class="badge">بحث + حذف</span></h3>
363
 
 
375
  </div>
376
  </div>
377
 
378
+ <!-- الجدول -->
379
  <div class="card" style="margin-top:14px;">
380
  <h3>الجدول</h3>
381
 
 
405
  </div>
406
 
407
  <script>
408
+ const STORAGE_KEY = "eq_records_v3";
409
 
410
  const el = {
411
  company: document.getElementById("company"),
 
417
  mobile: document.getElementById("mobile"),
418
  email: document.getElementById("email"),
419
  date: document.getElementById("date"),
 
420
  extraNote: document.getElementById("extraNote"),
421
  photoNote: document.getElementById("photoNote"),
422
 
 
432
  deleteAllBtn: document.getElementById("deleteAllBtn"),
433
  };
434
 
435
+ // التوقيع اليدوي
436
+ const canvas = document.getElementById("signaturePad");
437
+ const ctx = canvas.getContext("2d");
438
+ let drawing = false;
439
+ let signatureData = null;
440
+ let scaledOnce = false;
441
+
442
  let records = [];
443
  let editId = null;
444
 
 
520
  if(el.receiver) el.receiver.value = "";
521
  if(el.mobile) el.mobile.value = "";
522
  if(el.email) el.email.value = "";
523
+ clearSignaturePad();
524
  }
525
  }
526
 
 
533
  if(el.mobile) el.mobile.value = "";
534
  if(el.email) el.email.value = "";
535
  el.date.value = todayISO();
 
536
  el.extraNote.value = "";
537
  editId = null;
538
  el.saveBtn.textContent = "💾 حفظ";
539
+ clearSignaturePad();
540
  updateConditionalUI();
541
  }
542
 
 
549
  if(el.mobile) el.mobile.value = rec.mobile || "";
550
  if(el.email) el.email.value = rec.email || "";
551
  el.date.value = rec.date || todayISO();
 
552
  el.extraNote.value = rec.extraNote || "";
553
  editId = rec.id;
554
  el.saveBtn.textContent = "✅ حفظ التعديل";
555
  updateConditionalUI();
556
+
557
+ // رسم التوقيع المخزن على اللوحة إن وجد
558
+ clearSignaturePad();
559
+ if(rec.signature){
560
+ const img = new Image();
561
+ img.onload = () => {
562
+ const rect = canvas.getBoundingClientRect();
563
+ ctx.drawImage(img, 0, 0, rect.width, rect.height);
564
+ signatureData = rec.signature;
565
+ };
566
+ img.src = rec.signature;
567
+ }
568
+
569
  window.scrollTo({ top: 0, behavior: "smooth" });
570
  }
571
 
 
575
  return records.filter(r => {
576
  const hay = [
577
  r.company, r.cr, statusLabel(r.status), respondedTypeLabel(r.respondedType),
578
+ r.receiver, r.mobile, r.email, r.date, r.note, r.extraNote
579
  ].join(" ").toLowerCase();
580
  return hay.includes(q);
581
  });
 
598
  <td>${escapeHtml(r.mobile)}</td>
599
  <td>${escapeHtml(r.email)}</td>
600
  <td>${escapeHtml(r.date)}</td>
601
+ <td>${r.signature ? `<img src="${r.signature}" style="height:50px; background:#fff; border-radius:8px; padding:2px;">` : ""}</td>
602
  <td>${escapeHtml(r.note || "")}${r.extraNote ? " | " + escapeHtml(r.extraNote) : ""}</td>
603
  <td>
604
  <button class="tinyBtn edit" data-action="edit" data-id="${r.id}">تعديل</button>
 
624
  return "";
625
  }
626
 
627
+ /* ====== التوقيع اليدوي (Canvas) ====== */
628
+ function resizeCanvas(){
629
+ const ratio = window.devicePixelRatio || 1;
630
+ const rect = canvas.getBoundingClientRect();
631
+
632
+ const oldData = signatureData;
633
+
634
+ canvas.width = Math.max(1, Math.floor(rect.width * ratio));
635
+ canvas.height = Math.max(1, Math.floor(rect.height * ratio));
636
+
637
+ ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
638
+ ctx.lineWidth = 2;
639
+ ctx.lineCap = "round";
640
+ ctx.strokeStyle = "#000";
641
+
642
+ // إعادة رسم التوقيع إذا كان موجودًا (عند تغيير المقاس)
643
+ if(oldData){
644
+ const img = new Image();
645
+ img.onload = () => {
646
+ ctx.drawImage(img, 0, 0, rect.width, rect.height);
647
+ signatureData = oldData;
648
+ };
649
+ img.src = oldData;
650
+ }
651
+ }
652
+
653
+ function clearSignaturePad(){
654
+ const rect = canvas.getBoundingClientRect();
655
+ ctx.clearRect(0, 0, rect.width, rect.height);
656
+ signatureData = null;
657
+ }
658
+
659
+ function getPos(e){
660
+ const rect = canvas.getBoundingClientRect();
661
+ if(e.touches && e.touches[0]){
662
+ return { x: e.touches[0].clientX - rect.left, y: e.touches[0].clientY - rect.top };
663
+ }
664
+ return { x: e.clientX - rect.left, y: e.clientY - rect.top };
665
+ }
666
+
667
+ function startDraw(e){
668
+ drawing = true;
669
+ const p = getPos(e);
670
+ ctx.beginPath();
671
+ ctx.moveTo(p.x, p.y);
672
+ }
673
+
674
+ function draw(e){
675
+ if(!drawing) return;
676
+ e.preventDefault();
677
+ const p = getPos(e);
678
+ ctx.lineTo(p.x, p.y);
679
+ ctx.stroke();
680
+ }
681
+
682
+ function endDraw(){
683
+ if(!drawing) return;
684
+ drawing = false;
685
+ signatureData = canvas.toDataURL("image/png");
686
+ }
687
+
688
+ canvas.addEventListener("mousedown", startDraw);
689
+ canvas.addEventListener("mousemove", draw);
690
+ canvas.addEventListener("mouseup", endDraw);
691
+ canvas.addEventListener("mouseleave", endDraw);
692
+
693
+ canvas.addEventListener("touchstart", startDraw, { passive:false });
694
+ canvas.addEventListener("touchmove", draw, { passive:false });
695
+ canvas.addEventListener("touchend", endDraw);
696
+
697
+ document.getElementById("clearSignature").addEventListener("click", clearSignaturePad);
698
+
699
  function save(){
700
  const company = sanitize(el.company.value);
701
  if(!company){
 
712
  const mobile = normalizeMobile(mobileRaw);
713
  const email = el.email ? sanitize(el.email.value) : "";
714
  const date = el.date.value || todayISO();
 
715
  const extraNote = sanitize(el.extraNote.value);
716
 
717
  if(email && !isValidEmail(email)){
 
735
  mobile: status === "RESPONDED" ? mobile : "",
736
  email: status === "RESPONDED" ? email : "",
737
  date: status === "RESPONDED" ? date : (date || todayISO()),
738
+ signature: (status === "RESPONDED") ? (signatureData || "") : "",
739
  note,
740
  extraNote,
741
  updatedAt: new Date().toISOString()
 
797
  "رقم الجوال": r.mobile,
798
  "البريد الإلكتروني": r.email,
799
  "التاريخ": r.date,
800
+ "التوقيع": r.signature ? "مرفق (صورة)" : "",
801
  "ملاحظة": (r.note || "") + (r.extraNote ? " | " + r.extraNote : "")
802
  }));
803
 
 
813
  { wch: 18 },
814
  { wch: 28 },
815
  { wch: 14 },
816
+ { wch: 14 },
817
  { wch: 42 },
818
  ];
819
 
820
+ // ملاحظة: Excel عبر SheetJS لا يدعم إدراج الصور في الخلايا كصورة مباشرة في المتصفح بدون حلول إضافية.
821
+ // لذا نضع مؤشر "مرفق (صورة)" داخل Excel، بينما التوقيع يظهر كصورة داخل الصفحة.
822
  const wb = XLSX.utils.book_new();
823
  XLSX.utils.book_append_sheet(wb, ws, "الإقرارات");
824
  XLSX.writeFile(wb, `EQ_${todayISO()}.xlsx`);
 
850
  load();
851
  updateConditionalUI();
852
  render();
853
+
854
+ // تجهيز لوحة التوقيع بعد ظهورها (وعند تحميل الصفحة)
855
+ // نعمل resize الآن، وأيضًا عند تغيير الحجم
856
+ setTimeout(() => { resizeCanvas(); }, 0);
857
+ window.addEventListener("resize", () => { resizeCanvas(); });
858
  </script>
859
  </body>
860
  </html>