539 lines
25 KiB
HTML
539 lines
25 KiB
HTML
{% 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">
|
|
<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 }})">
|
|
<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>
|
|
|
|
{% if doc.status in ['extracted', 'confirmed', 'error'] %}
|
|
<div class="info-banner rescan-banner">
|
|
<div>
|
|
<strong>إعادة المسح</strong>
|
|
<span class="rescan-copy">إذا خلط الذكاء الاصطناعي بين أحرف مثل س و ن، يمكنك إعادة القراءة بنفس المحرك أو بمحرك آخر.</span>
|
|
</div>
|
|
<div class="rescan-controls">
|
|
<label for="providerSelect" class="sr-only">محرك الاستخراج</label>
|
|
<select id="providerSelect" title="محرك الاستخراج">
|
|
{% for provider in providers %}
|
|
<option value="{{ provider.id }}" {{ 'selected' if provider.id == current_provider else '' }}>
|
|
{{ provider.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="button" class="btn btn-warning btn-sm" id="rescanBtn" onclick="retriggerExtraction({{ doc.id }})">
|
|
إعادة المسح
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% 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 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">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Panel -->
|
|
<div class="form-panel">
|
|
<form id="reviewForm">
|
|
|
|
{% if doc.get('is_subsequent_page') %}
|
|
<div class="info-banner page-group-banner">
|
|
<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="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
|
|
<div class="page-group-copy">
|
|
<div>
|
|
هذه صفحة تكميلية ({{ doc.get('page_info') or doc.get('page_number') or '' }}) من مستند متعدد الصفحات.
|
|
{% if doc.get('inherited_from_page1') %}
|
|
بيانات الشخص مستوردة تلقائياً من الصفحة الأولى.
|
|
{% else %}
|
|
يرجى التأكد من بيانات الشخص يدوياً — لم يتم العثور على صفحة أولى مؤكدة بعد.
|
|
{% endif %}
|
|
</div>
|
|
{% if doc.get('page1_doc_id') and ai_verification_available %}
|
|
<div class="page-group-actions">
|
|
<button type="button" class="btn btn-secondary btn-sm" id="verifyCorrelationBtn" onclick="verifyCorrelation({{ doc.id }})">
|
|
تحقق بالذكاء الاصطناعي
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div id="correlationResult" class="hidden"></div>
|
|
{% endif %}
|
|
|
|
<!-- Person Info -->
|
|
<section class="form-section">
|
|
<h3>بيانات الشخص</h3>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<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 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 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 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 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 for="nationality">الجنسية</label>
|
|
<input id="nationality" type="text" name="nationality" value="{{ doc.person.get('nationality') or '' }}" title="الجنسية">
|
|
</div>
|
|
<div class="form-group">
|
|
<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 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 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>
|
|
<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 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 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 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 for="search_scope">نطاق البحث (القضاء)</label>
|
|
<input id="search_scope" type="text" name="search_scope" value="{{ doc.search_scope or '' }}" title="نطاق البحث" 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-wrapper-editable">
|
|
<table id="propertiesTable" class="props-table mobile-edit-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 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>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 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>
|
|
|
|
</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 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();
|
|
}
|
|
|
|
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();
|
|
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) {
|
|
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>' : ''}
|
|
<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
|
|
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)';
|
|
if (el.dataset.mergeDisabled === 'true') {
|
|
mergeIdInput.value = '';
|
|
newRadio.checked = true;
|
|
mergeRadio.disabled = true;
|
|
return;
|
|
}
|
|
mergeIdInput.value = el.dataset.personId;
|
|
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],[name=search_scope],[name=registry_number]').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('حدث خطأ: ' + (data.detail || JSON.stringify(data)));
|
|
btn.disabled = false;
|
|
btn.textContent = '✓ تأكيد والانتقال للتالية';
|
|
}
|
|
} catch (e) {
|
|
alert('خطأ في الشبكة: ' + e);
|
|
btn.disabled = false;
|
|
btn.textContent = '✓ تأكيد والانتقال للتالية';
|
|
}
|
|
}
|
|
|
|
async function retriggerExtraction(docId) {
|
|
const provider = document.getElementById('providerSelect')?.value || '';
|
|
const btn = document.getElementById('rescanBtn');
|
|
const providerLabel = document.getElementById('providerSelect')?.selectedOptions?.[0]?.text || 'المحرك الحالي';
|
|
if (!confirm(`إعادة استخراج البيانات باستخدام ${providerLabel}؟ سيتم استبدال النتائج الحالية.`)) return;
|
|
|
|
if (btn) {
|
|
btn.disabled = true;
|
|
btn.textContent = 'جارٍ إعادة المسح...';
|
|
}
|
|
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (provider) params.set('provider', provider);
|
|
const res = await fetch(`/extract/${docId}?${params.toString()}`, { method: 'POST' });
|
|
const data = await res.json();
|
|
if (!res.ok || !data.ok) {
|
|
throw new Error(data.error || data.detail || 'تعذر بدء إعادة المسح.');
|
|
}
|
|
window.location.href = `/review/${docId}?wait=1`;
|
|
} catch (e) {
|
|
alert('حدث خطأ أثناء إعادة المسح: ' + e.message);
|
|
if (btn) {
|
|
btn.disabled = false;
|
|
btn.textContent = 'إعادة المسح';
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyCorrelation(docId) {
|
|
const btn = document.getElementById('verifyCorrelationBtn');
|
|
const resultEl = document.getElementById('correlationResult');
|
|
if (!btn || !resultEl) return;
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'جارٍ التحقق...';
|
|
resultEl.className = 'info-banner correlation-result';
|
|
resultEl.innerHTML = 'جارٍ فحص الترابط بين الصفحة الحالية والصفحة الأولى...';
|
|
|
|
try {
|
|
const res = await fetch(`/review/${docId}/verify-correlation`, { method: 'POST' });
|
|
const data = await res.json();
|
|
if (!res.ok || !data.ok) {
|
|
throw new Error(data.error || 'تعذر التحقق من الترابط.');
|
|
}
|
|
|
|
const statusClass = data.same_document ? 'success-banner' : 'error-banner';
|
|
const reasons = (data.reasons_ar || []).map(item => `<li>${item}</li>`).join('');
|
|
const mismatches = (data.mismatch_flags || []).map(item => `<li>${item}</li>`).join('');
|
|
resultEl.className = `${statusClass} correlation-result`;
|
|
resultEl.innerHTML = `
|
|
<div class="correlation-heading">
|
|
<strong>${data.verdict_ar}</strong>
|
|
<span class="correlation-meta">الثقة: ${data.confidence} | المحرك: ${data.provider}</span>
|
|
</div>
|
|
${reasons ? `<ul class="correlation-list">${reasons}</ul>` : ''}
|
|
${mismatches ? `<ul class="correlation-list correlation-warnings">${mismatches}</ul>` : ''}
|
|
`;
|
|
} catch (e) {
|
|
resultEl.className = 'error-banner correlation-result';
|
|
resultEl.textContent = 'فشل التحقق: ' + e.message;
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'تحقق بالذكاء الاصطناعي';
|
|
}
|
|
}
|
|
|
|
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 %}
|