Initial commit: Real Estate Registry app
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}سجل العقارات اللبناني{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block navbar %}
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<a class="nav-brand" href="/">🏠 سجل العقارات</a>
|
||||
<div class="nav-links">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
<main class="main-content">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,105 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}قائمة الوثائق — سجل العقارات{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>قائمة الوثائق</h1>
|
||||
<a href="/" class="btn btn-primary">+ رفع وثائق جديدة</a>
|
||||
</div>
|
||||
|
||||
{% if uploaded %}
|
||||
<div class="success-banner">تم رفع الملفات بنجاح وبدأ الاستخراج. قد يستغرق بعض الوقت.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="stats-bar">
|
||||
<a href="/documents" class="stat {{ 'active' if not current_status }}">
|
||||
<span class="stat-num">{{ stats.total or 0 }}</span><span class="stat-label">الكل</span>
|
||||
</a>
|
||||
<a href="/documents?status=extracted" class="stat pending {{ 'active' if current_status=='extracted' }}">
|
||||
<span class="stat-num">{{ stats.pending_review or 0 }}</span><span class="stat-label">بانتظار المراجعة</span>
|
||||
</a>
|
||||
<a href="/documents?status=confirmed" class="stat confirmed {{ 'active' if current_status=='confirmed' }}">
|
||||
<span class="stat-num">{{ stats.confirmed or 0 }}</span><span class="stat-label">مؤكّدة</span>
|
||||
</a>
|
||||
<a href="/documents?status=pending" class="stat {{ 'active' if current_status=='pending' }}">
|
||||
<span class="stat-num">{{ stats.processing or 0 }}</span><span class="stat-label">جارٍ المعالجة</span>
|
||||
</a>
|
||||
{% if stats.errors %}
|
||||
<a href="/documents?status=error" class="stat error {{ 'active' if current_status=='error' }}">
|
||||
<span class="stat-num">{{ stats.errors }}</span><span class="stat-label">أخطاء</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if stats.pending_review %}
|
||||
<div class="alert-banner">
|
||||
<a href="/review/next">▶ ابدأ مراجعة {{ stats.pending_review }} وثيقة</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>الصورة</th>
|
||||
<th>الشخص</th>
|
||||
<th>رقم الطلب</th>
|
||||
<th>الحالة</th>
|
||||
<th>التاريخ</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in documents %}
|
||||
<tr>
|
||||
<td>{{ d.id }}</td>
|
||||
<td>
|
||||
<a href="/review/{{ d.id }}">
|
||||
<img src="/uploads/{{ d.image_path }}" class="doc-thumb-mini" alt="">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% 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">
|
||||
<a href="/review/{{ d.id }}" class="btn btn-sm btn-secondary">
|
||||
{% if d.status == 'confirmed' %}عرض{% else %}مراجعة{% endif %}
|
||||
</a>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteDoc({{ d.id }}, this)">حذف</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if not documents %}
|
||||
<div class="no-results">
|
||||
<p>لا توجد وثائق. <a href="/">ارفع وثائق جديدة</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
async function deleteDoc(docId, btn) {
|
||||
if (!confirm('حذف هذه الوثيقة نهائياً؟ لا يمكن التراجع.')) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '...';
|
||||
const res = await fetch(`/documents/${docId}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
btn.closest('tr').remove();
|
||||
} else {
|
||||
alert('حدث خطأ أثناء الحذف.');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'حذف';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,119 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}رفع وثائق — سجل العقارات{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>بطاقات معلومات الملكية العقارية</h1>
|
||||
<p class="subtitle">ارفع صور أو ملفات PDF للوثائق — اختر محرك الاستخراج (مجاني أو ذكاء اصطناعي)</p>
|
||||
</div>
|
||||
|
||||
{% if stats and stats.total %}
|
||||
<div class="stats-bar">
|
||||
<div class="stat"><span class="stat-num">{{ stats.total }}</span><span class="stat-label">إجمالي الوثائق</span></div>
|
||||
<div class="stat confirmed"><span class="stat-num">{{ stats.confirmed or 0 }}</span><span class="stat-label">مؤكّدة</span></div>
|
||||
<div class="stat pending"><span class="stat-num">{{ stats.pending_review or 0 }}</span><span class="stat-label">بانتظار المراجعة</span></div>
|
||||
{% if stats.errors %}<div class="stat error"><span class="stat-num">{{ stats.errors }}</span><span class="stat-label">أخطاء</span></div>{% endif %}
|
||||
</div>
|
||||
{% if stats.pending_review %}
|
||||
<div class="alert-banner">
|
||||
<a href="/review/next">▶ ابدأ مراجعة {{ stats.pending_review }} وثيقة</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="upload-card">
|
||||
<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="drop-zone" id="dropZone">
|
||||
<div class="drop-icon">📄</div>
|
||||
<p class="drop-text">اسحب الملفات هنا أو انقر للاختيار</p>
|
||||
<p class="drop-hint">JPG، PNG، PDF — يمكن رفع عدة ملفات دفعة واحدة</p>
|
||||
<input type="file" name="files" id="fileInput" multiple accept=".jpg,.jpeg,.png,.webp,.pdf" class="file-input">
|
||||
</div>
|
||||
|
||||
<div id="fileList" class="file-list hidden"></div>
|
||||
|
||||
<!-- Provider selector -->
|
||||
<div id="providerSection" class="provider-section hidden">
|
||||
<label class="provider-label">محرك الاستخراج:</label>
|
||||
<div class="provider-options">
|
||||
{% for p in providers %}
|
||||
<label class="provider-option">
|
||||
<input type="radio" name="provider" value="{{ p.id }}"
|
||||
{{ 'checked' if p.id == default_provider else '' }}>
|
||||
<span class="provider-card {{ 'provider-free' if p.id == 'easyocr' else 'provider-ai' }}">
|
||||
<span class="provider-name">{{ p.name }}</span>
|
||||
<span class="provider-model">{{ p.model }}</span>
|
||||
</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="uploadActions" class="upload-actions hidden">
|
||||
<button type="submit" class="btn btn-primary btn-large">
|
||||
⬆ رفع ومعالجة
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="clearFiles()">إلغاء</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="quick-links">
|
||||
<a href="/search" class="quick-link-card">
|
||||
<span class="ql-icon">🔍</span>
|
||||
<span>البحث في قاعدة البيانات</span>
|
||||
</a>
|
||||
<a href="/documents" class="quick-link-card">
|
||||
<span class="ql-icon">📋</span>
|
||||
<span>قائمة جميع الوثائق</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const dropZone = document.getElementById('dropZone');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const fileList = document.getElementById('fileList');
|
||||
const uploadActions = document.getElementById('uploadActions');
|
||||
|
||||
dropZone.addEventListener('click', () => fileInput.click());
|
||||
|
||||
dropZone.addEventListener('dragover', e => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('drag-over');
|
||||
});
|
||||
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
|
||||
dropZone.addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('drag-over');
|
||||
fileInput.files = e.dataTransfer.files;
|
||||
showFiles(fileInput.files);
|
||||
});
|
||||
|
||||
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>`
|
||||
).join('');
|
||||
fileList.classList.remove('hidden');
|
||||
document.getElementById('providerSection').classList.remove('hidden');
|
||||
uploadActions.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function clearFiles() {
|
||||
fileInput.value = '';
|
||||
fileList.innerHTML = '';
|
||||
fileList.classList.add('hidden');
|
||||
document.getElementById('providerSection').classList.add('hidden');
|
||||
uploadActions.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('uploadForm').addEventListener('submit', function() {
|
||||
document.querySelector('.btn-primary').textContent = '⏳ جارٍ الرفع...';
|
||||
document.querySelector('.btn-primary').disabled = true;
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}تسجيل الدخول - سجل العقارات اللبناني{% endblock %}
|
||||
|
||||
{% block navbar %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="login-container" style="max-width: 400px; margin: 50px auto; padding: 20px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
<h2>تسجيل الدخول</h2>
|
||||
|
||||
{% if error %}
|
||||
<div style="color: red; margin-bottom: 10px;">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/auth/login">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="username" style="display: block; margin-bottom: 5px;">اسم المستخدم:</label>
|
||||
<input type="text" id="username" name="username" required style="width: 100%; padding: 8px;">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="password" style="display: block; margin-bottom: 5px;">كلمة المرور:</label>
|
||||
<input type="password" id="password" name="password" required style="width: 100%; padding: 8px;">
|
||||
</div>
|
||||
|
||||
<button type="submit" style="width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
دخول
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,119 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ person.first_name }} {{ person.family_name or '' }} — سجل العقارات{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<a href="/search" class="back-link">← العودة للبحث</a>
|
||||
<h1>{{ person.first_name }} {{ person.father_name or '' }} {{ person.family_name or '' }}</h1>
|
||||
</div>
|
||||
<div>
|
||||
{% set scopes = documents | selectattr('search_scope') | map(attribute='search_scope') | unique | list %}
|
||||
<div class="export-dropdown" style="position: relative; display: inline-block;">
|
||||
<button class="btn btn-primary" onclick="this.nextElementSibling.classList.toggle('show')">📥 تصدير كملف إكسل ▾</button>
|
||||
<div class="export-menu" style="display:none; position:absolute; left:0; top:100%; background:#fff; border:1px solid #ddd; border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,.15); z-index:100; min-width:220px; margin-top:4px;">
|
||||
<a href="/persons/{{ person.id }}/export" class="export-option" download style="display:block; padding:10px 16px; text-decoration:none; color:#333; border-bottom:1px solid #eee;">📥 تصدير الكل</a>
|
||||
{% for scope in scopes %}
|
||||
{% if scope %}
|
||||
<a href="/persons/{{ person.id }}/export?qaza={{ scope|urlencode }}" class="export-option" download style="display:block; padding:10px 16px; text-decoration:none; color:#333;">📥 {{ scope }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="person-info-card">
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><span class="info-label">الاسم الكامل</span><span>{{ person.first_name }} {{ person.father_name or '' }} {{ person.family_name or '' }}</span></div>
|
||||
{% if person.mother_name %}<div class="info-item"><span class="info-label">اسم الأم</span><span>{{ person.mother_name }}</span></div>{% endif %}
|
||||
{% if person.family_origin %}<div class="info-item"><span class="info-label">الهوا</span><span>{{ person.family_origin }}</span></div>{% endif %}
|
||||
{% if person.nationality %}<div class="info-item"><span class="info-label">الجنسية</span><span>{{ person.nationality }}</span></div>{% endif %}
|
||||
{% if person.birth_date %}<div class="info-item"><span class="info-label">تاريخ الولادة</span><span>{{ person.birth_date }}</span></div>{% endif %}
|
||||
{% if person.registry_number %}<div class="info-item"><span class="info-label">رقم السجل</span><span>{{ person.registry_number }}</span></div>{% endif %}
|
||||
{% if person.registry_place %}<div class="info-item"><span class="info-label">مكان السجل</span><span>{{ person.registry_place }}</span></div>{% endif %}
|
||||
{% 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>
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
<h2>العقارات المملوكة ({{ properties|length }})</h2>
|
||||
</div>
|
||||
|
||||
{% if properties %}
|
||||
<div class="table-wrapper">
|
||||
<table class="results-table props-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>اسم الفريق</th>
|
||||
<th>رقم العقار</th>
|
||||
<th>القسم</th>
|
||||
<th>البلوك</th>
|
||||
<th>المنطقة العقارية</th>
|
||||
<th>القضاء</th>
|
||||
<th>عدد الأسهم</th>
|
||||
<th>نوع الملكية</th>
|
||||
</tr>
|
||||
</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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="margin: 1rem 0; padding: 15px; background-color: #fff3f3; border: 1px solid #ffcaca; border-radius: 8px; color: #dc3545; font-weight: bold; text-align: center;">
|
||||
⚠️ نتيجة البحث: لا يملك أي عقار في نطاق البحث المذكور.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if documents %}
|
||||
<div class="docs-section">
|
||||
<h3>الوثائق الأصلية</h3>
|
||||
<div class="doc-thumbnails">
|
||||
{% for d in documents %}
|
||||
<a href="/review/{{ d.id }}" class="doc-thumb">
|
||||
<img src="/uploads/{{ d.image_path }}" alt="وثيقة">
|
||||
<div class="doc-thumb-info">
|
||||
{% if d.request_number %}طلب {{ d.request_number }}{% endif %}
|
||||
{% if d.request_date %}<br>{{ d.request_date }}{% endif %}
|
||||
{% if d.search_scope %}<br>القضاء: {{ d.search_scope }}{% endif %}
|
||||
{% if d.page_info %}<br><span class="doc-page-info">{{ d.page_info }}</span>{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% 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 %}
|
||||
@@ -0,0 +1,358 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}مراجعة الوثيقة #{{ doc.id }}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="/static/css/review.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="review-header">
|
||||
<div class="review-title">
|
||||
<h2>مراجعة الوثيقة #{{ doc.id }}</h2>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if doc.status == 'error' %}
|
||||
<div class="error-banner">
|
||||
⚠ خطأ في الاستخراج: {{ doc.extraction_error }}
|
||||
<button class="btn btn-sm btn-warning" onclick="retriggerExtraction({{ doc.id }})">إعادة المحاولة</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="review-layout">
|
||||
<!-- 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>
|
||||
</div>
|
||||
<div class="image-container" id="imageContainer">
|
||||
<img id="docImage" src="/uploads/{{ doc.image_path }}" alt="وثيقة" class="doc-image">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Panel -->
|
||||
<div class="form-panel">
|
||||
<form id="reviewForm">
|
||||
|
||||
<!-- Person Info -->
|
||||
<section class="form-section">
|
||||
<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>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>اسم الأب</label>
|
||||
<input type="text" name="father_name" value="{{ doc.person.get('father_name') or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>اسم الأم</label>
|
||||
<input type="text" name="mother_name" value="{{ doc.person.get('mother_name') or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>اللقب</label>
|
||||
<input type="text" name="family_name" value="{{ doc.person.get('family_name') or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>الهوا / المنشأ</label>
|
||||
<input type="text" name="family_origin" value="{{ doc.person.get('family_origin') or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>الجنسية</label>
|
||||
<input type="text" name="nationality" value="{{ doc.person.get('nationality') or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>تاريخ الولادة</label>
|
||||
<input type="text" name="birth_date" value="{{ doc.person.get('birth_date') or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>رقم السجل</label>
|
||||
<input type="text" name="registry_number" value="{{ doc.person.get('registry_number') or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>مكان السجل</label>
|
||||
<input type="text" name="registry_place" value="{{ doc.person.get('registry_place') or '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Merge suggestion (populated by JS) -->
|
||||
<div id="mergeSuggestion" class="merge-banner hidden">
|
||||
<h4>شخص مشابه موجود في قاعدة البيانات:</h4>
|
||||
<div id="mergeMatches"></div>
|
||||
<div class="merge-actions">
|
||||
<label><input type="radio" name="merge_action" value="merge" checked> دمج مع الشخص الموجود</label>
|
||||
<label><input type="radio" name="merge_action" value="new"> إنشاء شخص جديد</label>
|
||||
</div>
|
||||
<input type="hidden" id="mergePersonId" value="">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Document Info -->
|
||||
<section class="form-section">
|
||||
<h3>بيانات الطلب</h3>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>رقم الطلب</label>
|
||||
<input type="text" name="request_number" value="{{ doc.request_number or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>تاريخ الطلب</label>
|
||||
<input type="text" name="request_date" value="{{ doc.request_date or '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>معلومات الصفحة</label>
|
||||
<input type="text" name="page_info" value="{{ doc.page_info or '' }}" placeholder="مثال: 1 من 3">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>نطاق البحث (القضاء)</label>
|
||||
<input type="text" name="search_scope" value="{{ doc.search_scope or '' }}" placeholder="مثال: المتن">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Properties Table -->
|
||||
<section class="form-section">
|
||||
<div class="section-header">
|
||||
<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">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>اسم الفريق</th>
|
||||
<th>رقم العقار</th>
|
||||
<th>القسم</th>
|
||||
<th>البلوك</th>
|
||||
<th>المنطقة العقارية</th>
|
||||
<th>القضاء</th>
|
||||
<th>عدد الأسهم</th>
|
||||
<th>نوع الملكية</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-primary btn-large" onclick="confirmDocument()" id="confirmBtn">
|
||||
✓ تأكيد والانتقال للتالية
|
||||
</button>
|
||||
<span class="keyboard-hint">أو اضغط Ctrl+Enter</span>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/static/js/review.js"></script>
|
||||
<script>
|
||||
const DOC_ID = {{ doc.id }};
|
||||
|
||||
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>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
tr.querySelector('input').focus();
|
||||
}
|
||||
|
||||
function removeRow(btn) {
|
||||
btn.closest('tr').remove();
|
||||
}
|
||||
|
||||
function getFormData() {
|
||||
const form = document.getElementById('reviewForm');
|
||||
const person = {
|
||||
first_name: form.querySelector('[name=first_name]').value,
|
||||
father_name: form.querySelector('[name=father_name]').value,
|
||||
mother_name: form.querySelector('[name=mother_name]').value,
|
||||
family_name: form.querySelector('[name=family_name]').value,
|
||||
family_origin: form.querySelector('[name=family_origin]').value,
|
||||
nationality: form.querySelector('[name=nationality]').value,
|
||||
birth_date: form.querySelector('[name=birth_date]').value,
|
||||
registry_number: form.querySelector('[name=registry_number]').value,
|
||||
registry_place: form.querySelector('[name=registry_place]').value,
|
||||
};
|
||||
const rows = document.querySelectorAll('#propertiesTbody tr');
|
||||
const properties = Array.from(rows).map(row => {
|
||||
const inputs = row.querySelectorAll('input');
|
||||
return {
|
||||
party_name: inputs[0].value,
|
||||
property_number: inputs[1].value,
|
||||
section: inputs[2].value,
|
||||
block: inputs[3].value,
|
||||
real_estate_district: inputs[4].value,
|
||||
qaza: inputs[5].value,
|
||||
num_shares: inputs[6].value,
|
||||
ownership_type: inputs[7].value,
|
||||
};
|
||||
});
|
||||
|
||||
const reqNum = form.querySelector('[name=request_number]').value;
|
||||
const reqDate = form.querySelector('[name=request_date]').value;
|
||||
const pageInfo = form.querySelector('[name=page_info]').value;
|
||||
const searchScope = form.querySelector('[name=search_scope]').value;
|
||||
|
||||
return {
|
||||
person,
|
||||
properties,
|
||||
request_number: reqNum,
|
||||
request_date: reqDate,
|
||||
page_info: pageInfo,
|
||||
search_scope: searchScope
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Duplicate person check ────────────────────────────
|
||||
let _dupTimer = null;
|
||||
|
||||
function checkDuplicate() {
|
||||
clearTimeout(_dupTimer);
|
||||
_dupTimer = setTimeout(async () => {
|
||||
const form = document.getElementById('reviewForm');
|
||||
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 banner = document.getElementById('mergeSuggestion');
|
||||
const matchesDiv = document.getElementById('mergeMatches');
|
||||
const mergeIdInput = document.getElementById('mergePersonId');
|
||||
|
||||
if (data.matches && data.matches.length > 0) {
|
||||
matchesDiv.innerHTML = data.matches.map(m =>
|
||||
`<div class="merge-person" data-person-id="${m.id}">
|
||||
<span class="mp-name">${m.first_name} ${m.father_name||''} ${m.family_name||''}</span>
|
||||
<span class="mp-count">${m.property_count} عقار</span>
|
||||
${m.family_origin ? '<span class="mp-count">'+m.family_origin+'</span>' : ''}
|
||||
</div>`
|
||||
).join('');
|
||||
mergeIdInput.value = data.matches[0].id;
|
||||
banner.classList.remove('hidden');
|
||||
|
||||
// Click to select a match
|
||||
matchesDiv.querySelectorAll('.merge-person').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
matchesDiv.querySelectorAll('.merge-person').forEach(e => e.style.outline = '');
|
||||
el.style.outline = '2px solid var(--primary)';
|
||||
mergeIdInput.value = el.dataset.personId;
|
||||
form.querySelector('[name=merge_action][value=merge]').checked = true;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
banner.classList.add('hidden');
|
||||
mergeIdInput.value = '';
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Attach duplicate check to name fields
|
||||
document.querySelectorAll('[name=first_name],[name=father_name],[name=family_name]').forEach(input => {
|
||||
input.addEventListener('blur', checkDuplicate);
|
||||
});
|
||||
// Run once on load
|
||||
checkDuplicate();
|
||||
|
||||
// ─── Confirm ───────────────────────────────────────────
|
||||
async function confirmDocument() {
|
||||
const btn = document.getElementById('confirmBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⏳ جارٍ الحفظ...';
|
||||
|
||||
try {
|
||||
const formData = getFormData();
|
||||
|
||||
// Check if user chose to merge
|
||||
const mergeAction = document.querySelector('[name=merge_action]:checked');
|
||||
const mergeId = document.getElementById('mergePersonId').value;
|
||||
if (mergeAction && mergeAction.value === 'merge' && mergeId) {
|
||||
formData.merge_person_id = parseInt(mergeId);
|
||||
}
|
||||
|
||||
const res = await fetch(`/confirm/${DOC_ID}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.ok) {
|
||||
window.location.href = data.next;
|
||||
} else {
|
||||
alert('حدث خطأ: ' + JSON.stringify(data));
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✓ تأكيد والانتقال للتالية';
|
||||
}
|
||||
} catch (e) {
|
||||
alert('خطأ في الشبكة: ' + e);
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✓ تأكيد والانتقال للتالية';
|
||||
}
|
||||
}
|
||||
|
||||
async function retriggerExtraction(docId) {
|
||||
if (!confirm('إعادة استخراج البيانات بالذكاء الاصطناعي؟')) return;
|
||||
await fetch(`/extract/${docId}`, { method: 'POST' });
|
||||
location.reload();
|
||||
}
|
||||
|
||||
async function deleteDoc(docId) {
|
||||
if (!confirm('حذف هذه الوثيقة نهائياً؟ لا يمكن التراجع.')) return;
|
||||
const res = await fetch(`/documents/${docId}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
window.location.href = '/review/next';
|
||||
} else {
|
||||
alert('حدث خطأ أثناء الحذف.');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
confirmDocument();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,232 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}بحث — سجل العقارات{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>البحث في قاعدة البيانات</h1>
|
||||
</div>
|
||||
|
||||
<div class="search-card">
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-property" class="tab-content hidden">
|
||||
<div class="search-grid">
|
||||
<div class="form-group">
|
||||
<label>رقم العقار</label>
|
||||
<input type="text" name="property_number" value="{{ property_number }}" placeholder="مثال: 162">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>المنطقة العقارية</label>
|
||||
<input type="text" name="district" value="{{ district }}" placeholder="مثال: مراح الزيتات">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>البلوك</label>
|
||||
<input type="text" name="block" value="{{ block }}" placeholder="رقم البلوك">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">🔍 بحث</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div class="error-banner">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Person results -->
|
||||
{% if persons %}
|
||||
<div class="results-section">
|
||||
<h2>نتائج البحث بالاسم ({{ persons|length }} شخص)</h2>
|
||||
|
||||
{% if selected_person %}
|
||||
<!-- Single person: show full details inline -->
|
||||
<div class="person-info-card">
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><span class="info-label">الاسم الكامل</span><span>{{ selected_person.person.first_name }} {{ selected_person.person.father_name or '' }} {{ selected_person.person.family_name or '' }}</span></div>
|
||||
{% if selected_person.person.family_origin %}<div class="info-item"><span class="info-label">الهوا</span><span>{{ selected_person.person.family_origin }}</span></div>{% endif %}
|
||||
{% if selected_person.person.nationality %}<div class="info-item"><span class="info-label">الجنسية</span><span>{{ selected_person.person.nationality }}</span></div>{% endif %}
|
||||
{% if selected_person.person.registry_number %}<div class="info-item"><span class="info-label">رقم السجل</span><span>{{ selected_person.person.registry_number }}</span></div>{% endif %}
|
||||
{% 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>
|
||||
{% set scopes = selected_person.documents | selectattr('search_scope') | map(attribute='search_scope') | unique | list %}
|
||||
<div class="export-dropdown" style="position: relative; display: inline-block;">
|
||||
<button class="btn btn-primary" onclick="this.nextElementSibling.classList.toggle('show')">📥 تصدير كملف إكسل ▾</button>
|
||||
<div class="export-menu" style="display:none; position:absolute; left:0; top:100%; background:#fff; border:1px solid #ddd; border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,.15); z-index:100; min-width:220px; margin-top:4px;">
|
||||
<a href="/persons/{{ selected_person.person.id }}/export" class="export-option" download style="display:block; padding:10px 16px; text-decoration:none; color:#333; border-bottom:1px solid #eee;">📥 تصدير الكل</a>
|
||||
{% for scope in scopes %}
|
||||
{% if scope %}
|
||||
<a href="/persons/{{ selected_person.person.id }}/export?qaza={{ scope|urlencode }}" class="export-option" download style="display:block; padding:10px 16px; text-decoration:none; color:#333;">📥 {{ scope }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if selected_person.properties %}
|
||||
<div class="table-wrapper" style="margin-top:1rem">
|
||||
<table class="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>اسم الفريق</th>
|
||||
<th>رقم العقار</th>
|
||||
<th>القسم</th>
|
||||
<th>البلوك</th>
|
||||
<th>المنطقة العقارية</th>
|
||||
<th>القضاء</th>
|
||||
<th>عدد الأسهم</th>
|
||||
<th>نوع الملكية</th>
|
||||
</tr>
|
||||
</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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="margin-top: 1rem; padding: 15px; background-color: #fff3f3; border: 1px solid #ffcaca; border-radius: 8px; color: #dc3545; font-weight: bold; text-align: center;">
|
||||
⚠️ نتيجة البحث: لا يملك أي عقار في القضاء المذكور.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="persons-grid">
|
||||
{% for p in persons %}
|
||||
<a href="/persons/{{ p.id }}" 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if p.search_scopes %}
|
||||
<div class="person-reg">نطاق البحث: {{ p.search_scopes | replace(',', '، ') }}</div>
|
||||
{% endif %}
|
||||
{% if p.registry_number %}
|
||||
<div class="person-reg">سجل: {{ p.registry_number }}</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Property results -->
|
||||
{% if properties %}
|
||||
<div class="results-section">
|
||||
<h2>نتائج البحث بالعقار ({{ properties|length }} سجل)</h2>
|
||||
<div class="table-wrapper">
|
||||
<table class="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>المالك</th>
|
||||
<th>اسم الفريق</th>
|
||||
<th>رقم العقار</th>
|
||||
<th>القسم</th>
|
||||
<th>البلوك</th>
|
||||
<th>المنطقة العقارية</th>
|
||||
<th>القضاء</th>
|
||||
<th>عدد الأسهم</th>
|
||||
<th>نوع الملكية</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pr in properties %}
|
||||
<tr>
|
||||
<td>
|
||||
{% 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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if (q or property_number or district or block) and not persons and not properties %}
|
||||
<div class="no-results">
|
||||
<p>لا توجد نتائج للبحث.</p>
|
||||
<p><a href="/search">بحث جديد</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function showTab(tab) {
|
||||
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');
|
||||
}
|
||||
|
||||
// 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', () => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
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');
|
||||
} else {
|
||||
document.getElementById('qInput').focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,64 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}إدارة المستخدمين - سجل العقارات اللبناني{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="users-container" style="max-width: 800px; margin: 20px auto; padding: 20px;">
|
||||
<h2>إدارة النظام والمستخدمين</h2>
|
||||
|
||||
{% if backup_msg %}
|
||||
<div style="padding: 10px; background-color: #d4edda; color: #155724; border-radius: 4px; margin-bottom: 15px;">
|
||||
{{ backup_msg }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="margin-bottom: 30px; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
|
||||
<h3>نسخ احتياطي لقاعدة البيانات</h3>
|
||||
<p>قم بإنشاء نسخة احتياطية من قاعدة البيانات الحالية.</p>
|
||||
<a href="/auth/backup" style="display: inline-block; padding: 8px 15px; background-color: #28a745; color: white; text-decoration: none; border-radius: 4px;">إنشاء نسخة احتياطية</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
|
||||
<h3>إضافة مستخدم جديد</h3>
|
||||
<form method="post" action="/auth/users/create" style="display: flex; gap: 10px; align-items: flex-end;">
|
||||
<div>
|
||||
<label for="username" style="display: block; margin-bottom: 5px;">اسم المستخدم</label>
|
||||
<input type="text" id="username" name="username" required style="padding: 8px;">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" style="display: block; margin-bottom: 5px;">كلمة المرور</label>
|
||||
<input type="password" id="password" name="password" required style="padding: 8px;">
|
||||
</div>
|
||||
<button type="submit" style="padding: 9px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">إضافة</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div style="padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
|
||||
<h3>المستخدمين</h3>
|
||||
<table style="width: 100%; border-collapse: collapse; margin-top: 10px;">
|
||||
<thead>
|
||||
<tr style="background-color: #f8f9fa;">
|
||||
<th style="padding: 10px; border-bottom: 2px solid #dee2e6; text-align: right;">الرقم</th>
|
||||
<th style="padding: 10px; border-bottom: 2px solid #dee2e6; text-align: right;">اسم المستخدم</th>
|
||||
<th style="padding: 10px; border-bottom: 2px solid #dee2e6; text-align: right;">تاريخ الإنشاء</th>
|
||||
<th style="padding: 10px; border-bottom: 2px solid #dee2e6; text-align: right;">إجراءات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #dee2e6;">{{ user.id }}</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #dee2e6;">{{ user.username }}</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #dee2e6;">{{ user.created_at }}</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #dee2e6;">
|
||||
<form method="post" action="/auth/users/delete/{{ user.id }}" onsubmit="return confirm('هل أنت متأكد من حذف هذا المستخدم؟');" style="display: inline;">
|
||||
<button type="submit" style="padding: 5px 10px; background-color: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer;">حذف</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user