UI beautification: SVG icons, polished buttons, glass navbar, refined login, animated orbs, modern cards
This commit is contained in:
+110
-9
@@ -2,26 +2,62 @@
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<title>{% block title %}سجل العقارات اللبناني{% endblock %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Kufi+Arabic:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-backdrop" aria-hidden="true">
|
||||
<span class="backdrop-orb orb-1"></span>
|
||||
<span class="backdrop-orb orb-2"></span>
|
||||
</div>
|
||||
{% block navbar %}
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<a class="nav-brand" href="/">🏠 سجل العقارات</a>
|
||||
<button class="nav-toggle" id="navToggle" aria-label="القائمة">
|
||||
<a class="nav-brand" href="/">
|
||||
<span class="nav-brand-mark">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 21h18"/>
|
||||
<path d="M5 21V7l7-4 7 4v14"/>
|
||||
<path d="M9 21v-6h6v6"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="nav-brand-text">سجل العقارات</span>
|
||||
</a>
|
||||
<button class="nav-toggle" id="navToggle" aria-label="القائمة" aria-expanded="false" aria-controls="navLinks">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div class="nav-links" id="navLinks">
|
||||
<a href="/" class="nav-link">رفع وثائق</a>
|
||||
<a href="/review/next" class="nav-link">مراجعة</a>
|
||||
<a href="/search" class="nav-link">بحث</a>
|
||||
<a href="/documents" class="nav-link">قائمة الوثائق</a>
|
||||
<a href="/auth/users" class="nav-link">الإدارة</a>
|
||||
<a href="/auth/logout" class="nav-link">تسجيل خروج</a>
|
||||
<a href="/" class="nav-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||
رفع وثائق
|
||||
</a>
|
||||
<a href="/review/next" class="nav-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
||||
مراجعة
|
||||
</a>
|
||||
<a href="/search" class="nav-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
بحث
|
||||
</a>
|
||||
<a href="/documents" class="nav-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
قائمة الوثائق
|
||||
</a>
|
||||
<a href="/auth/users" class="nav-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15c-4 0-7 2-7 4v1h14v-1c0-2-3-4-7-4z"/><circle cx="12" cy="8" r="4"/></svg>
|
||||
الإدارة
|
||||
</a>
|
||||
<a href="/auth/logout" class="nav-link nav-link-logout">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||||
خروج
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -31,6 +67,33 @@
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<nav class="mobile-dock" aria-label="التنقل السريع">
|
||||
<a href="/" class="mobile-dock-link" data-match="/">
|
||||
<span class="mobile-dock-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||
</span>
|
||||
<span>رفع</span>
|
||||
</a>
|
||||
<a href="/review/next" class="mobile-dock-link" data-match="/review">
|
||||
<span class="mobile-dock-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
||||
</span>
|
||||
<span>مراجعة</span>
|
||||
</a>
|
||||
<a href="/search" class="mobile-dock-link" data-match="/search,/persons">
|
||||
<span class="mobile-dock-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
</span>
|
||||
<span>بحث</span>
|
||||
</a>
|
||||
<a href="/documents" class="mobile-dock-link" data-match="/documents">
|
||||
<span class="mobile-dock-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
</span>
|
||||
<span>وثائق</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
const navToggle = document.getElementById('navToggle');
|
||||
const navLinks = document.getElementById('navLinks');
|
||||
@@ -38,8 +101,46 @@
|
||||
navToggle.addEventListener('click', () => {
|
||||
navLinks.classList.toggle('open');
|
||||
navToggle.classList.toggle('active');
|
||||
navToggle.setAttribute('aria-expanded', String(navLinks.classList.contains('open')));
|
||||
});
|
||||
|
||||
navLinks.querySelectorAll('a').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
navLinks.classList.remove('open');
|
||||
navToggle.classList.remove('active');
|
||||
navToggle.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
document.querySelectorAll('.export-dropdown').forEach(dropdown => {
|
||||
if (!dropdown.contains(event.target)) {
|
||||
dropdown.querySelector('.export-menu')?.classList.remove('show');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-export-toggle]').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
button.nextElementSibling?.classList.toggle('show');
|
||||
});
|
||||
});
|
||||
|
||||
const currentPath = window.location.pathname;
|
||||
const mobileDock = document.querySelector('.mobile-dock');
|
||||
if (currentPath.startsWith('/auth/login')) {
|
||||
mobileDock?.setAttribute('hidden', 'hidden');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.mobile-dock-link').forEach(link => {
|
||||
const patterns = (link.dataset.match || '').split(',').filter(Boolean);
|
||||
const isRoot = patterns.includes('/') && currentPath === '/';
|
||||
const isMatched = isRoot || patterns.some(pattern => pattern !== '/' && currentPath.startsWith(pattern));
|
||||
if (isMatched) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
|
||||
+19
-12
@@ -3,8 +3,15 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>قائمة الوثائق</h1>
|
||||
<a href="/" class="btn btn-primary">+ رفع وثائق جديدة</a>
|
||||
<div>
|
||||
<span class="page-kicker">متابعة المعالجة</span>
|
||||
<h1>قائمة الوثائق</h1>
|
||||
<p class="subtitle">تابع حالة كل وثيقة وانتقل مباشرة إلى شاشة المراجعة عند الحاجة.</p>
|
||||
</div>
|
||||
<a href="/" class="btn btn-primary">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||
رفع وثائق جديدة
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if uploaded %}
|
||||
@@ -33,12 +40,12 @@
|
||||
|
||||
{% if stats.pending_review %}
|
||||
<div class="alert-banner">
|
||||
<a href="/review/next">▶ ابدأ مراجعة {{ stats.pending_review }} وثيقة</a>
|
||||
<a href="/review/next">ابدأ مراجعة {{ stats.pending_review }} وثيقة →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="results-table">
|
||||
<table class="results-table responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
@@ -53,21 +60,21 @@
|
||||
<tbody>
|
||||
{% for d in documents %}
|
||||
<tr>
|
||||
<td>{{ d.id }}</td>
|
||||
<td>
|
||||
<a href="/review/{{ d.id }}">
|
||||
<td data-label="#">{{ d.id }}</td>
|
||||
<td data-label="الصورة">
|
||||
<a href="/review/{{ d.id }}" title="فتح الوثيقة {{ d.id }} للمراجعة" aria-label="فتح الوثيقة {{ d.id }} للمراجعة">
|
||||
<img src="/uploads/{{ d.image_path }}" class="doc-thumb-mini" alt="">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="الشخص">
|
||||
{% if d.person_id %}
|
||||
<a href="/persons/{{ d.person_id }}">{{ d.first_name or '' }} {{ d.family_name or '' }}</a>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td>{{ d.request_number or '—' }}</td>
|
||||
<td><span class="status-badge status-{{ d.status }}">{{ d.status }}</span></td>
|
||||
<td>{{ d.created_at[:10] if d.created_at else '' }}</td>
|
||||
<td class="doc-actions">
|
||||
<td data-label="رقم الطلب">{{ d.request_number or '—' }}</td>
|
||||
<td data-label="الحالة"><span class="status-badge status-{{ d.status }}">{{ d.status }}</span></td>
|
||||
<td data-label="التاريخ">{{ d.created_at[:10] if d.created_at else '' }}</td>
|
||||
<td data-label="الإجراءات" class="doc-actions">
|
||||
<a href="/review/{{ d.id }}" class="btn btn-sm btn-secondary">
|
||||
{% if d.status == 'confirmed' %}عرض{% else %}مراجعة{% endif %}
|
||||
</a>
|
||||
|
||||
+38
-12
@@ -2,9 +2,16 @@
|
||||
{% block title %}رفع وثائق — سجل العقارات{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>بطاقات معلومات الملكية العقارية</h1>
|
||||
<p class="subtitle">ارفع صور أو ملفات PDF للوثائق — اختر محرك الاستخراج (مجاني أو ذكاء اصطناعي)</p>
|
||||
<div class="page-header page-header-hero">
|
||||
<div>
|
||||
<span class="page-kicker">منصة الأرشفة والمراجعة</span>
|
||||
<h1>بطاقات معلومات الملكية العقارية</h1>
|
||||
<p class="subtitle">ارفع صور أو ملفات PDF، ثم راجع النتائج واستكمل البحث من واجهة واحدة واضحة وسريعة.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<a href="/search" class="btn btn-secondary">فتح البحث</a>
|
||||
<a href="/documents" class="btn btn-primary">عرض الوثائق</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if stats and stats.total %}
|
||||
@@ -16,16 +23,24 @@
|
||||
</div>
|
||||
{% if stats.pending_review %}
|
||||
<div class="alert-banner">
|
||||
<a href="/review/next">▶ ابدأ مراجعة {{ stats.pending_review }} وثيقة</a>
|
||||
<a href="/review/next">ابدأ مراجعة {{ stats.pending_review }} وثيقة →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="upload-card">
|
||||
<div class="card-heading">
|
||||
<div>
|
||||
<h2>رفع ملفات جديدة</h2>
|
||||
<p>اختر دفعة صور أو PDF، ثم حدّد محرك الاستخراج قبل بدء المعالجة.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="files" id="fileInput" multiple accept=".jpg,.jpeg,.png,.webp,.pdf" class="file-input">
|
||||
<label for="fileInput" class="drop-zone" id="dropZone">
|
||||
<div class="drop-icon">📄</div>
|
||||
<div class="drop-icon">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||
</div>
|
||||
<p class="drop-text">اسحب الملفات هنا أو انقر للاختيار</p>
|
||||
<p class="drop-hint">JPG، PNG، PDF — يمكن رفع عدة ملفات دفعة واحدة</p>
|
||||
</label>
|
||||
@@ -51,7 +66,8 @@
|
||||
|
||||
<div id="uploadActions" class="upload-actions hidden">
|
||||
<button type="submit" class="btn btn-primary btn-large">
|
||||
⬆ رفع ومعالجة
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||
رفع ومعالجة
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="clearFiles()">إلغاء</button>
|
||||
</div>
|
||||
@@ -60,12 +76,22 @@
|
||||
|
||||
<div class="quick-links">
|
||||
<a href="/search" class="quick-link-card">
|
||||
<span class="ql-icon">🔍</span>
|
||||
<span>البحث في قاعدة البيانات</span>
|
||||
<span class="ql-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
</span>
|
||||
<span>
|
||||
<strong>البحث في قاعدة البيانات</strong>
|
||||
<small>ابحث بالأسماء أو بالعقارات مع نتائج منظّمة.</small>
|
||||
</span>
|
||||
</a>
|
||||
<a href="/documents" class="quick-link-card">
|
||||
<span class="ql-icon">📋</span>
|
||||
<span>قائمة جميع الوثائق</span>
|
||||
<span class="ql-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
</span>
|
||||
<span>
|
||||
<strong>قائمة جميع الوثائق</strong>
|
||||
<small>تابع الحالات، افتح المراجعة، أو احذف المستندات غير المطلوبة.</small>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -96,7 +122,7 @@ fileInput.addEventListener('change', () => showFiles(fileInput.files));
|
||||
function showFiles(files) {
|
||||
if (!files.length) return;
|
||||
fileList.innerHTML = Array.from(files).map(f =>
|
||||
`<div class="file-item">${f.name.endsWith('.pdf')?'📕':'📄'} ${f.name} <span class="file-size">${(f.size/1024).toFixed(0)} KB</span></div>`
|
||||
`<div class="file-item">${f.name} <span class="file-size">${(f.size/1024).toFixed(0)} KB</span></div>`
|
||||
).join('');
|
||||
fileList.classList.remove('hidden');
|
||||
document.getElementById('providerSection').classList.remove('hidden');
|
||||
@@ -112,7 +138,7 @@ function clearFiles() {
|
||||
}
|
||||
|
||||
document.getElementById('uploadForm').addEventListener('submit', function() {
|
||||
document.querySelector('.btn-primary').textContent = '⏳ جارٍ الرفع...';
|
||||
document.querySelector('.btn-primary').textContent = 'جارٍ الرفع...';
|
||||
document.querySelector('.btn-primary').disabled = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
+12
-2
@@ -6,7 +6,17 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="login-card">
|
||||
<h2>🏠 تسجيل الدخول</h2>
|
||||
<div class="login-logo">
|
||||
<span class="login-logo-icon">
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 21h18"/>
|
||||
<path d="M5 21V7l7-4 7 4v14"/>
|
||||
<path d="M9 21v-6h6v6"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<h2>تسجيل الدخول</h2>
|
||||
<p class="login-subtitle">أدخل بياناتك للوصول إلى سجل العقارات</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="login-error">{{ error }}</div>
|
||||
@@ -23,7 +33,7 @@
|
||||
<input type="password" id="password" name="password" required placeholder="أدخل كلمة المرور">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-large" style="width:100%; justify-content:center; margin-top:.5rem;">
|
||||
<button type="submit" class="btn btn-primary btn-large btn-block login-submit">
|
||||
دخول
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -4,18 +4,28 @@
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<a href="/search" class="back-link">← العودة للبحث</a>
|
||||
<a href="/search" class="back-link no-print">← العودة للبحث</a>
|
||||
<h1>{{ person.first_name }} {{ person.father_name or '' }} {{ person.family_name or '' }}</h1>
|
||||
{% if current_search_scope %}
|
||||
<p class="subtitle">عرض نتائج نطاق البحث: {{ current_search_scope }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="header-actions no-print">
|
||||
<button type="button" class="btn btn-secondary" onclick="window.print()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
||||
طباعة
|
||||
</button>
|
||||
{% set scopes = documents | selectattr('search_scope') | map(attribute='search_scope') | unique | list %}
|
||||
<div class="export-dropdown">
|
||||
<button class="btn btn-primary" onclick="this.nextElementSibling.classList.toggle('show')">📥 تصدير كملف إكسل ▾</button>
|
||||
<button type="button" class="btn btn-primary" data-export-toggle>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
تصدير كملف إكسل ▾
|
||||
</button>
|
||||
<div class="export-menu">
|
||||
<a href="/persons/{{ person.id }}/export" class="export-option" download>📥 تصدير الكل</a>
|
||||
<a href="/persons/{{ person.id }}/export" class="export-option" download>تصدير الكل</a>
|
||||
{% for scope in scopes %}
|
||||
{% if scope %}
|
||||
<a href="/persons/{{ person.id }}/export?qaza={{ scope|urlencode }}" class="export-option" download>📥 {{ scope }}</a>
|
||||
<a href="/persons/{{ person.id }}/export?qaza={{ scope|urlencode }}" class="export-option" download>{{ scope }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -35,6 +45,11 @@
|
||||
{% set scopes = documents | selectattr('search_scope') | map(attribute='search_scope') | unique | list %}
|
||||
{% if scopes %}<div class="info-item"><span class="info-label">القضاء</span><span>{{ scopes | join('، ') }}</span></div>{% endif %}
|
||||
</div>
|
||||
{% if current_search_scope %}
|
||||
<div class="person-actions no-print">
|
||||
<a href="/persons/{{ person.id }}" class="btn btn-secondary">عرض كل النطاقات</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
@@ -43,7 +58,7 @@
|
||||
|
||||
{% if properties %}
|
||||
<div class="table-wrapper">
|
||||
<table class="results-table props-full">
|
||||
<table class="results-table props-full responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
@@ -59,29 +74,29 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pr in properties %}
|
||||
<tr {% if pr.ownership_type == 'لا يملك' %}style="background-color: #fff3f3; color: #dc3545; font-weight: bold;"{% endif %}>
|
||||
<td class="row-num">{{ loop.index }}</td>
|
||||
<td>{{ pr.party_name or '' }}</td>
|
||||
<td class="prop-num">{{ pr.property_number or '' }}</td>
|
||||
<td>{{ pr.section or '' }}</td>
|
||||
<td>{{ pr.block or '' }}</td>
|
||||
<td>{{ pr.real_estate_district or '' }}</td>
|
||||
<td>{{ pr.qaza or '' }}</td>
|
||||
<td>{{ pr.num_shares or '' }}</td>
|
||||
<td>{{ pr.ownership_type or '' }}</td>
|
||||
<tr class="{{ 'ownership-alert' if pr.ownership_type == 'لا يملك' else '' }}">
|
||||
<td data-label="#" class="row-num">{{ loop.index }}</td>
|
||||
<td data-label="اسم الفريق">{{ pr.party_name or '' }}</td>
|
||||
<td data-label="رقم العقار" class="prop-num">{{ pr.property_number or '' }}</td>
|
||||
<td data-label="القسم">{{ pr.section or '' }}</td>
|
||||
<td data-label="البلوك">{{ pr.block or '' }}</td>
|
||||
<td data-label="المنطقة العقارية">{{ pr.real_estate_district or '' }}</td>
|
||||
<td data-label="القضاء">{{ pr.qaza or '' }}</td>
|
||||
<td data-label="عدد الأسهم">{{ pr.num_shares or '' }}</td>
|
||||
<td data-label="نوع الملكية">{{ pr.ownership_type or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="error-banner" style="justify-content:center;">
|
||||
<div class="error-banner centered-banner">
|
||||
⚠️ نتيجة البحث: لا يملك أي عقار في نطاق البحث المذكور.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if documents %}
|
||||
<div class="docs-section">
|
||||
<div class="docs-section no-print">
|
||||
<h3>الوثائق الأصلية</h3>
|
||||
<div class="doc-thumbnails">
|
||||
{% for d in documents %}
|
||||
@@ -101,19 +116,3 @@
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Export dropdown toggle
|
||||
document.addEventListener('click', function(e) {
|
||||
document.querySelectorAll('.export-menu').forEach(function(menu) {
|
||||
if (!menu.parentElement.contains(e.target)) {
|
||||
menu.classList.remove('show');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '.export-menu.show { display: block !important; } .export-option:hover { background-color: #f0f0f0; }';
|
||||
document.head.appendChild(style);
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
+140
-67
@@ -8,13 +8,19 @@
|
||||
{% block content %}
|
||||
<div class="review-header">
|
||||
<div class="review-title">
|
||||
<h2>مراجعة الوثيقة #{{ doc.id }}</h2>
|
||||
<div>
|
||||
<span class="page-kicker">المراجعة والتحقق</span>
|
||||
<h2>مراجعة الوثيقة #{{ doc.id }}</h2>
|
||||
</div>
|
||||
<span class="status-badge status-{{ doc.status }}">{{ doc.status }}</span>
|
||||
</div>
|
||||
<div class="review-nav">
|
||||
<a href="/documents" class="btn btn-secondary btn-sm">← قائمة الوثائق</a>
|
||||
<a href="/review/next" class="btn btn-secondary btn-sm">التالية ←</a>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteDoc({{ doc.id }})">🗑 حذف الوثيقة</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteDoc({{ doc.id }})">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
حذف الوثيقة
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,9 +35,15 @@
|
||||
<!-- Image Panel -->
|
||||
<div class="image-panel">
|
||||
<div class="image-toolbar">
|
||||
<button onclick="zoomIn()" class="btn btn-sm">🔍+</button>
|
||||
<button onclick="zoomOut()" class="btn btn-sm">🔍-</button>
|
||||
<button onclick="resetZoom()" class="btn btn-sm">↺</button>
|
||||
<button type="button" onclick="zoomIn()" class="btn btn-sm btn-secondary" title="تكبير">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
|
||||
</button>
|
||||
<button type="button" onclick="zoomOut()" class="btn btn-sm btn-secondary" title="تصغير">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
|
||||
</button>
|
||||
<button type="button" onclick="resetZoom()" class="btn btn-sm btn-secondary" title="إعادة ضبط">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="image-container" id="imageContainer">
|
||||
<img id="docImage" src="/uploads/{{ doc.image_path }}" alt="وثيقة" class="doc-image">
|
||||
@@ -47,46 +59,47 @@
|
||||
<h3>بيانات الشخص</h3>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>الاسم *</label>
|
||||
<input type="text" name="first_name" value="{{ doc.person.get('first_name') or '' }}" required>
|
||||
<label for="first_name">الاسم *</label>
|
||||
<input id="first_name" type="text" name="first_name" value="{{ doc.person.get('first_name') or '' }}" title="الاسم" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>اسم الأب</label>
|
||||
<input type="text" name="father_name" value="{{ doc.person.get('father_name') or '' }}">
|
||||
<label for="father_name">اسم الأب</label>
|
||||
<input id="father_name" type="text" name="father_name" value="{{ doc.person.get('father_name') or '' }}" title="اسم الأب">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>اسم الأم</label>
|
||||
<input type="text" name="mother_name" value="{{ doc.person.get('mother_name') or '' }}">
|
||||
<label for="mother_name">اسم الأم</label>
|
||||
<input id="mother_name" type="text" name="mother_name" value="{{ doc.person.get('mother_name') or '' }}" title="اسم الأم">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>اللقب</label>
|
||||
<input type="text" name="family_name" value="{{ doc.person.get('family_name') or '' }}">
|
||||
<label for="family_name">اللقب</label>
|
||||
<input id="family_name" type="text" name="family_name" value="{{ doc.person.get('family_name') or '' }}" title="اللقب">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>الهوا / المنشأ</label>
|
||||
<input type="text" name="family_origin" value="{{ doc.person.get('family_origin') or '' }}">
|
||||
<label for="family_origin">الهوا / المنشأ</label>
|
||||
<input id="family_origin" type="text" name="family_origin" value="{{ doc.person.get('family_origin') or '' }}" title="الهوا أو المنشأ">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>الجنسية</label>
|
||||
<input type="text" name="nationality" value="{{ doc.person.get('nationality') or '' }}">
|
||||
<label for="nationality">الجنسية</label>
|
||||
<input id="nationality" type="text" name="nationality" value="{{ doc.person.get('nationality') or '' }}" title="الجنسية">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>تاريخ الولادة</label>
|
||||
<input type="text" name="birth_date" value="{{ doc.person.get('birth_date') or '' }}">
|
||||
<label for="birth_date">تاريخ الولادة</label>
|
||||
<input id="birth_date" type="text" name="birth_date" value="{{ doc.person.get('birth_date') or '' }}" title="تاريخ الولادة">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>رقم السجل</label>
|
||||
<input type="text" name="registry_number" value="{{ doc.person.get('registry_number') or '' }}">
|
||||
<label for="registry_number">رقم السجل</label>
|
||||
<input id="registry_number" type="text" name="registry_number" value="{{ doc.person.get('registry_number') or '' }}" title="رقم السجل">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>مكان السجل</label>
|
||||
<input type="text" name="registry_place" value="{{ doc.person.get('registry_place') or '' }}">
|
||||
<label for="registry_place">مكان السجل</label>
|
||||
<input id="registry_place" type="text" name="registry_place" value="{{ doc.person.get('registry_place') or '' }}" title="مكان السجل">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Merge suggestion (populated by JS) -->
|
||||
<div id="mergeSuggestion" class="merge-banner hidden">
|
||||
<h4>شخص مشابه موجود في قاعدة البيانات:</h4>
|
||||
<p id="mergeScopeNotice" class="merge-note hidden"></p>
|
||||
<div id="mergeMatches"></div>
|
||||
<div class="merge-actions">
|
||||
<label><input type="radio" name="merge_action" value="merge" checked> دمج مع الشخص الموجود</label>
|
||||
@@ -101,20 +114,20 @@
|
||||
<h3>بيانات الطلب</h3>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>رقم الطلب</label>
|
||||
<input type="text" name="request_number" value="{{ doc.request_number or '' }}">
|
||||
<label for="request_number">رقم الطلب</label>
|
||||
<input id="request_number" type="text" name="request_number" value="{{ doc.request_number or '' }}" title="رقم الطلب">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>تاريخ الطلب</label>
|
||||
<input type="text" name="request_date" value="{{ doc.request_date or '' }}">
|
||||
<label for="request_date">تاريخ الطلب</label>
|
||||
<input id="request_date" type="text" name="request_date" value="{{ doc.request_date or '' }}" title="تاريخ الطلب">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>معلومات الصفحة</label>
|
||||
<input type="text" name="page_info" value="{{ doc.page_info or '' }}" placeholder="مثال: 1 من 3">
|
||||
<label for="page_info">معلومات الصفحة</label>
|
||||
<input id="page_info" type="text" name="page_info" value="{{ doc.page_info or '' }}" title="معلومات الصفحة" placeholder="مثال: 1 من 3">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>نطاق البحث (القضاء)</label>
|
||||
<input type="text" name="search_scope" value="{{ doc.search_scope or '' }}" placeholder="مثال: المتن">
|
||||
<label for="search_scope">نطاق البحث (القضاء)</label>
|
||||
<input id="search_scope" type="text" name="search_scope" value="{{ doc.search_scope or '' }}" title="نطاق البحث" placeholder="مثال: المتن">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -125,8 +138,8 @@
|
||||
<h3>العقارات المملوكة</h3>
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="addRow()">+ إضافة صف</button>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table id="propertiesTable" class="props-table">
|
||||
<div class="table-wrapper table-wrapper-editable">
|
||||
<table id="propertiesTable" class="props-table mobile-edit-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>اسم الفريق</th>
|
||||
@@ -143,15 +156,15 @@
|
||||
<tbody id="propertiesTbody">
|
||||
{% for prop in doc.properties %}
|
||||
<tr>
|
||||
<td><input type="text" value="{{ prop.party_name or '' }}"></td>
|
||||
<td><input type="text" value="{{ prop.property_number or '' }}"></td>
|
||||
<td><input type="text" value="{{ prop.section or '' }}"></td>
|
||||
<td><input type="text" value="{{ prop.block or '' }}"></td>
|
||||
<td><input type="text" value="{{ prop.real_estate_district or '' }}"></td>
|
||||
<td><input type="text" value="{{ prop.qaza or '' }}"></td>
|
||||
<td><input type="text" value="{{ prop.num_shares or '' }}"></td>
|
||||
<td><input type="text" value="{{ prop.ownership_type or '' }}"></td>
|
||||
<td><button type="button" class="btn-del" onclick="removeRow(this)">✕</button></td>
|
||||
<td data-label="اسم الفريق"><input type="text" value="{{ prop.party_name or '' }}" title="اسم الفريق" aria-label="اسم الفريق"></td>
|
||||
<td data-label="رقم العقار"><input type="text" value="{{ prop.property_number or '' }}" title="رقم العقار" aria-label="رقم العقار"></td>
|
||||
<td data-label="القسم"><input type="text" value="{{ prop.section or '' }}" title="القسم" aria-label="القسم"></td>
|
||||
<td data-label="البلوك"><input type="text" value="{{ prop.block or '' }}" title="البلوك" aria-label="البلوك"></td>
|
||||
<td data-label="المنطقة العقارية"><input type="text" value="{{ prop.real_estate_district or '' }}" title="المنطقة العقارية" aria-label="المنطقة العقارية"></td>
|
||||
<td data-label="القضاء"><input type="text" value="{{ prop.qaza or '' }}" title="القضاء" aria-label="القضاء"></td>
|
||||
<td data-label="عدد الأسهم"><input type="text" value="{{ prop.num_shares or '' }}" title="عدد الأسهم" aria-label="عدد الأسهم"></td>
|
||||
<td data-label="نوع الملكية"><input type="text" value="{{ prop.ownership_type or '' }}" title="نوع الملكية" aria-label="نوع الملكية"></td>
|
||||
<td data-label="حذف"><button type="button" class="btn-del" onclick="removeRow(this)">✕</button></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -162,7 +175,8 @@
|
||||
<!-- Actions -->
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-primary btn-large" onclick="confirmDocument()" id="confirmBtn">
|
||||
✓ تأكيد والانتقال للتالية
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
تأكيد والانتقال للتالية
|
||||
</button>
|
||||
<span class="keyboard-hint">أو اضغط Ctrl+Enter</span>
|
||||
</div>
|
||||
@@ -181,15 +195,15 @@ function addRow() {
|
||||
const tbody = document.getElementById('propertiesTbody');
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td><input type="text" value=""></td>
|
||||
<td><input type="text" value=""></td>
|
||||
<td><input type="text" value=""></td>
|
||||
<td><input type="text" value=""></td>
|
||||
<td><input type="text" value=""></td>
|
||||
<td><input type="text" value=""></td>
|
||||
<td><input type="text" value=""></td>
|
||||
<td><input type="text" value=""></td>
|
||||
<td><button type="button" class="btn-del" onclick="removeRow(this)">✕</button></td>
|
||||
<td data-label="اسم الفريق"><input type="text" value="" title="اسم الفريق" aria-label="اسم الفريق"></td>
|
||||
<td data-label="رقم العقار"><input type="text" value="" title="رقم العقار" aria-label="رقم العقار"></td>
|
||||
<td data-label="القسم"><input type="text" value="" title="القسم" aria-label="القسم"></td>
|
||||
<td data-label="البلوك"><input type="text" value="" title="البلوك" aria-label="البلوك"></td>
|
||||
<td data-label="المنطقة العقارية"><input type="text" value="" title="المنطقة العقارية" aria-label="المنطقة العقارية"></td>
|
||||
<td data-label="القضاء"><input type="text" value="" title="القضاء" aria-label="القضاء"></td>
|
||||
<td data-label="عدد الأسهم"><input type="text" value="" title="عدد الأسهم" aria-label="عدد الأسهم"></td>
|
||||
<td data-label="نوع الملكية"><input type="text" value="" title="نوع الملكية" aria-label="نوع الملكية"></td>
|
||||
<td data-label="حذف"><button type="button" class="btn-del" onclick="removeRow(this)">✕</button></td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
tr.querySelector('input').focus();
|
||||
@@ -252,25 +266,68 @@ function checkDuplicate() {
|
||||
const first = form.querySelector('[name=first_name]').value.trim();
|
||||
const father = form.querySelector('[name=father_name]').value.trim();
|
||||
const family = form.querySelector('[name=family_name]').value.trim();
|
||||
if (!first) return;
|
||||
|
||||
const params = new URLSearchParams({first_name: first, father_name: father, family_name: family});
|
||||
const res = await fetch('/api/check-duplicate?' + params);
|
||||
const data = await res.json();
|
||||
|
||||
const scope = form.querySelector('[name=search_scope]').value.trim();
|
||||
const registry = form.querySelector('[name=registry_number]').value.trim();
|
||||
const banner = document.getElementById('mergeSuggestion');
|
||||
const matchesDiv = document.getElementById('mergeMatches');
|
||||
const mergeIdInput = document.getElementById('mergePersonId');
|
||||
const scopeNotice = document.getElementById('mergeScopeNotice');
|
||||
const mergeRadio = form.querySelector('[name=merge_action][value=merge]');
|
||||
const newRadio = form.querySelector('[name=merge_action][value=new]');
|
||||
|
||||
if (!first) {
|
||||
banner.classList.add('hidden');
|
||||
matchesDiv.innerHTML = '';
|
||||
mergeIdInput.value = '';
|
||||
mergeRadio.disabled = false;
|
||||
scopeNotice.classList.add('hidden');
|
||||
newRadio.checked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
first_name: first,
|
||||
father_name: father,
|
||||
family_name: family,
|
||||
search_scope: scope,
|
||||
registry_number: registry,
|
||||
});
|
||||
const res = await fetch('/api/check-duplicate?' + params);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.matches && data.matches.length > 0) {
|
||||
matchesDiv.innerHTML = data.matches.map(m =>
|
||||
`<div class="merge-person" data-person-id="${m.id}">
|
||||
const firstAllowed = data.matches.find(m => m.merge_allowed);
|
||||
matchesDiv.innerHTML = data.matches.map(m => {
|
||||
const status = m.registry_match
|
||||
? 'مطابقة رقم سجل'
|
||||
: (m.same_scope ? 'نفس نطاق البحث' : 'نطاق مختلف');
|
||||
const scopes = (m.search_scopes || []).length ? (m.search_scopes || []).join('، ') : 'غير محدد';
|
||||
const safeClass = m.merge_allowed ? 'merge-person-safe' : 'merge-person-unsafe';
|
||||
const disabledAttr = m.merge_allowed ? '' : 'data-merge-disabled="true"';
|
||||
return `<div class="merge-person ${safeClass}" data-person-id="${m.id}" ${disabledAttr}>
|
||||
<span class="mp-name">${m.first_name} ${m.father_name||''} ${m.family_name||''}</span>
|
||||
<span class="mp-count">${m.property_count} عقار</span>
|
||||
<span class="mp-count">${m.document_count || 0} طلب</span>
|
||||
${m.family_origin ? '<span class="mp-count">'+m.family_origin+'</span>' : ''}
|
||||
</div>`
|
||||
).join('');
|
||||
mergeIdInput.value = data.matches[0].id;
|
||||
<span class="mp-count">${status}</span>
|
||||
<span class="mp-count">النطاقات: ${scopes}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
if (firstAllowed) {
|
||||
mergeIdInput.value = firstAllowed.id;
|
||||
mergeRadio.checked = true;
|
||||
mergeRadio.disabled = false;
|
||||
scopeNotice.textContent = 'تم تفعيل الدمج فقط للمرشحين ضمن نفس نطاق البحث أو عند تطابق رقم السجل.';
|
||||
scopeNotice.classList.remove('hidden');
|
||||
} else {
|
||||
mergeIdInput.value = '';
|
||||
newRadio.checked = true;
|
||||
mergeRadio.disabled = true;
|
||||
scopeNotice.textContent = 'المرشحون الموجودون يحملون الاسم نفسه لكن في نطاقات بحث مختلفة. سيتم إنشاء شخص جديد ما لم يوجد تطابق برقم السجل.';
|
||||
scopeNotice.classList.remove('hidden');
|
||||
}
|
||||
|
||||
banner.classList.remove('hidden');
|
||||
|
||||
// Click to select a match
|
||||
@@ -278,19 +335,35 @@ function checkDuplicate() {
|
||||
el.addEventListener('click', () => {
|
||||
matchesDiv.querySelectorAll('.merge-person').forEach(e => e.style.outline = '');
|
||||
el.style.outline = '2px solid var(--primary)';
|
||||
if (el.dataset.mergeDisabled === 'true') {
|
||||
mergeIdInput.value = '';
|
||||
newRadio.checked = true;
|
||||
mergeRadio.disabled = true;
|
||||
return;
|
||||
}
|
||||
mergeIdInput.value = el.dataset.personId;
|
||||
form.querySelector('[name=merge_action][value=merge]').checked = true;
|
||||
mergeRadio.disabled = false;
|
||||
mergeRadio.checked = true;
|
||||
});
|
||||
});
|
||||
|
||||
if (firstAllowed) {
|
||||
const selected = matchesDiv.querySelector(`[data-person-id="${firstAllowed.id}"]`);
|
||||
if (selected) {
|
||||
selected.style.outline = '2px solid var(--primary)';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
banner.classList.add('hidden');
|
||||
mergeIdInput.value = '';
|
||||
mergeRadio.disabled = false;
|
||||
scopeNotice.classList.add('hidden');
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Attach duplicate check to name fields
|
||||
document.querySelectorAll('[name=first_name],[name=father_name],[name=family_name]').forEach(input => {
|
||||
document.querySelectorAll('[name=first_name],[name=father_name],[name=family_name],[name=search_scope],[name=registry_number]').forEach(input => {
|
||||
input.addEventListener('blur', checkDuplicate);
|
||||
});
|
||||
// Run once on load
|
||||
@@ -300,7 +373,7 @@ checkDuplicate();
|
||||
async function confirmDocument() {
|
||||
const btn = document.getElementById('confirmBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⏳ جارٍ الحفظ...';
|
||||
btn.textContent = 'جارٍ الحفظ...';
|
||||
|
||||
try {
|
||||
const formData = getFormData();
|
||||
@@ -321,7 +394,7 @@ async function confirmDocument() {
|
||||
if (data.ok) {
|
||||
window.location.href = data.next;
|
||||
} else {
|
||||
alert('حدث خطأ: ' + JSON.stringify(data));
|
||||
alert('حدث خطأ: ' + (data.detail || JSON.stringify(data)));
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✓ تأكيد والانتقال للتالية';
|
||||
}
|
||||
|
||||
+77
-59
@@ -3,21 +3,36 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>البحث في قاعدة البيانات</h1>
|
||||
<div>
|
||||
<span class="page-kicker">البحث والاستكشاف</span>
|
||||
<h1>البحث في قاعدة البيانات</h1>
|
||||
<p class="subtitle">اختر طريقة البحث المناسبة ثم انتقل مباشرة إلى سجل الشخص أو العقار المطلوب.</p>
|
||||
</div>
|
||||
{% if persons or properties %}
|
||||
<div class="header-actions no-print">
|
||||
<button type="button" class="btn btn-secondary" onclick="window.print()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
||||
طباعة
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="search-card">
|
||||
<div class="search-card no-print">
|
||||
<form method="get" action="/search" id="searchForm">
|
||||
<div class="search-tabs">
|
||||
<button type="button" class="tab-btn active" onclick="showTab('person')">بحث بالاسم</button>
|
||||
<button type="button" class="tab-btn" onclick="showTab('property')">بحث بالعقار</button>
|
||||
<button type="button" class="tab-btn active" data-tab="person">بحث بالاسم</button>
|
||||
<button type="button" class="tab-btn" data-tab="property">بحث بالعقار</button>
|
||||
</div>
|
||||
|
||||
<div id="tab-person" class="tab-content">
|
||||
<div class="search-row">
|
||||
<input type="text" name="q" id="qInput" value="{{ q }}"
|
||||
placeholder="اكتب اسم الشخص..." class="search-input" autocomplete="off">
|
||||
<button type="submit" class="btn btn-primary">🔍 بحث</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
بحث
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +51,10 @@
|
||||
<input type="text" name="block" value="{{ block }}" placeholder="رقم البلوك">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">🔍 بحث</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
بحث
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -61,16 +79,23 @@
|
||||
{% set scopes = selected_person.documents | selectattr('search_scope') | map(attribute='search_scope') | unique | list %}
|
||||
{% if scopes %}<div class="info-item"><span class="info-label">القضاء</span><span>{{ scopes | join('، ') }}</span></div>{% endif %}
|
||||
</div>
|
||||
<div style="margin-top:1rem">
|
||||
<a href="/persons/{{ selected_person.person.id }}" class="btn btn-secondary">عرض التفاصيل كاملة</a>
|
||||
<div class="person-actions">
|
||||
<a href="/persons/{{ selected_person.person.id }}{% if selected_person.current_search_scope %}?search_scope={{ selected_person.current_search_scope|urlencode }}{% endif %}" class="btn btn-secondary no-print">عرض التفاصيل كاملة</a>
|
||||
<button type="button" class="btn btn-secondary no-print" onclick="window.print()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
||||
طباعة
|
||||
</button>
|
||||
{% set scopes = selected_person.documents | selectattr('search_scope') | map(attribute='search_scope') | unique | list %}
|
||||
<div class="export-dropdown">
|
||||
<button class="btn btn-primary" onclick="this.nextElementSibling.classList.toggle('show')">📥 تصدير كملف إكسل ▾</button>
|
||||
<div class="export-dropdown no-print">
|
||||
<button type="button" class="btn btn-primary" data-export-toggle>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
تصدير كملف إكسل ▾
|
||||
</button>
|
||||
<div class="export-menu">
|
||||
<a href="/persons/{{ selected_person.person.id }}/export" class="export-option" download>📥 تصدير الكل</a>
|
||||
<a href="/persons/{{ selected_person.person.id }}/export" class="export-option" download>تصدير الكل</a>
|
||||
{% for scope in scopes %}
|
||||
{% if scope %}
|
||||
<a href="/persons/{{ selected_person.person.id }}/export?qaza={{ scope|urlencode }}" class="export-option" download>📥 {{ scope }}</a>
|
||||
<a href="/persons/{{ selected_person.person.id }}/export?qaza={{ scope|urlencode }}" class="export-option" download>{{ scope }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -79,8 +104,8 @@
|
||||
</div>
|
||||
|
||||
{% if selected_person.properties %}
|
||||
<div class="table-wrapper" style="margin-top:1rem">
|
||||
<table class="results-table">
|
||||
<div class="table-wrapper table-wrapper-spaced">
|
||||
<table class="results-table responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
@@ -96,42 +121,41 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pr in selected_person.properties %}
|
||||
<tr {% if pr.ownership_type == 'لا يملك' %}style="background-color: #fff3f3; color: #dc3545; font-weight: bold;"{% endif %}>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ pr.party_name or '' }}</td>
|
||||
<td>{{ pr.property_number or '' }}</td>
|
||||
<td>{{ pr.section or '' }}</td>
|
||||
<td>{{ pr.block or '' }}</td>
|
||||
<td>{{ pr.real_estate_district or '' }}</td>
|
||||
<td>{{ pr.qaza or '' }}</td>
|
||||
<td>{{ pr.num_shares or '' }}</td>
|
||||
<td>{{ pr.ownership_type or '' }}</td>
|
||||
<tr class="{{ 'ownership-alert' if pr.ownership_type == 'لا يملك' else '' }}">
|
||||
<td data-label="#">{{ loop.index }}</td>
|
||||
<td data-label="اسم الفريق">{{ pr.party_name or '' }}</td>
|
||||
<td data-label="رقم العقار">{{ pr.property_number or '' }}</td>
|
||||
<td data-label="القسم">{{ pr.section or '' }}</td>
|
||||
<td data-label="البلوك">{{ pr.block or '' }}</td>
|
||||
<td data-label="المنطقة العقارية">{{ pr.real_estate_district or '' }}</td>
|
||||
<td data-label="القضاء">{{ pr.qaza or '' }}</td>
|
||||
<td data-label="عدد الأسهم">{{ pr.num_shares or '' }}</td>
|
||||
<td data-label="نوع الملكية">{{ pr.ownership_type or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="error-banner" style="margin-top:1rem; justify-content:center;">
|
||||
<div class="error-banner centered-banner banner-spaced">
|
||||
⚠️ نتيجة البحث: لا يملك أي عقار في القضاء المذكور.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="persons-grid">
|
||||
{% for p in persons %}
|
||||
<a href="/persons/{{ p.id }}" class="person-card">
|
||||
<a href="/persons/{{ p.id }}{% if p.search_scope %}?search_scope={{ p.search_scope|urlencode }}{% endif %}" class="person-card">
|
||||
<div class="person-name">{{ p.first_name }} {{ p.father_name or '' }} {{ p.family_name or '' }}</div>
|
||||
<div class="person-meta">
|
||||
{% if p.family_origin %}{{ p.family_origin }} · {% endif %}
|
||||
{% if p.property_count > 0 %}
|
||||
{{ p.property_count }} عقار
|
||||
{% else %}
|
||||
<span style="color: #dc3545; font-weight: bold;">لا يملك عقارات</span>
|
||||
<span class="person-meta-alert">لا يملك عقارات</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if p.search_scopes %}
|
||||
<div class="person-reg">نطاق البحث: {{ p.search_scopes | replace(',', '، ') }}</div>
|
||||
{% endif %}
|
||||
<div class="person-reg">نطاق البحث: {{ p.search_scope or 'غير محدد' }}</div>
|
||||
{% if p.document_count %}<div class="person-reg">عدد الطلبات: {{ p.document_count }}</div>{% endif %}
|
||||
{% if p.registry_number %}
|
||||
<div class="person-reg">سجل: {{ p.registry_number }}</div>
|
||||
{% endif %}
|
||||
@@ -147,7 +171,7 @@
|
||||
<div class="results-section">
|
||||
<h2>نتائج البحث بالعقار ({{ properties|length }} سجل)</h2>
|
||||
<div class="table-wrapper">
|
||||
<table class="results-table">
|
||||
<table class="results-table responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>المالك</th>
|
||||
@@ -164,19 +188,19 @@
|
||||
<tbody>
|
||||
{% for pr in properties %}
|
||||
<tr>
|
||||
<td>
|
||||
<td data-label="المالك">
|
||||
{% if pr.person_id %}
|
||||
<a href="/persons/{{ pr.person_id }}">{{ pr.first_name or '' }} {{ pr.family_name or '' }}</a>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td>{{ pr.party_name or '' }}</td>
|
||||
<td class="prop-num">{{ pr.property_number or '' }}</td>
|
||||
<td>{{ pr.section or '' }}</td>
|
||||
<td>{{ pr.block or '' }}</td>
|
||||
<td>{{ pr.real_estate_district or '' }}</td>
|
||||
<td>{{ pr.qaza or '' }}</td>
|
||||
<td>{{ pr.num_shares or '' }}</td>
|
||||
<td>{{ pr.ownership_type or '' }}</td>
|
||||
<td data-label="اسم الفريق">{{ pr.party_name or '' }}</td>
|
||||
<td data-label="رقم العقار" class="prop-num">{{ pr.property_number or '' }}</td>
|
||||
<td data-label="القسم">{{ pr.section or '' }}</td>
|
||||
<td data-label="البلوك">{{ pr.block or '' }}</td>
|
||||
<td data-label="المنطقة العقارية">{{ pr.real_estate_district or '' }}</td>
|
||||
<td data-label="القضاء">{{ pr.qaza or '' }}</td>
|
||||
<td data-label="عدد الأسهم">{{ pr.num_shares or '' }}</td>
|
||||
<td data-label="نوع الملكية">{{ pr.ownership_type or '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -196,35 +220,29 @@
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function showTab(tab) {
|
||||
function showTab(tab, button = null) {
|
||||
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
|
||||
document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active'));
|
||||
document.getElementById('tab-' + tab).classList.remove('hidden');
|
||||
event.target.classList.add('active');
|
||||
if (button) {
|
||||
button.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// Export dropdown toggle
|
||||
document.addEventListener('click', function(e) {
|
||||
document.querySelectorAll('.export-menu').forEach(function(menu) {
|
||||
if (!menu.parentElement.contains(e.target)) {
|
||||
menu.classList.remove('show');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// CSS for show class
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '.export-menu.show { display: block !important; } .export-option:hover { background-color: #f0f0f0; }';
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Auto-select correct tab based on query params
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.tab-btn').forEach(button => {
|
||||
button.addEventListener('click', () => showTab(button.dataset.tab, button));
|
||||
});
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const personButton = document.querySelector('.tab-btn[data-tab="person"]');
|
||||
const propertyButton = document.querySelector('.tab-btn[data-tab="property"]');
|
||||
|
||||
if (params.get('property_number') || params.get('district') || params.get('block')) {
|
||||
showTab('property');
|
||||
document.querySelectorAll('.tab-btn')[1].classList.add('active');
|
||||
document.querySelectorAll('.tab-btn')[0].classList.remove('active');
|
||||
showTab('property', propertyButton);
|
||||
} else {
|
||||
showTab('person', personButton);
|
||||
document.getElementById('qInput').focus();
|
||||
}
|
||||
});
|
||||
|
||||
+23
-10
@@ -5,7 +5,11 @@
|
||||
{% block content %}
|
||||
<div class="admin-container">
|
||||
<div class="page-header">
|
||||
<h1>إدارة النظام والمستخدمين</h1>
|
||||
<div>
|
||||
<span class="page-kicker">الإعدادات والإدارة</span>
|
||||
<h1>إدارة النظام والمستخدمين</h1>
|
||||
<p class="subtitle">أنشئ نسخاً احتياطية وأدر حسابات الدخول من مكان واحد.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if backup_msg %}
|
||||
@@ -13,13 +17,19 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="admin-section">
|
||||
<h3>💾 نسخ احتياطي لقاعدة البيانات</h3>
|
||||
<h3>
|
||||
<svg class="section-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||||
نسخ احتياطي لقاعدة البيانات
|
||||
</h3>
|
||||
<p>قم بإنشاء نسخة احتياطية من قاعدة البيانات الحالية.</p>
|
||||
<a href="/auth/backup" class="btn btn-success">إنشاء نسخة احتياطية</a>
|
||||
</div>
|
||||
|
||||
<div class="admin-section">
|
||||
<h3>➕ إضافة مستخدم جديد</h3>
|
||||
<h3>
|
||||
<svg class="section-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>
|
||||
إضافة مستخدم جديد
|
||||
</h3>
|
||||
<form method="post" action="/auth/users/create" class="add-user-form">
|
||||
<div class="form-group">
|
||||
<label for="username">اسم المستخدم</label>
|
||||
@@ -34,9 +44,12 @@
|
||||
</div>
|
||||
|
||||
<div class="admin-section">
|
||||
<h3>👥 المستخدمين</h3>
|
||||
<h3>
|
||||
<svg class="section-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||
المستخدمين
|
||||
</h3>
|
||||
<div class="table-wrapper">
|
||||
<table class="results-table">
|
||||
<table class="results-table responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>الرقم</th>
|
||||
@@ -48,11 +61,11 @@
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.created_at }}</td>
|
||||
<td>
|
||||
<form method="post" action="/auth/users/delete/{{ user.id }}" onsubmit="return confirm('هل أنت متأكد من حذف هذا المستخدم؟');" style="display:inline;">
|
||||
<td data-label="الرقم">{{ user.id }}</td>
|
||||
<td data-label="اسم المستخدم">{{ user.username }}</td>
|
||||
<td data-label="تاريخ الإنشاء">{{ user.created_at }}</td>
|
||||
<td data-label="الإجراءات">
|
||||
<form method="post" action="/auth/users/delete/{{ user.id }}" onsubmit="return confirm('هل أنت متأكد من حذف هذا المستخدم؟');" class="inline-form">
|
||||
<button type="submit" class="btn btn-sm btn-danger">حذف</button>
|
||||
</form>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user