stat2025 commited on
Commit
ad59553
·
verified ·
1 Parent(s): 78be24b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +723 -112
index.html CHANGED
@@ -2,147 +2,758 @@
2
  <html lang="ar" dir="rtl">
3
  <head>
4
  <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1" />
6
- <title>بيان استلام الإقرارات</title>
7
 
8
- <link rel="stylesheet" href="style.css" />
9
- <!-- SheetJS (Excel Export) -->
 
 
 
10
  <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  </head>
12
 
13
  <body>
14
- <div class="app">
15
- <header class="topbar">
16
- <div class="brand">
17
- <div class="badge">بيان استلام</div>
18
- <h1>إقرار الموافقة على مشاركة القوائم المالية في المركز السعودي للأعمال</h1>
19
- <p class="subtitle">أدخل البيانات يدويًا ثم اضغط حفظ لإضافتها للجدول. البيانات تبقى محفوظة حتى تقوم بالحذف.</p>
20
- </div>
21
- <div class="meta">
22
- <div class="stat">
23
- <div class="stat-label">عدد السجلات</div>
24
- <div class="stat-value" id="rowsCount">0</div>
25
- </div>
26
- <div class="stat">
27
- <div class="stat-label">آخر حفظ</div>
28
- <div class="stat-value" id="lastSaved">—</div>
29
- </div>
30
- </div>
31
- </header>
32
-
33
- <main class="grid">
34
- <!-- Form Card -->
35
- <section class="card">
36
- <div class="card-title">إدخال البيانات</div>
37
-
38
- <form id="entryForm" class="form" autocomplete="off">
39
- <div class="row">
40
- <label>
41
- <span>اسم الشركة</span>
42
- <input id="companyName" type="text" placeholder="مثال: شركة ..." required />
43
- </label>
44
-
45
- <label>
46
- <span>السجل التجاري</span>
47
- <input id="crNumber" type="text" inputmode="numeric" placeholder="مثال: 1010..." required />
48
- </label>
49
- </div>
50
 
51
- <div class="row">
52
- <label>
53
- <span>اسم المستلم</span>
54
- <input id="receiverName" type="text" placeholder="اسم المستلم" required />
55
- </label>
56
 
57
- <label>
58
- <span>رقم الجوال</span>
59
- <input id="mobile" type="tel" inputmode="tel" placeholder="05xxxxxxxx" required />
60
- </label>
61
  </div>
62
 
63
- <div class="row">
64
- <label class="full">
65
- <span>البريد الإلكتروني</span>
66
- <input id="email" type="email" placeholder="name@example.com" />
67
- </label>
68
  </div>
69
 
70
- <div class="row">
71
- <label>
72
- <span>التاريخ</span>
73
- <input id="date" type="date" required />
74
- </label>
75
-
76
- <label>
77
- <span>التوقيع (كتابة)</span>
78
- <input id="signatureText" type="text" placeholder="اكتب الاسم كتوقيع (اختياري)" />
79
- </label>
80
  </div>
81
 
82
- <div class="sig-wrap">
83
- <div class="sig-head">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  <div>
85
- <div class="sig-title">التوقيع (رسم)</div>
86
- <div class="sig-hint">اختياري ارسم التوقيع، ثم يمكنك مسحه</div>
87
  </div>
88
- <div class="sig-actions">
89
- <button type="button" class="btn btn-ghost" id="clearSigBtn">مسح التوقيع</button>
 
 
90
  </div>
91
  </div>
92
- <canvas id="sigCanvas" width="900" height="220" aria-label="Signature canvas"></canvas>
 
 
 
 
93
  </div>
94
 
95
- <div class="actions">
96
- <input type="hidden" id="editId" value="" />
97
- <button type="submit" class="btn btn-primary" id="saveBtn">حفظ</button>
98
- <button type="button" class="btn btn-ghost" id="resetBtn">تفريغ الحقول</button>
 
99
  </div>
100
 
101
- <div class="note">
102
- <strong>ملاحظة:</strong> البيانات تُحفظ على نفس الجهاز/المتصفح. إن فتحت الصفحة من جهاز آخر لن تظهر إلا إذا صدّرت Excel ونقلته.
 
103
  </div>
104
- </form>
105
- </section>
106
-
107
- <!-- Table Card -->
108
- <section class="card">
109
- <div class="card-head">
110
- <div class="card-title">السجلات المحفوظة</div>
111
- <div class="toolbar">
112
- <input id="search" class="search" type="search" placeholder="بحث سريع: اسم شركة / سجل / جوال ..." />
113
- <button class="btn btn-secondary" id="exportBtn" type="button">تصدير Excel</button>
114
- <button class="btn btn-danger" id="deleteAllBtn" type="button">حذف الكل</button>
 
 
 
 
 
 
 
 
 
 
 
115
  </div>
116
  </div>
117
 
118
- <div class="table-wrap">
119
- <table class="table" id="dataTable">
120
- <thead>
121
- <tr>
122
- <th class="col-n">م</th>
123
- <th>اسم الشركة</th>
124
- <th>السجل التجاري</th>
125
- <th>اسم المستلم</th>
126
- <th>رقم الجوال</th>
127
- <th>البريد الإلكتروني</th>
128
- <th>التاريخ</th>
129
- <th>التوقيع</th>
130
- <th class="col-actions">إجراءات</th>
131
- </tr>
132
- </thead>
133
- <tbody id="tableBody">
134
- <!-- rows injected -->
135
- </tbody>
136
- </table>
137
  </div>
138
 
139
- <div class="footer-help">
140
- يمكنك تعديل أي سجل أو حذفه. عند التصدير سيُنشأ ملف Excel يحتوي على كل السجلات الحالية.
 
141
  </div>
142
- </section>
143
- </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </div>
145
 
146
- <script src="app.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  </body>
148
  </html>
 
2
  <html lang="ar" dir="rtl">
3
  <head>
4
  <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>الإقرارات - بيان الاستلام</title>
7
 
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
11
+
12
+ <!-- Excel Export -->
13
  <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
14
+
15
+ <style>
16
+ :root{
17
+ /* ألوان مستوحاة من دليل هوية الهيئة العامة للإحصاء (RGB) */
18
+ --gastat-green:#53CD3F; /* R83 G205 B63 */
19
+ --gastat-blue:#00B2DF; /* R0 G178 B223 */
20
+ --gastat-purple:#4137A8;/* R65 G55 B168 */
21
+ --gastat-gray:#8492A2; /* R132 G146 B162 */
22
+
23
+ --bg:#071321;
24
+ --card:#0b1d34;
25
+ --card2:#081a2f;
26
+ --text:#eef6ff;
27
+ --muted:#c3d1ea;
28
+ --line:rgba(255,255,255,.12);
29
+ --shadow: 0 18px 60px rgba(0,0,0,.35);
30
+ --radius:18px;
31
+
32
+ --ok:#22c55e;
33
+ --warn:#f59e0b;
34
+ --bad:#ef4444;
35
+ }
36
+
37
+ *{ box-sizing:border-box; }
38
+ body{
39
+ margin:0;
40
+ font-family:"Cairo", system-ui, -apple-system, Segoe UI, Arial, sans-serif;
41
+ background:
42
+ radial-gradient(900px 520px at 15% 15%, rgba(0,178,223,.22), transparent 60%),
43
+ radial-gradient(900px 520px at 85% 10%, rgba(83,205,63,.18), transparent 60%),
44
+ radial-gradient(1000px 650px at 60% 70%, rgba(65,55,168,.18), transparent 60%),
45
+ var(--bg);
46
+ color:var(--text);
47
+ }
48
+
49
+ .wrap{ max-width:1200px; margin:18px auto 60px; padding:0 14px; }
50
+
51
+ .hero{
52
+ border:1px solid rgba(255,255,255,.12);
53
+ box-shadow: var(--shadow);
54
+ border-radius: var(--radius);
55
+ padding:18px 18px 14px;
56
+ background: linear-gradient(135deg, rgba(0,178,223,.20), rgba(65,55,168,.18));
57
+ position:relative; overflow:hidden;
58
+ }
59
+ .hero:before{
60
+ content:"";
61
+ position:absolute; inset:-60px -80px auto auto;
62
+ width:260px; height:260px;
63
+ background: radial-gradient(circle, rgba(255,255,255,.18), transparent 60%);
64
+ transform: rotate(12deg);
65
+ pointer-events:none;
66
+ }
67
+
68
+ .titleTop{ text-align:center; margin:0 0 8px; color:rgba(255,255,255,.9); font-size:16px; }
69
+ .titleBar{
70
+ margin:0; text-align:center; font-size:18px; font-weight:800;
71
+ background: rgba(255,255,255,.06);
72
+ border:1px solid rgba(255,255,255,.12);
73
+ padding:12px 10px; border-radius:14px;
74
+ }
75
+ .subNote{ margin:10px 0 0; text-align:center; color:rgba(195,209,234,.92); font-size:13px; line-height:1.7; }
76
+
77
+ .grid{
78
+ display:grid; grid-template-columns: 1.2fr 1fr; gap:14px; margin-top:14px;
79
+ }
80
+ @media (max-width: 980px){ .grid{ grid-template-columns:1fr; } }
81
+
82
+ .card{
83
+ background: linear-gradient(180deg, rgba(11,29,52,.92), rgba(8,26,47,.92));
84
+ border:1px solid rgba(255,255,255,.12);
85
+ box-shadow: var(--shadow);
86
+ border-radius: var(--radius);
87
+ padding:14px;
88
+ }
89
+
90
+ .card h3{ margin:0 0 10px; font-size:15px; display:flex; gap:10px; align-items:center; }
91
+ .badge{
92
+ font-size:12px; padding:4px 10px; border-radius:999px;
93
+ border:1px solid rgba(255,255,255,.16);
94
+ background: rgba(255,255,255,.06);
95
+ color: var(--muted);
96
+ }
97
+
98
+ .formGrid{ display:grid; grid-template-columns: 1fr 1fr; gap:10px; }
99
+ @media (max-width: 680px){ .formGrid{ grid-template-columns:1fr; } }
100
+
101
+ label{ display:block; font-size:12px; color: var(--muted); margin:0 0 6px; }
102
+ input, select{
103
+ width:100%;
104
+ padding:12px 12px;
105
+ border-radius:14px;
106
+ border:1px solid rgba(255,255,255,.14);
107
+ background: rgba(255,255,255,.06);
108
+ color: var(--text);
109
+ outline:none;
110
+ transition:.15s;
111
+ font-size:14px;
112
+ }
113
+ input:focus, select:focus{
114
+ border-color: rgba(0,178,223,.55);
115
+ box-shadow: 0 0 0 4px rgba(0,178,223,.16);
116
+ }
117
+ input::placeholder{ color: rgba(195,209,234,.7); }
118
+
119
+ .actions{ display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
120
+ .btn{
121
+ border:0; border-radius:14px; padding:11px 14px;
122
+ font-family:inherit; font-weight:800; cursor:pointer;
123
+ display:inline-flex; align-items:center; gap:8px;
124
+ border:1px solid rgba(255,255,255,.14);
125
+ background: rgba(255,255,255,.08);
126
+ color: var(--text);
127
+ transition:.15s;
128
+ }
129
+ .btn:hover{ transform: translateY(-1px); }
130
+ .btn:active{ transform: translateY(0px); }
131
+
132
+ .btn.primary{
133
+ background: linear-gradient(135deg, rgba(83,205,63,.92), rgba(34,197,94,.78));
134
+ border-color: rgba(83,205,63,.35);
135
+ }
136
+ .btn.export{
137
+ background: linear-gradient(135deg, rgba(0,178,223,.92), rgba(65,55,168,.72));
138
+ border-color: rgba(0,178,223,.35);
139
+ }
140
+ .btn.warn{
141
+ background: linear-gradient(135deg, rgba(245,158,11,.95), rgba(217,119,6,.82));
142
+ border-color: rgba(245,158,11,.35);
143
+ }
144
+ .btn.danger{
145
+ background: linear-gradient(135deg, rgba(239,68,68,.95), rgba(220,38,38,.82));
146
+ border-color: rgba(239,68,68,.35);
147
+ }
148
+
149
+ .noteBox{
150
+ margin-top:10px;
151
+ border-radius:14px;
152
+ border:1px solid rgba(255,255,255,.14);
153
+ background: rgba(0,178,223,.10);
154
+ padding:10px 12px;
155
+ color: rgba(238,246,255,.95);
156
+ font-size:13px;
157
+ line-height:1.7;
158
+ }
159
+ .noteBox.warn{
160
+ background: rgba(245,158,11,.12);
161
+ border-color: rgba(245,158,11,.28);
162
+ }
163
+
164
+ .tableWrap{
165
+ overflow:auto;
166
+ border-radius: var(--radius);
167
+ border:1px solid rgba(255,255,255,.12);
168
+ background: rgba(255,255,255,.04);
169
+ }
170
+ table{ width:100%; border-collapse:collapse; min-width:1100px; }
171
+ thead th{
172
+ position:sticky; top:0;
173
+ background: rgba(255,255,255,.08);
174
+ backdrop-filter: blur(8px);
175
+ font-size:12px; color: rgba(238,246,255,.95);
176
+ text-align:center;
177
+ padding:10px 8px;
178
+ border-bottom:1px solid rgba(255,255,255,.12);
179
+ white-space:nowrap;
180
+ }
181
+ tbody td{
182
+ font-size:13px; color: rgba(238,246,255,.92);
183
+ padding:10px 8px;
184
+ border-bottom:1px solid rgba(255,255,255,.08);
185
+ text-align:center;
186
+ vertical-align:middle;
187
+ white-space:nowrap;
188
+ }
189
+ tbody tr:hover{ background: rgba(255,255,255,.05); }
190
+
191
+ .tinyBtn{
192
+ padding:7px 10px; border-radius:12px; font-weight:800; font-size:12px;
193
+ border:1px solid rgba(255,255,255,.14);
194
+ background: rgba(255,255,255,.07);
195
+ color: var(--text);
196
+ cursor:pointer;
197
+ }
198
+ .tinyBtn.danger{ background: rgba(239,68,68,.18); border-color: rgba(239,68,68,.35); }
199
+ .tinyBtn.edit{ background: rgba(245,158,11,.16); border-color: rgba(245,158,11,.35); }
200
+
201
+ .statusRow{
202
+ display:flex; align-items:center; justify-content:space-between;
203
+ gap:10px; flex-wrap:wrap; margin-bottom:10px;
204
+ }
205
+ .pill{
206
+ font-size:12px; padding:6px 10px; border-radius:999px;
207
+ border:1px solid rgba(255,255,255,.14);
208
+ background: rgba(255,255,255,.06);
209
+ color: rgba(195,209,234,.95);
210
+ }
211
+ .searchBox input{
212
+ width:320px; max-width:70vw;
213
+ padding:10px 12px; border-radius:999px;
214
+ }
215
+
216
+ .mutedSmall{
217
+ font-size:12px; color: rgba(195,209,234,.9);
218
+ margin-top:10px; border-top:1px dashed rgba(255,255,255,.14); padding-top:10px;
219
+ line-height:1.7;
220
+ }
221
+
222
+ /* تحسين للجوال: تقليل المسافات */
223
+ @media (max-width: 480px){
224
+ .wrap{ padding:0 10px; }
225
+ .titleBar{ font-size:16px; }
226
+ .btn{ width:100%; justify-content:center; }
227
+ .searchBox input{ width:100%; }
228
+ }
229
+ </style>
230
  </head>
231
 
232
  <body>
233
+ <div class="wrap">
234
+ <div class="hero">
235
+ <p class="titleTop">"بيان استلام"</p>
236
+ <h1 class="titleBar">إدارة حالات الإقرارات وتوثيق الاستلام</h1>
237
+ <p class="subNote">
238
+ أدخل البيانات ثم اختر الحالة. يتم الحفظ تلقائيًا ولا تختفي السجلات عند إغلاق الصفحة إلا بالحذف.
239
+ </p>
240
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
+ <div class="grid">
243
+ <!-- نموذج الإدخال -->
244
+ <div class="card">
245
+ <h3>إدخال بيانات <span class="badge">متوافق للجوال + حفظ دائم</span></h3>
 
246
 
247
+ <div class="formGrid">
248
+ <div>
249
+ <label>اسم الشركة (مطلوب)</label>
250
+ <input id="company" type="text" placeholder="مثال: شركة ...." />
251
  </div>
252
 
253
+ <div>
254
+ <label>رقم السجل التجاري</label>
255
+ <input id="cr" type="text" inputmode="numeric" placeholder="مثال: 1010xxxxxx" />
 
 
256
  </div>
257
 
258
+ <div style="grid-column:1/-1">
259
+ <label>الحالة</label>
260
+ <select id="status">
261
+ <option value="RESPONDED">استجاب</option>
262
+ <option value="BRANCH_NO_SEPARATE">فرع ليس له حسابات مستقلة</option>
263
+ <option value="TEMP_CLOSED">مغلقة مؤقتًا</option>
264
+ <option value="PERM_CLOSED">مغلقة نهائيًا</option>
265
+ </select>
 
 
266
  </div>
267
 
268
+ <!-- يظهر فقط عند استجاب -->
269
+ <div id="respondedBlock" style="grid-column:1/-1; display:none;">
270
+ <div class="formGrid">
271
+ <div style="grid-column:1/-1">
272
+ <label>نوع الكيان عند الاستجابة</label>
273
+ <select id="respondedType">
274
+ <option value="">— اختر —</option>
275
+ <option value="MAIN_CENTER">مركز رئيسي</option>
276
+ <option value="BRANCH_SEPARATE">فرع له حسابات مستقلة</option>
277
+ <option value="SINGLE">مفردة</option>
278
+ </select>
279
+ </div>
280
+
281
+ <div>
282
+ <label>اسم المستلم</label>
283
+ <input id="receiver" type="text" placeholder="الاسم الثلاثي" />
284
+ </div>
285
+
286
+ <div>
287
+ <label>رقم الجوال</label>
288
+ <input id="mobile" type="tel" inputmode="numeric" placeholder="05xxxxxxxx أو 9665xxxxxxxx" />
289
+ </div>
290
+
291
+ <div>
292
+ <label>البريد الإلكتروني</label>
293
+ <input id="email" type="email" placeholder="name@example.com" />
294
+ </div>
295
+
296
  <div>
297
+ <label>التاريخ</label>
298
+ <input id="date" type="date" />
299
  </div>
300
+
301
+ <div style="grid-column:1/-1">
302
+ <label>التوقيع (نصي)</label>
303
+ <input id="signature" type="text" placeholder="اسم/ملاحظة توقيع" />
304
  </div>
305
  </div>
306
+
307
+ <div class="noteBox" id="respondedHelp">
308
+ عند اختيار <b>استجاب</b> يُفضّل تحديد (مركز/فرع مستقل/مفردة)، ثم تُكمل بيانات الاستلام إن توفرت.
309
+ <br>جميع الحقول (عدا اسم الشركة) اختيارية.
310
+ </div>
311
  </div>
312
 
313
+ <!-- يظهر للحالات التي تتطلب تنبيه تصوير -->
314
+ <div id="photoNote" style="grid-column:1/-1; display:none;">
315
+ <div class="noteBox warn">
316
+ 📸 <b>ملاحظة:</b> التقط صورة (للواجهة/اللوحة/الإغلاق) وسترفق لاحقًا بالنظام من قبلك.
317
+ </div>
318
  </div>
319
 
320
+ <div style="grid-column:1/-1">
321
+ <label>ملاحظة إضافية (اختياري)</label>
322
+ <input id="extraNote" type="text" placeholder="أي تعليق أو توضيح..." />
323
  </div>
324
+ </div>
325
+
326
+ <div class="actions">
327
+ <button class="btn primary" id="saveBtn">💾 حفظ</button>
328
+ <button class="btn warn" id="clearFormBtn">🧹 تفريغ الحقول</button>
329
+ <button class="btn export" id="exportBtn">📤 تصدير Excel</button>
330
+ </div>
331
+
332
+ <div class="mutedSmall">
333
+ الحفظ داخل المتصفح (LocalStorage).<br>
334
+ التحقق: البريد بصيغة Email، والجوال أرقام (05… أو 9665…)، وباقي الحقول اختيارية.
335
+ </div>
336
+ </div>
337
+
338
+ <!-- إدارة السجلات -->
339
+ <div class="card">
340
+ <h3>إدارة السجلات <span class="badge">بحث + حذف</span></h3>
341
+
342
+ <div class="statusRow">
343
+ <div class="pill" id="countPill">عدد السجلات: 0</div>
344
+ <div class="searchBox">
345
+ <input id="search" type="text" placeholder="🔎 بحث: شركة / سجل / حالة / جوال / بريد" />
346
  </div>
347
  </div>
348
 
349
+ <div class="actions">
350
+ <button class="btn danger" id="deleteSelectedBtn">🗑️ حذف المحدد</button>
351
+ <button class="btn danger" id="deleteAllBtn">⚠️ حذف الكل</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  </div>
353
 
354
+ <div class="mutedSmall">
355
+ للتعديل اضغط <b>تعديل</b> في الصف (سيتم تعبئة النموذج) ثم احفظ.<br>
356
+ • حذف سجل واحد من زر <b>حذف</b> في نفس الصف.
357
  </div>
358
+ </div>
359
+ </div>
360
+
361
+ <!-- الجدول -->
362
+ <div class="card" style="margin-top:14px;">
363
+ <h3>الجدول</h3>
364
+
365
+ <div class="tableWrap">
366
+ <table>
367
+ <thead>
368
+ <tr>
369
+ <th>تحديد</th>
370
+ <th>م</th>
371
+ <th>اسم الشركة</th>
372
+ <th>رقم السجل التجاري</th>
373
+ <th>الحالة</th>
374
+ <th>تفصيل الاستجابة</th>
375
+ <th>اسم المستلم</th>
376
+ <th>رقم الجوال</th>
377
+ <th>البريد الإلكتروني</th>
378
+ <th>التاريخ</th>
379
+ <th>التوقيع</th>
380
+ <th>ملاحظة</th>
381
+ <th>إجراءات</th>
382
+ </tr>
383
+ </thead>
384
+ <tbody id="tbody"></tbody>
385
+ </table>
386
+ </div>
387
+ </div>
388
  </div>
389
 
390
+ <script>
391
+ const STORAGE_KEY = "eq_records_v2";
392
+
393
+ const el = {
394
+ company: document.getElementById("company"),
395
+ cr: document.getElementById("cr"),
396
+ status: document.getElementById("status"),
397
+ respondedBlock: document.getElementById("respondedBlock"),
398
+ respondedType: document.getElementById("respondedType"),
399
+ receiver: document.getElementById("receiver"),
400
+ mobile: document.getElementById("mobile"),
401
+ email: document.getElementById("email"),
402
+ date: document.getElementById("date"),
403
+ signature: document.getElementById("signature"),
404
+ extraNote: document.getElementById("extraNote"),
405
+ photoNote: document.getElementById("photoNote"),
406
+
407
+ saveBtn: document.getElementById("saveBtn"),
408
+ clearFormBtn: document.getElementById("clearFormBtn"),
409
+ exportBtn: document.getElementById("exportBtn"),
410
+
411
+ tbody: document.getElementById("tbody"),
412
+ countPill: document.getElementById("countPill"),
413
+ search: document.getElementById("search"),
414
+
415
+ deleteSelectedBtn: document.getElementById("deleteSelectedBtn"),
416
+ deleteAllBtn: document.getElementById("deleteAllBtn"),
417
+ };
418
+
419
+ let records = [];
420
+ let editId = null;
421
+
422
+ function todayISO(){
423
+ const d = new Date();
424
+ const y = d.getFullYear();
425
+ const m = String(d.getMonth()+1).padStart(2,"0");
426
+ const day = String(d.getDate()).padStart(2,"0");
427
+ return `${y}-${m}-${day}`;
428
+ }
429
+
430
+ function escapeHtml(str){
431
+ return String(str ?? "")
432
+ .replaceAll("&","&amp;")
433
+ .replaceAll("<","&lt;")
434
+ .replaceAll(">","&gt;")
435
+ .replaceAll('"',"&quot;")
436
+ .replaceAll("'","&#039;");
437
+ }
438
+
439
+ function sanitize(s){ return String(s ?? "").trim(); }
440
+
441
+ function load(){
442
+ try{
443
+ const raw = localStorage.getItem(STORAGE_KEY);
444
+ records = raw ? JSON.parse(raw) : [];
445
+ }catch(e){
446
+ records = [];
447
+ }
448
+ }
449
+ function persist(){
450
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
451
+ }
452
+
453
+ function isValidEmail(v){
454
+ if(!v) return true;
455
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
456
+ }
457
+
458
+ function normalizeMobile(v){
459
+ return sanitize(v).replace(/\s+/g,"").replace(/[^\d+]/g,"");
460
+ }
461
+
462
+ function isValidMobile(v){
463
+ if(!v) return true;
464
+ const x = normalizeMobile(v);
465
+ // 05xxxxxxxx (10 أرقام) أو 9665xxxxxxxx (12 رقم) أو +9665xxxxxxxx
466
+ return /^(05\d{8}|9665\d{8}|\+9665\d{8})$/.test(x);
467
+ }
468
+
469
+ function statusLabel(code){
470
+ switch(code){
471
+ case "RESPONDED": return "استجاب";
472
+ case "BRANCH_NO_SEPARATE": return "فرع ليس له حسابات مستقلة";
473
+ case "TEMP_CLOSED": return "مغلقة مؤقتًا";
474
+ case "PERM_CLOSED": return "مغلقة نهائيًا";
475
+ default: return code || "";
476
+ }
477
+ }
478
+
479
+ function respondedTypeLabel(code){
480
+ switch(code){
481
+ case "MAIN_CENTER": return "مركز رئيسي";
482
+ case "BRANCH_SEPARATE": return "فرع له حسابات مستقلة";
483
+ case "SINGLE": return "مفردة";
484
+ default: return "";
485
+ }
486
+ }
487
+
488
+ function updateConditionalUI(){
489
+ const s = el.status.value;
490
+ const isResponded = (s === "RESPONDED");
491
+ const needsPhoto = (s === "TEMP_CLOSED" || s === "PERM_CLOSED");
492
+
493
+ el.respondedBlock.style.display = isResponded ? "block" : "none";
494
+ el.photoNote.style.display = needsPhoto ? "block" : "none";
495
+
496
+ // عند غير استجاب: نظّف حقول الاستجابة حتى لا تُصدّر خطأ
497
+ if(!isResponded){
498
+ el.respondedType.value = "";
499
+ if(el.receiver) el.receiver.value = "";
500
+ if(el.mobile) el.mobile.value = "";
501
+ if(el.email) el.email.value = "";
502
+ if(el.signature) el.signature.value = "";
503
+ }
504
+ }
505
+
506
+ function resetForm(){
507
+ el.company.value = "";
508
+ el.cr.value = "";
509
+ el.status.value = "RESPONDED";
510
+ el.respondedType.value = "";
511
+ if(el.receiver) el.receiver.value = "";
512
+ if(el.mobile) el.mobile.value = "";
513
+ if(el.email) el.email.value = "";
514
+ el.date.value = todayISO();
515
+ if(el.signature) el.signature.value = "";
516
+ el.extraNote.value = "";
517
+ editId = null;
518
+ el.saveBtn.textContent = "💾 حفظ";
519
+ updateConditionalUI();
520
+ }
521
+
522
+ function fillForm(rec){
523
+ el.company.value = rec.company || "";
524
+ el.cr.value = rec.cr || "";
525
+ el.status.value = rec.status || "RESPONDED";
526
+ el.respondedType.value = rec.respondedType || "";
527
+ if(el.receiver) el.receiver.value = rec.receiver || "";
528
+ if(el.mobile) el.mobile.value = rec.mobile || "";
529
+ if(el.email) el.email.value = rec.email || "";
530
+ el.date.value = rec.date || todayISO();
531
+ if(el.signature) el.signature.value = rec.signature || "";
532
+ el.extraNote.value = rec.extraNote || "";
533
+ editId = rec.id;
534
+ el.saveBtn.textContent = "✅ حفظ التعديل";
535
+ updateConditionalUI();
536
+ window.scrollTo({ top: 0, behavior: "smooth" });
537
+ }
538
+
539
+ function filteredRecords(){
540
+ const q = sanitize(el.search.value).toLowerCase();
541
+ if(!q) return records;
542
+ return records.filter(r => {
543
+ const hay = [
544
+ r.company, r.cr, statusLabel(r.status), respondedTypeLabel(r.respondedType),
545
+ r.receiver, r.mobile, r.email, r.date, r.signature, r.note, r.extraNote
546
+ ].join(" ").toLowerCase();
547
+ return hay.includes(q);
548
+ });
549
+ }
550
+
551
+ function render(){
552
+ const list = filteredRecords();
553
+ el.tbody.innerHTML = "";
554
+
555
+ list.forEach((r, idx) => {
556
+ const tr = document.createElement("tr");
557
+ tr.innerHTML = `
558
+ <td><input type="checkbox" data-id="${r.id}" class="rowCheck"></td>
559
+ <td>${idx + 1}</td>
560
+ <td>${escapeHtml(r.company)}</td>
561
+ <td>${escapeHtml(r.cr)}</td>
562
+ <td>${escapeHtml(statusLabel(r.status))}</td>
563
+ <td>${escapeHtml(respondedTypeLabel(r.respondedType))}</td>
564
+ <td>${escapeHtml(r.receiver)}</td>
565
+ <td>${escapeHtml(r.mobile)}</td>
566
+ <td>${escapeHtml(r.email)}</td>
567
+ <td>${escapeHtml(r.date)}</td>
568
+ <td>${escapeHtml(r.signature)}</td>
569
+ <td>${escapeHtml(r.note || "")}${r.extraNote ? " | " + escapeHtml(r.extraNote) : ""}</td>
570
+ <td>
571
+ <button class="tinyBtn edit" data-action="edit" data-id="${r.id}">تعديل</button>
572
+ <button class="tinyBtn danger" data-action="delete" data-id="${r.id}">حذف</button>
573
+ </td>
574
+ `;
575
+ el.tbody.appendChild(tr);
576
+ });
577
+
578
+ el.countPill.textContent = `عدد السجلات: ${records.length}`;
579
+ }
580
+
581
+ function buildAutoNote(status){
582
+ if(status === "BRANCH_NO_SEPARATE"){
583
+ return "فرع ليس له حسابات مستقلة";
584
+ }
585
+ if(status === "TEMP_CLOSED"){
586
+ return "مغلقة مؤقتًا - التقط صورة وسترفق لاحقًا بالنظام من قبلك";
587
+ }
588
+ if(status === "PERM_CLOSED"){
589
+ return "مغلقة نهائيًا - التقط صورة وسترفق لاحقًا بالنظام من قبلك";
590
+ }
591
+ return "";
592
+ }
593
+
594
+ function save(){
595
+ const company = sanitize(el.company.value);
596
+ if(!company){
597
+ alert("فضلاً أدخل اسم الشركة (مطلوب).");
598
+ return;
599
+ }
600
+
601
+ const status = el.status.value;
602
+ const cr = sanitize(el.cr.value);
603
+
604
+ // بيانات الاستجابة (اختيارية)
605
+ const respondedType = sanitize(el.respondedType.value);
606
+ const receiver = el.receiver ? sanitize(el.receiver.value) : "";
607
+ const mobileRaw = el.mobile ? sanitize(el.mobile.value) : "";
608
+ const mobile = normalizeMobile(mobileRaw);
609
+ const email = el.email ? sanitize(el.email.value) : "";
610
+ const date = el.date.value || todayISO();
611
+ const signature = el.signature ? sanitize(el.signature.value) : "";
612
+ const extraNote = sanitize(el.extraNote.value);
613
+
614
+ // تحقق (اختياري)
615
+ if(email && !isValidEmail(email)){
616
+ alert("البريد الإلكتروني غير صحيح.");
617
+ return;
618
+ }
619
+ if(mobileRaw && !isValidMobile(mobileRaw)){
620
+ alert("رقم الجوال غير صحيح. مثال: 05xxxxxxxx أو 9665xxxxxxxx أو +9665xxxxxxxx");
621
+ return;
622
+ }
623
+
624
+ // ملاحظة تلقائية للحالات غير استجاب
625
+ let note = buildAutoNote(status);
626
+
627
+ // إذا استجاب ولم يحدد النوع: لا نجعله إلزامي (حسب طلبك)، لكن ننبه بلطف
628
+ if(status === "RESPONDED" && !respondedType){
629
+ // اختياري: بدون منع الحفظ
630
+ // يمكنك إلغاء التنبيه إذا ما تبيه
631
+ // alert("تنبيه: لم يتم اختيار (مركز/فرع مستقل/مفردة). يمكنك إكماله لاحقًا بالتعديل.");
632
+ }
633
+
634
+ const rec = {
635
+ id: editId ?? crypto.randomUUID(),
636
+ company,
637
+ cr,
638
+ status,
639
+ respondedType: status === "RESPONDED" ? respondedType : "",
640
+ receiver: status === "RESPONDED" ? receiver : "",
641
+ mobile: status === "RESPONDED" ? mobile : "",
642
+ email: status === "RESPONDED" ? email : "",
643
+ date: status === "RESPONDED" ? date : (date || todayISO()),
644
+ signature: status === "RESPONDED" ? signature : "",
645
+ note,
646
+ extraNote,
647
+ updatedAt: new Date().toISOString()
648
+ };
649
+
650
+ if(editId){
651
+ const i = records.findIndex(x => x.id === editId);
652
+ if(i !== -1) records[i] = { ...records[i], ...rec };
653
+ }else{
654
+ records.push(rec);
655
+ }
656
+
657
+ persist();
658
+ render();
659
+ resetForm();
660
+ }
661
+
662
+ function deleteOne(id){
663
+ const r = records.find(x => x.id === id);
664
+ if(!r) return;
665
+ if(!confirm(`تأكيد حذف سجل الشركة: ${r.company} ؟`)) return;
666
+ records = records.filter(x => x.id !== id);
667
+ persist();
668
+ render();
669
+ if(editId === id) resetForm();
670
+ }
671
+
672
+ function deleteSelected(){
673
+ const checks = [...document.querySelectorAll(".rowCheck:checked")];
674
+ if(checks.length === 0){ alert("لم يتم تحديد أي سجلات."); return; }
675
+ if(!confirm(`تأكيد حذف (${checks.length}) سجل/سجلات؟`)) return;
676
+ const ids = new Set(checks.map(c => c.getAttribute("data-id")));
677
+ records = records.filter(r => !ids.has(r.id));
678
+ persist();
679
+ render();
680
+ if(editId && ids.has(editId)) resetForm();
681
+ }
682
+
683
+ function deleteAll(){
684
+ if(records.length === 0){ alert("لا توجد سجلات للحذف."); return; }
685
+ if(!confirm("تحذير: سيتم حذف جميع السجلات نهائيًا من هذه الصفحة. هل أنت متأكد؟")) return;
686
+ records = [];
687
+ persist();
688
+ render();
689
+ resetForm();
690
+ }
691
+
692
+ function exportExcel(){
693
+ const list = filteredRecords();
694
+ if(list.length === 0){ alert("لا توجد بيانات للتصدير."); return; }
695
+
696
+ const rows = list.map((r, idx) => ({
697
+ "م": idx + 1,
698
+ "اسم الشركة": r.company,
699
+ "رقم السجل التجاري": r.cr,
700
+ "الحالة": statusLabel(r.status),
701
+ "تفصيل الاستجابة": respondedTypeLabel(r.respondedType),
702
+ "اسم المستلم": r.receiver,
703
+ "رقم الجوال": r.mobile,
704
+ "البريد الإلكتروني": r.email,
705
+ "التاريخ": r.date,
706
+ "التوقيع": r.signature,
707
+ "ملاحظة": (r.note || "") + (r.extraNote ? " | " + r.extraNote : "")
708
+ }));
709
+
710
+ const ws = XLSX.utils.json_to_sheet(rows, { cellDates: true });
711
+
712
+ ws["!cols"] = [
713
+ { wch: 5 }, // م
714
+ { wch: 32 }, // اسم الشركة
715
+ { wch: 18 }, // السجل
716
+ { wch: 18 }, // الحالة
717
+ { wch: 20 }, // تفصيل
718
+ { wch: 22 }, // اسم المستلم
719
+ { wch: 18 }, // الجوال
720
+ { wch: 28 }, // البريد
721
+ { wch: 14 }, // التاريخ
722
+ { wch: 22 }, // التوقيع
723
+ { wch: 42 }, // ملاحظة
724
+ ];
725
+
726
+ const wb = XLSX.utils.book_new();
727
+ XLSX.utils.book_append_sheet(wb, ws, "الإقرارات");
728
+ XLSX.writeFile(wb, `EQ_${todayISO()}.xlsx`);
729
+ }
730
+
731
+ // Events
732
+ el.status.addEventListener("change", updateConditionalUI);
733
+ el.saveBtn.addEventListener("click", save);
734
+ el.clearFormBtn.addEventListener("click", resetForm);
735
+ el.exportBtn.addEventListener("click", exportExcel);
736
+ el.search.addEventListener("input", render);
737
+ el.deleteSelectedBtn.addEventListener("click", deleteSelected);
738
+ el.deleteAllBtn.addEventListener("click", deleteAll);
739
+
740
+ el.tbody.addEventListener("click", (e) => {
741
+ const btn = e.target.closest("button");
742
+ if(!btn) return;
743
+ const action = btn.getAttribute("data-action");
744
+ const id = btn.getAttribute("data-id");
745
+ if(action === "delete") deleteOne(id);
746
+ if(action === "edit"){
747
+ const rec = records.find(x => x.id === id);
748
+ if(rec) fillForm(rec);
749
+ }
750
+ });
751
+
752
+ // Init
753
+ el.date.value = todayISO();
754
+ load();
755
+ updateConditionalUI();
756
+ render();
757
+ </script>
758
  </body>
759
  </html>