Upload 5 files
Browse files- .gitattributes +35 -35
- data.js +0 -0
- index.html +52 -17
- script.js +81 -0
- 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 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");}
|
| 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");}
|
| 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 |
-
|
| 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}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|