stat2025 commited on
Commit
87c3e98
·
verified ·
1 Parent(s): 9c74792

Upload 5 files

Browse files
Files changed (5) hide show
  1. .gitattributes +35 -35
  2. data.js +0 -0
  3. index.html +52 -17
  4. script.js +81 -0
  5. style.css +2 -28
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
data.js ADDED
The diff for this file is too large to render. See raw diff
 
index.html CHANGED
@@ -1,19 +1,54 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype 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
+ <meta name="theme-color" content="#4137A8" />
7
+ <title>إدارة ومطابقة عينات المسوح</title>
8
+ <link rel="stylesheet" href="./style.css" />
9
+ </head>
10
+ <body>
11
+ <div class="wrap">
12
+ <header class="site-header">
13
+ <a class="brand" href="#home" aria-label="الصفحة الرئيسية"><span class="logo" aria-hidden="true"></span><span><strong>إدارة عينات المسوح</strong><small>البحث والمطابقة ومتابعة العينات</small></span></a>
14
+ <nav class="nav" aria-label="التنقل الرئيسي">
15
+ <button class="nav-link active" data-view="home">الرئيسية</button>
16
+ <button class="nav-link" data-view="batch">فحص السجلات</button>
17
+ <button class="nav-link" data-view="intersections">التقاطعات</button>
18
+ <button class="nav-link" data-view="researchers">الباحثون</button>
19
+ <button class="nav-link" data-view="surveys">المسوح</button>
20
+ </nav>
21
+ </header>
22
+
23
+ <main>
24
+ <section id="home" class="view active">
25
+ <div class="hero"><div><p class="eyebrow">أداة العمل الرئيسية</p><h1>ابحث عن المنشأة أو السجل أو الباحث</h1><p>تُعرض جميع مرات الظهور من مصدر البيانات نفسه في نتيجة واحدة واضحة.</p></div></div>
26
+ <section class="search-card card" aria-label="البحث الموحد">
27
+ <label for="q">البحث الموحد</label>
28
+ <div class="search-row"><input id="q" type="search" inputmode="search" autocomplete="off" placeholder="رقم السجل التجاري، اسم المنشأة، أو اسم الباحث" /><button id="btnSearch" class="btn primary">بحث</button><button id="btnClear" class="btn secondary">مسح</button></div>
29
+ <p class="hint">يُعطى رقم السجل التجاري أولوية عند توفره في البيانات. البحث النصي مرن ولا يُستخدم لإثبات المطابقة.</p>
30
+ </section>
31
+ <div id="metrics" class="metrics" aria-label="ملخص البيانات"></div>
32
+ <section class="result-section"><div class="section-heading"><div><h2>نتائج البحث</h2><p id="searchMeta">اكتب عبارة بحث للبدء.</p></div></div><div id="searchResults" class="results empty-state">اكتب رقم سجل أو اسم منشأة أو اسم باحث.</div></section>
33
+ </section>
34
+
35
+ <section id="batch" class="view">
36
+ <div class="section-heading"><div><p class="eyebrow">فحص سريع</p><h1>فحص السجلات</h1><p>الصق أرقام السجلات من Excel أو من أي قائمة، ثم افحصها دفعة واحدة.</p></div></div>
37
+ <section class="card batch-card"><label for="batchInput">قائمة السجلات التجارية</label><textarea id="batchInput" rows="10" placeholder="مثال:
38
+ 2511128298
39
+ 2062617120، 2050006430"></textarea><div class="batch-actions"><span id="batchHint" class="hint">لم تُدخل أي سجلات بعد.</span><div><button id="btnBatchClear" class="btn secondary">مسح القائمة</button><button id="btnBatch" class="btn primary">فحص السجلات</button></div></div></section>
40
+ <div id="batchSummary" class="metrics compact"></div>
41
+ <section class="result-section"><div class="section-heading"><div><h2>نتائج الفحص</h2><p id="batchMeta">ستبقى السجلات غير الموجودة ظاهرة في النتائج والتصدير.</p></div><button id="btnExport" class="btn secondary" disabled>تحميل النتائج Excel</button></div><div id="batchResults" class="results empty-state">الصق قائمة السجلات ثم اختر «فحص السجلات».</div></section>
42
+ </section>
43
+
44
+ <section id="intersections" class="view"><div class="section-heading"><div><p class="eyebrow">تداخل العينات</p><h1>التقاطعات بين المسوح</h1><p>السجلات التي يظهر لها أكثر من مسح فقط.</p></div></div><div id="intersectionMetrics" class="metrics compact"></div><div id="intersectionResults" class="results empty-state">يجري تحميل البيانات…</div></section>
45
+ <section id="researchers" class="view"><div class="section-heading"><div><p class="eyebrow">المسؤوليات</p><h1>الباحثون</h1><p>ابحث باسم الباحث لعرض مؤشرات العينات والمسوح.</p></div><input id="researcherFilter" class="small-search" placeholder="ابحث باسم الباحث" /></div><div id="researcherResults" class="results"></div></section>
46
+ <section id="surveys" class="view"><div class="section-heading"><div><p class="eyebrow">مصدر البيانات</p><h1>المسوح</h1><p>ملخص كل مسح محمّل في النظام.</p></div></div><div id="surveyResults" class="survey-grid"></div></section>
47
+ </main>
48
+ </div>
49
+ <div id="toast" class="toast" role="status" aria-live="polite"></div>
50
+ <footer>تصميم وإعداد <b>نوف الناصر</b> · تطوير إدارة عينات المسوح</footer>
51
+ <script src="./data.js"></script>
52
+ <script src="./script.js"></script>
53
+ </body>
54
  </html>
script.js ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* طبقة البيانات والفهرسة: تبقى كل المعالجة محلية داخل المتصفح. */
2
+ const FIELD_ALIASES = {
3
+ commercialRegister: ["السجل التجاري", "رقم السجل التجاري", "رقم السجل", "السجل", "commercial register", "cr"],
4
+ facilityName: ["اسم المنشأة", "إسم المنشأة", "المنشأة", "اسم المنشاه", "facility", "establishment name"],
5
+ researcher: ["اسم الباحث", "اسم الباحث/ة", "الباحث", "researcher"],
6
+ collectionStatus: ["حالة الاستيفاء", "حالة جمع البيانات", "collection status"],
7
+ completionStatus: ["حالة اكتمال البيانات", "حالة الاكتمال", "completion status"],
8
+ approvalStatus: ["حالة الاعتماد", "اعتماد", "الوصف", "الوصف.1", "approval"],
9
+ surveyName: ["اسم المسح", "المسح", "survey", "survey name"],
10
+ };
11
+
12
+ const app = { rows: [], byRegister: new Map(), entityGroups: new Map(), researchers: new Map(), surveys: new Map(), batch: [] };
13
+ const $ = (id) => document.getElementById(id);
14
+ const digits = { "٠":"0", "١":"1", "٢":"2", "٣":"3", "٤":"4", "٥":"5", "٦":"6", "٧":"7", "٨":"8", "٩":"9", "۰":"0", "۱":"1", "۲":"2", "۳":"3", "۴":"4", "۵":"5", "۶":"6", "۷":"7", "۸":"8", "۹":"9" };
15
+
16
+ function plain(value) { return String(value ?? "").replace(/[\u200E\u200F\u202A-\u202E\u00A0]/g, " ").replace(/\s+/g, " ").trim(); }
17
+ function normText(value) { return plain(value).toLowerCase().replace(/[إأآا]/g, "ا").replace(/ى/g, "ي").replace(/ة/g, "ه").replace(/[\u064B-\u065F\u0670]/g, ""); }
18
+ function normalizeDigits(value) { return plain(value).replace(/[٠-٩۰-۹]/g, (c) => digits[c]); }
19
+ function expandScientific(value) {
20
+ const m = value.match(/^([+\-]?)(\d+)(?:\.(\d*))?[eE]([+\-]?\d+)$/);
21
+ if (!m) return value;
22
+ const sign = m[1] === "-" ? "-" : ""; const whole = m[2]; const fraction = m[3] || ""; const power = Number(m[4]);
23
+ const all = whole + fraction; const point = whole.length + power;
24
+ if (!Number.isInteger(power) || point < 0 || point > 80) return value;
25
+ return sign + (point >= all.length ? all + "0".repeat(point - all.length) : point <= 0 ? "0." + "0".repeat(-point) + all : all.slice(0, point) + "." + all.slice(point));
26
+ }
27
+ function normalizeRegister(value) {
28
+ let v = expandScientific(normalizeDigits(value));
29
+ if (!v || /^(nan|null|undefined)$/i.test(v)) return "";
30
+ if (/^\d+\.0+$/.test(v)) v = v.replace(/\.0+$/, "");
31
+ if (/^[\d\s,،._\-/]+$/.test(v)) return v.replace(/[\s,،._\-/]/g, "");
32
+ return v;
33
+ }
34
+ function getField(raw, aliases) { const entries = Object.entries(raw || {}); for (const alias of aliases) { const hit = entries.find(([key]) => normText(key) === normText(alias)); if (hit && plain(hit[1])) return plain(hit[1]); } return ""; }
35
+ function getStatusClass(value) { const v = normText(value); if (/اعطت كامل|استيفاء ذاتي|مكتمل|معتمد/.test(v) && !/غير/.test(v)) return "ok"; if (/رفض|لم يعثر|مغلق|تعذر|غير مكتمل|ناقص/.test(v)) return "danger"; if (/يطلب التواصل|بعض البيانات|جديد|اخرى/.test(v)) return "warn"; return ""; }
36
+ function entityKey(row, id) { return row.commercialRegister ? `cr:${row.commercialRegister}` : `row:${id}`; }
37
+ function safeRows() { return typeof DATA !== "undefined" && Array.isArray(DATA) ? DATA : []; }
38
+
39
+ function loadData() {
40
+ const rawRows = safeRows();
41
+ if (!rawRows.length) throw new Error("لا توجد بيانات داخل data.js");
42
+ app.rows = rawRows.map((raw, index) => {
43
+ const commercialRegister = normalizeRegister(getField(raw, FIELD_ALIASES.commercialRegister));
44
+ return { id:index, raw, commercialRegister, facilityName:getField(raw, FIELD_ALIASES.facilityName), researcher:getField(raw, FIELD_ALIASES.researcher), collectionStatus:getField(raw, FIELD_ALIASES.collectionStatus), completionStatus:getField(raw, FIELD_ALIASES.completionStatus), approvalStatus:getField(raw, FIELD_ALIASES.approvalStatus), surveyName:getField(raw, FIELD_ALIASES.surveyName) || "مسح الهيكلي" };
45
+ });
46
+ app.rows.forEach((row) => {
47
+ const key = entityKey(row, row.id); if (!app.entityGroups.has(key)) app.entityGroups.set(key, { key, commercialRegister:row.commercialRegister, rows:[] }); app.entityGroups.get(key).rows.push(row);
48
+ if (row.commercialRegister) { if (!app.byRegister.has(row.commercialRegister)) app.byRegister.set(row.commercialRegister, []); app.byRegister.get(row.commercialRegister).push(row); }
49
+ const researcherKey = normText(row.researcher); if (researcherKey) { if (!app.researchers.has(researcherKey)) app.researchers.set(researcherKey, { name:row.researcher, rows:[] }); app.researchers.get(researcherKey).rows.push(row); }
50
+ if (!app.surveys.has(row.surveyName)) app.surveys.set(row.surveyName, []); app.surveys.get(row.surveyName).push(row);
51
+ });
52
+ }
53
+ function groupInfo(group) { const rows = group.rows; const names = [...new Set(rows.map(r => r.facilityName).filter(Boolean))]; const surveys = [...new Set(rows.map(r => r.surveyName))]; const researchers = [...new Set(rows.map(r => r.researcher).filter(Boolean))]; return { ...group, name:names[0] || "—", names, surveys, researchers, multiSurvey:surveys.length > 1 }; }
54
+ function groupForRows(rows) { const g = new Map(); rows.forEach(r => { const key = entityKey(r, r.id); if (!g.has(key)) g.set(key, { key, commercialRegister:r.commercialRegister, rows:[] }); g.get(key).rows.push(r); }); return [...g.values()].map(groupInfo); }
55
+
56
+ function showView(name) { document.querySelectorAll(".view").forEach(v => v.classList.toggle("active", v.id === name)); document.querySelectorAll(".nav-link").forEach(b => b.classList.toggle("active", b.dataset.view === name)); window.scrollTo({ top:0, behavior:"smooth" }); }
57
+ function toast(message) { const el=$("toast"); el.textContent=message; el.classList.add("show"); clearTimeout(toast.timer); toast.timer=setTimeout(()=>el.classList.remove("show"),3200); }
58
+ function metric(value,label){return `<div class="metric"><b>${value}</b><span>${label}</span></div>`;}
59
+ function renderMetrics() { const exactFacilities = new Set(app.rows.map(r=>normText(r.facilityName)).filter(Boolean)); const shared = [...app.byRegister.values()].filter(rows=>new Set(rows.map(r=>r.surveyName)).size>1).length; $("metrics").innerHTML=[metric(app.surveys.size,"عدد المسوح"),metric(app.rows.length.toLocaleString("ar-SA"),"إجمالي السجلات"),metric(exactFacilities.size.toLocaleString("ar-SA"),"منشآت فريدة بالاسم"),metric(shared.toLocaleString("ar-SA"),"منشآت مشتركة بالسجل"),metric(app.researchers.size.toLocaleString("ar-SA"),"عدد الباحثين")].join(""); }
60
+ function renderEntity(group) { const g=groupInfo(group); const title=g.names.length>1 ? `${g.name} <span class="badge warn">توجد أسماء أصلية متعددة</span>` : g.name; const note=g.multiSurvey ? (g.researchers.length > 1 ? `<span class="badge warn">المنشأة موزعة على أكثر من باحث في المسوح المختلفة</span>` : `<span class="badge ok">الباحث نفسه في المسوح المختلفة</span>`) : ""; const occurrences=[...g.rows].sort((a,b)=>a.surveyName.localeCompare(b.surveyName,"ar")).map(r=>`<div class="occurrence"><div><span>المسح</span>${escapeHtml(r.surveyName)}</div><div><span>الباحث</span>${escapeHtml(r.researcher||"—")}</div><div><span>حالة الاستيفاء</span><i class="badge ${getStatusClass(r.collectionStatus)}">${escapeHtml(r.collectionStatus||"—")}</i></div><div><span>اكتمال البيانات</span><i class="badge ${getStatusClass(r.completionStatus)}">${escapeHtml(r.completionStatus||"—")}</i></div></div>`).join(""); return `<article class="entity-card"><div class="entity-top"><div><h3>${title}</h3><p>السجل التجاري: <b>${escapeHtml(g.commercialRegister||"غير متاح في المصدر")}</b></p></div><div class="badges"><span class="badge">${g.surveys.length} مسح</span><span class="badge">${g.rows.length} ظهور</span><span class="badge">${g.researchers.length} باحث</span>${note}</div></div><div class="occurrences">${occurrences}</div></article>`; }
61
+ function escapeHtml(v){return String(v??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;");}
62
+
63
+ function search() { const input=$("q").value; const q=normText(input); const reg=normalizeRegister(input); if(!q){ $("searchResults").className="results empty-state"; $("searchResults").textContent="اكتب رقم سجل أو اسم منشأة أو اسم باحث."; $("searchMeta").textContent="اكتب عبارة بحث للبدء."; return; }
64
+ let rows=[]; let mode=""; if(reg && app.byRegister.has(reg)){rows=app.byRegister.get(reg);mode="سجل تجاري";} else { rows=app.rows.filter(r=>normText(r.facilityName).includes(q)||normText(r.researcher).includes(q)); mode="اسم منشأة أو باحث"; }
65
+ const groups=groupForRows(rows); $("searchMeta").textContent=groups.length?`تم العثور على ${groups.length} نتيجة حسب ${mode}.` : "لم يتم العثور على نتائج مطابقة."; $("searchResults").className="results"; $("searchResults").innerHTML=groups.length?groups.slice(0,100).map(renderEntity).join(""):"<div class='empty-state'>لم يتم العثور على نتائج مطابقة.</div>"; if(groups.length>100) toast("تم عرض أول 100 نتيجة حفاظًا على سرعة الصفحة."); }
66
+
67
+ function parseBatch(text) { const tokens=String(text||"").split(/[\s,،;؛\t\n\r]+/).map(normalizeRegister).filter(Boolean); const unique=[]; const seen=new Set(); tokens.forEach(v=>{if(!seen.has(v)){seen.add(v);unique.push(v);}}); return { values:unique, duplicates:tokens.length-unique.length, entered:tokens.length }; }
68
+ function updateBatchHint(){const p=parseBatch($("batchInput").value); $("batchHint").textContent=p.entered?`تم التعرف على ${p.values.length} سجلًا${p.duplicates?`، وتم تجاهل ${p.duplicates} قيمة مكررة أثناء الفحص.`:"."}`:"لم تُدخل أي سجلات بعد.";}
69
+ function batchCheck(){const parsed=parseBatch($("batchInput").value); if(!parsed.values.length){toast("لم يتم إدخال سجلات للفحص.");return;} app.batch=parsed.values.map((register,order)=>{const rows=app.byRegister.get(register)||[]; const group=rows.length?groupInfo({key:`cr:${register}`,commercialRegister:register,rows}):null; return {order,register,group};}); const found=app.batch.filter(x=>x.group); const multi=found.filter(x=>x.group.surveys.length>1); const one=found.length-multi.length; $("batchSummary").innerHTML=[metric(app.batch.length,"إجمالي السجلات"),metric(found.length,"الموجودة"),metric(app.batch.length-found.length,"غير الموجودة"),metric(one,"في مسح واحد"),metric(multi.length,"في عدة مسوح")].join(""); $("batchMeta").textContent=`تم فحص ${app.batch.length} سجلًا بالترتيب الذي أُدخلت به.`; $("batchResults").className="results"; $("batchResults").innerHTML=renderBatchTable(app.batch); $("btnExport").disabled=false;}
70
+ function renderBatchTable(items){return `<div class="table-wrap"><table><thead><tr><th>رقم السجل التجاري</th><th>اسم المنشأة</th><th>مرات الظهور</th><th>الباحثون</th><th>حالة المطابقة</th></tr></thead><tbody>${items.map(item=>{const g=item.group;if(!g)return `<tr><td>${escapeHtml(item.register)}</td><td>—</td><td>0</td><td>—</td><td><span class="badge danger">غير موجود</span></td></tr>`;return `<tr><td><button class="row-button" data-entity="${escapeHtml(g.key)}">${escapeHtml(item.register)}</button></td><td>${escapeHtml(g.name)}</td><td>${g.rows.length}</td><td>${g.researchers.length}</td><td><span class="badge ${g.multiSurvey?"warn":"ok"}">${g.multiSurvey?"موجود في عدة مسوح":"موجود"}</span></td></tr>`;}).join("")}</tbody></table></div>`;}
71
+
72
+ function renderIntersections(){const groups=[...app.byRegister.entries()].map(([register,rows])=>groupInfo({key:`cr:${register}`,commercialRegister:register,rows})).filter(g=>g.multiSurvey).sort((a,b)=>b.surveys.length-a.surveys.length); const two=groups.filter(g=>g.surveys.length===2).length, three=groups.filter(g=>g.surveys.length>=3).length; $("intersectionMetrics").innerHTML=[metric(groups.length,"منشآت مشتركة"),metric(two,"في مسحين"),metric(three,"في 3 مسوح أو أكثر"),metric(app.byRegister.size,"سجلات قابلة للمطابقة"),metric(app.surveys.size,"إجمالي المسوح")].join(""); $("intersectionResults").className="results"; $("intersectionResults").innerHTML=groups.length?`<div class="table-wrap"><table><thead><tr><th>السجل التجاري</th><th>اسم المنشأة</th><th>عدد المسوح</th><th>المسوح</th><th>الباحثون</th></tr></thead><tbody>${groups.slice(0,200).map(g=>`<tr><td><button class="row-button" data-entity="${g.key}">${escapeHtml(g.commercialRegister)}</button></td><td>${escapeHtml(g.name)}</td><td>${g.surveys.length}</td><td>${escapeHtml(g.surveys.join("، "))}</td><td>${escapeHtml(g.researchers.join("، "))}</td></tr>`).join("")}</tbody></table></div>`:"<div class='empty-state'>لا توجد تقاطعات قابلة للحساب: لا يحتوي مصدر البيانات الحالي على سجلات تجارية صالحة أو أكثر من مسح واحد.</div>";}
73
+ function renderResearchers(){const q=normText($("researcherFilter").value); const entries=[...app.researchers.values()].filter(x=>normText(x.name).includes(q)).sort((a,b)=>b.rows.length-a.rows.length).slice(0,80); $("researcherResults").innerHTML=entries.length?entries.map(x=>{const surveys=[...new Set(x.rows.map(r=>r.surveyName))]; const shared=x.rows.filter(r=>r.commercialRegister&&new Set(app.byRegister.get(r.commercialRegister).map(z=>z.surveyName)).size>1).length;return `<article class="card researcher-card"><h3>${escapeHtml(x.name)}</h3><p>${x.rows.length} منشأة/سجل · ${surveys.length} مسح · ${shared} ظهور متقاطع</p><div class="badges"><span class="badge">${escapeHtml(surveys.join("، "))}</span></div></article>`;}).join(""):"<div class='empty-state'>لا يوجد باحث مطابق.</div>";}
74
+ function renderSurveys(){ $("surveyResults").innerHTML=[...app.surveys.entries()].map(([name,rows])=>{const researchers=new Set(rows.map(r=>r.researcher).filter(Boolean));const shared=new Set(rows.filter(r=>r.commercialRegister&&new Set(app.byRegister.get(r.commercialRegister).map(z=>z.surveyName)).size>1).map(r=>r.commercialRegister));return `<article class="card survey-card"><h3>${escapeHtml(name)}</h3><p>${rows.length.toLocaleString("ar-SA")} سجل</p><p>${researchers.size} باحث · ${shared.size} منشأة متقاطعة</p></article>`;}).join("");}
75
+
76
+ function xmlCell(value, style=""){return `<Cell${style?` ss:StyleID="${style}"`:""}><Data ss:Type="String">${escapeXml(value)}</Data></Cell>`;}function escapeXml(v){return String(v??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");}
77
+ function exportBatch(){if(!app.batch.length){toast("لا توجد نتائج لتصديرها.");return;} const max=Math.max(0,...app.batch.map(x=>x.group?.rows.length||0)); const headers=["رقم السجل التجاري","اسم المنشأة","عدد مرات الظهور","حالة المطابقة"];for(let i=1;i<=max;i++)headers.push(`الباحث ${i}`,`المسح ${i}`,`حالة الاستيفاء ${i}`); const data=app.batch.map(item=>{const g=item.group;const values=[item.register,g?.name||"",g?.rows.length||0,g?(g.multiSurvey?"موجود في عدة مسوح":"موجود"):"غير موجود"];const occurrences=g?[...g.rows].sort((a,b)=>a.surveyName.localeCompare(b.surveyName,"ar")):[];for(let i=0;i<max;i++){const r=occurrences[i];values.push(r?.researcher||"",r?.surveyName||"",r?.collectionStatus||"");}return values;}); const xml=`<?xml version="1.0" encoding="UTF-8"?><?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel"><Styles><Style ss:ID="h"><Font ss:Bold="1" ss:Color="#FFFFFF"/><Interior ss:Color="#4137A8" ss:Pattern="Solid"/><Alignment ss:Horizontal="Center" ss:Vertical="Center"/></Style></Styles><Worksheet ss:Name="نتائج الفحص" ss:RightToLeft="1"><Table><Row>${headers.map(h=>xmlCell(h,"h")).join("")}</Row>${data.map(row=>`<Row>${row.map((v,i)=>xmlCell(v,i===0?"":"")).join("")}</Row>`).join("")}</Table><AutoFilter x:Range="R1C1:R${data.length+1}C${headers.length}" xmlns="urn:schemas-microsoft-com:office:excel"/><WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"><FreezePanes/><FrozenNoSplit/><SplitHorizontal>1</SplitHorizontal><TopRowBottomPane>1</TopRowBottomPane><Panes><Pane><Number>3</Number></Pane></Panes></WorksheetOptions></Worksheet></Workbook>`; const blob=new Blob(["\uFEFF"+xml],{type:"application/vnd.ms-excel;charset=utf-8"});const a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download="نتائج_فحص_السجلات.xls";a.click();URL.revokeObjectURL(a.href);toast("تم إنشاء ملف Excel بنجاح.");}
78
+
79
+ function openEntity(key){const g=app.entityGroups.get(key);if(!g)return;showView("home");$("searchResults").className="results";$("searchResults").innerHTML=renderEntity(g);$("searchMeta").textContent="تفاصيل السجل المحدد.";}
80
+ function init(){try{loadData();renderMetrics();renderIntersections();renderResearchers();renderSurveys();}catch(err){console.error(err);document.querySelector("main").innerHTML="<div class='empty-state'>تعذر تحميل بيانات المسوح.</div>";return;} document.querySelectorAll(".nav-link").forEach(b=>b.addEventListener("click",()=>showView(b.dataset.view)));$("btnSearch").addEventListener("click",search);$("btnClear").addEventListener("click",()=>{$("q").value="";search();$("q").focus();});$("q").addEventListener("keydown",e=>{if(e.key==="Enter")search();});$("batchInput").addEventListener("input",updateBatchHint);$("btnBatch").addEventListener("click",batchCheck);$("btnBatchClear").addEventListener("click",()=>{$("batchInput").value="";app.batch=[];updateBatchHint();$("batchSummary").innerHTML="";$("batchResults").className="results empty-state";$("batchResults").textContent="الصق قائمة السجلات ثم اختر «فحص السجلات».";$("btnExport").disabled=true;});$("btnExport").addEventListener("click",exportBatch);$("researcherFilter").addEventListener("input",renderResearchers);document.addEventListener("click",e=>{const button=e.target.closest("[data-entity]");if(button)openEntity(button.dataset.entity);});}
81
+ init();
style.css CHANGED
@@ -1,28 +1,2 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }
 
1
+ :root{--primary:#4137A8;--secondary:#1CADE4;--ink:#0B1324;--muted:#5C6E88;--line:#E7ECF5;--bg:#F6F8FC;--card:#fff;--radius:20px;--shadow:0 12px 35px rgba(51,91,116,.1)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;color:var(--ink);font-family:system-ui,-apple-system,"Segoe UI",Tahoma,Arial,sans-serif;background:radial-gradient(900px 480px at 12% 0%,rgba(65,55,168,.1),transparent 60%),radial-gradient(900px 480px at 88% 0%,rgba(28,173,228,.1),transparent 60%),var(--bg);min-height:100vh}.wrap{width:min(1180px,92vw);margin:auto;padding:22px 0 96px}.site-header{display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;margin-bottom:30px}.brand{display:flex;align-items:center;gap:12px;color:inherit;text-decoration:none}.brand strong{display:block;color:var(--primary);font-size:18px}.brand small{display:block;color:var(--muted);font-size:12px;margin-top:3px}.logo{width:44px;height:44px;border-radius:16px;background:radial-gradient(16px 16px at 28% 26%,#fff,transparent 60%),linear-gradient(135deg,var(--primary),var(--secondary));box-shadow:var(--shadow);flex:none}.nav{display:flex;gap:4px;flex-wrap:wrap}.nav-link{border:0;background:transparent;padding:10px 12px;border-radius:12px;color:var(--muted);font:inherit;cursor:pointer}.nav-link:hover,.nav-link.active{background:#fff;color:var(--primary);box-shadow:0 5px 16px rgba(51,91,116,.08);font-weight:750}.view{display:none}.view.active{display:block}.hero{padding:12px 0 18px}.eyebrow{margin:0 0 5px;color:var(--secondary);font-size:13px;font-weight:800}.hero h1,.section-heading h1{font-size:clamp(24px,4vw,34px);margin:0 0 8px;color:var(--primary)}.hero p,.section-heading p{margin:0;color:var(--muted);line-height:1.7}.card{background:rgba(255,255,255,.94);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}.search-card,.batch-card{padding:20px}.search-card label,.batch-card label{display:block;font-weight:800;color:var(--primary);margin-bottom:10px}.search-row{display:flex;gap:10px}input,textarea{font:inherit;color:var(--ink);border:1px solid var(--line);border-radius:14px;background:#fff;outline:none}input:focus,textarea:focus{border-color:rgba(28,173,228,.65);box-shadow:0 0 0 4px rgba(28,173,228,.12)}.search-row input{flex:1;padding:13px 15px}.btn{border:0;border-radius:14px;padding:11px 15px;font:inherit;font-weight:800;cursor:pointer;white-space:nowrap;transition:opacity .2s,transform .1s}.btn:hover{opacity:.91}.btn:active{transform:translateY(1px)}.btn:disabled{opacity:.5;cursor:not-allowed}.primary{background:linear-gradient(135deg,var(--primary),var(--secondary));color:#fff}.secondary{background:#fff;color:var(--muted);border:1px solid var(--line)}.hint{color:var(--muted);font-size:12.5px;line-height:1.6}.metrics{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:18px 0}.metrics.compact{grid-template-columns:repeat(5,1fr)}.metric{background:rgba(255,255,255,.9);border:1px solid var(--line);border-radius:16px;padding:14px;box-shadow:0 7px 21px rgba(51,91,116,.06)}.metric b{font-size:22px;display:block;color:var(--primary);margin-bottom:3px}.metric span{color:var(--muted);font-size:12px}.section-heading{display:flex;justify-content:space-between;align-items:end;gap:14px;margin:26px 0 12px}.section-heading h2{margin:0 0 5px;color:var(--primary);font-size:20px}.results{display:grid;gap:12px}.empty-state{padding:30px;background:#fff;border:1px dashed #b8c5d7;border-radius:var(--radius);color:var(--muted);text-align:center}.entity-card{background:#fff;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;box-shadow:0 7px 21px rgba(51,91,116,.06)}.entity-top{padding:16px 18px;display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.entity-top h3{margin:0;color:var(--primary);font-size:17px}.entity-top p{margin:5px 0 0;color:var(--muted);font-size:13px}.badges{display:flex;gap:6px;flex-wrap:wrap}.badge{font-size:12px;padding:5px 9px;border-radius:999px;background:#f4f7fb;border:1px solid var(--line);color:var(--muted);white-space:nowrap}.badge.ok{background:#edf9f1;border-color:#c9ebd6;color:#14733c}.badge.warn{background:#fff8e9;border-color:#f6dfab;color:#8b6100}.badge.danger{background:#fff0f0;border-color:#f2c4c4;color:#a72b2b}.occurrences{border-top:1px solid var(--line)}.occurrence{display:grid;grid-template-columns:1fr 1fr 1.4fr 1fr;gap:12px;padding:13px 18px;border-bottom:1px solid var(--line);font-size:13px}.occurrence:last-child{border:0}.occurrence span{color:var(--muted);display:block;font-size:11px;margin-bottom:3px}.table-wrap{overflow:auto;background:#fff;border:1px solid var(--line);border-radius:var(--radius);box-shadow:0 7px 21px rgba(51,91,116,.06)}table{width:100%;min-width:780px;border-collapse:collapse;font-size:13px}th{text-align:right;color:var(--primary);background:#f9fbfe;position:sticky;top:0;z-index:1}th,td{padding:12px;border-bottom:1px solid var(--line);vertical-align:top}tr:last-child td{border:0}tr:hover td{background:#fbfdff}.row-button{display:block;width:100%;border:0;background:transparent;text-align:right;font:inherit;cursor:pointer;padding:0;color:inherit}.batch-card textarea{width:100%;padding:14px;resize:vertical;line-height:1.65}.batch-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px}.batch-actions div{display:flex;gap:8px}.small-search{padding:10px 12px;width:min(280px,100%)}.researcher-card,.survey-card{padding:16px}.researcher-card h3,.survey-card h3{color:var(--primary);margin:0 0 7px}.researcher-card p,.survey-card p{margin:5px 0;color:var(--muted);font-size:13px}.survey-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px}.toast{position:fixed;bottom:72px;left:50%;transform:translateX(-50%) translateY(20px);opacity:0;background:#0B1324;color:#fff;padding:11px 16px;border-radius:12px;box-shadow:var(--shadow);transition:.2s;z-index:10;font-size:13px}.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}footer{position:fixed;bottom:0;right:0;left:0;text-align:center;padding:10px;background:rgba(255,255,255,.88);backdrop-filter:blur(8px);border-top:1px solid var(--line);color:var(--muted);font-size:12px}footer b{color:var(--primary)}
2
+ @media(max-width:800px){.metrics,.metrics.compact{grid-template-columns:repeat(2,1fr)}.site-header{align-items:flex-start}.nav{width:100%;overflow:auto;flex-wrap:nowrap;padding-bottom:2px}.nav-link{white-space:nowrap}.occurrence{grid-template-columns:repeat(2,1fr)}.section-heading{align-items:flex-start;flex-direction:column}.entity-top{flex-direction:column}.search-row{flex-wrap:wrap}.search-row input{min-width:100%;flex-basis:100%}.batch-actions{align-items:flex-start;flex-direction:column}.batch-actions div{width:100%}.batch-actions .btn{flex:1}}@media(max-width:480px){.wrap{width:min(94vw,1180px);padding-top:14px}.metrics,.metrics.compact{grid-template-columns:1fr 1fr;gap:8px}.metric{padding:11px}.metric b{font-size:18px}.search-row .btn{flex:1}.occurrence{padding:12px}.hero{padding-top:0}}