— بيروت 📱 حمّل التطبيق
عاجل
عاجل: مصادر في مستشفيات غزة: 3 شهداء بينهم طفل بنيران قوات الاحتلال في مدينتي غزة وخان يونس منذ صباح اليومعاجل: بحرية حرس الثورة: الناقلة الثانية التي كانت تحظى بحماية مقاتلات أمريكية تعرضت لحادث أثناء محاولتها العبور سرًا عبر المسار غير القانوني ما أدى إلى اندلاع حريق كبير وتوقفها في مكانهاعاجل: بحرية حرس الثورة: الناقلتين أطفأتا جميع أنظمة الملاحة والتعرّف الخاصة بهما واصطدمتا بـ”الجدار الناري” لمضيق هرمز قبل أن تُتركا عند الساحل الجنوبي للمضيق وهما تحترقانعاجل: بحرية حرس الثورة: نحذر من ان جميع السفن المتعاونة مع الجيش الأميركي المعتدي والتي تحاول العبور عبر هذا المسار ستلقى نفس المصيرعاجل: الخارجية القطرية: نعمل دون كلل لانهاء حالة عدم اليقين بشأن الازمة بين واشنطن طهرانعاجل: مصادر فلسطينية: قصف من طائرات الاحتلال على مبنى في منطقة عسقولة بحي الزيتون بمدينة غزةعاجل: وزارة الصحة بغزة: شهيدان و 16 جريحا وصلوا الى مستشفيات القطاع خلال الـ 24 ساعة الماضيةعاجل: فصائل المقاومة: ندعو الى حل ما يُسمّى “مجلس السلام” لفشله في انهاء معاناة شعبنا
إعلان تداول الذهب الفاخر في لبنان
عاجل

عاجل: أجواء لبنان | خريطة البلاغات والمتابعة المباشرة

`; } if(stage===1&&state data if(!data ); } // — STATS OVERHAUL LOGIC — state )`; } ); } if(state } } }); return m;…

100%

أجواء

رقم واتساب غير موثّقيمكنك الدخول، لكن التبليغ يتطلب توثيق الرقم.

${waiting>0?’يمكنك طلب رابط جديد بعد’:’يمكنك طلب الرابط الآن’}${waiting>0?formatPhoneCountdown(waiting):’متاح’}

${adminEscape(reviewLabel)}`;
}
const shell=document.querySelector(‘.report-shell’);if(shell)shell.scrollTo({top:0,behavior:force?’auto’:’smooth’});
if(stage===1&&state.maps.mini)setTimeout(()=>state.maps.mini.invalidateSize(),80);
return true;
}

async function handleReportSubmit(e){
e.preventDefault();

showConfirmModal(“هل أنت متأكد من إرسال هذا التقرير؟”, async () => {
const data=Object.fromEntries(new FormData(e.target).entries());
data.lat=document.getElementById(‘lat’).value;data.lon=document.getElementById(‘lon’).value;

if (data.type === ‘assault_mode’) {
if (!data.assault_subtype) { showAppModal(‘خطأ’, “يرجى تحديد نوع الاعتداء”, ‘error’); return; }
data.type = data.assault_subtype;
delete data.assault_subtype;
}

data.is_circular_flight = 0;
data.aircraft_direction = null;
if(!data.lat){showAppModal(‘خطأ’, “حدد الموقع”, ‘error’);return;}
if(!data.city || data.city.trim() === “”) { showAppModal(‘خطأ’, “يرجى تحديد المنطقة (اسم القرية أو المدينة)!”, ‘error’); return; }

try{const res=await fetch(`${API_URL}?action=add_report`,{method:’POST’,body:JSON.stringify(data)});const result=await res.json();if(res.ok){showAppModal(‘وصل بلاغك’, “ظهر البلاغ كبلاغ منفرد، وسيتحوّل إلى متقاطع جماعياً عند ورود بلاغ مستقل قريب.”, ‘success’);e.target.reset();document.getElementById(‘lat’).value=””;document.getElementById(‘lon’).value=””;document.getElementById(‘selected-location-name’).textContent=”لم تحدد موقعاً بعد”;delete document.getElementById(‘selected-location-name’).dataset.state;toggleReportStage(1,true);router.navigate(‘home’);}else showAppModal(‘تعذر الإرسال’, result.error||”فشل الإرسال”, ‘error’);}catch(x){showAppModal(‘خطأ’, “خطأ في الاتصال”, ‘error’);}
});
}

// — STATS OVERHAUL LOGIC —
state.statsData = null;
state.statsPeriod = ‘daily’;
state.statsMapMode=”markers”; // ‘markers’ | ‘heat’
state.statsHeatPoints = [];
state.statsCurrentFilter=”all”;

function stepStatsDate(days) {
const input = document.getElementById(‘stats-date’);
const curVal = input.value || getBeirutDateString();
const d = new Date(curVal);
d.setDate(d.getDate() + days);
input.value = d.toISOString().split(‘T’)[0];
loadStats();
}

function setStatsToday() {
const input = document.getElementById(‘stats-date’);
input.value = getBeirutDateString();
loadStats();
}

function switchStatsPeriod(period) {
state.statsPeriod = period;
document.querySelectorAll(‘.period-btn’).forEach(b => {
b.classList.toggle(‘active’, b.dataset.period === period);
});
updateStatsKPIs();
}

function updateStatsKPIs() {
if(!state.statsData) return;
const p = state.statsPeriod;
let tot = state.statsData.totals;
if(p === ‘weekly’) tot = state.statsData.weekly_totals;
if(p === ‘monthly’) tot = state.statsData.monthly_totals;
if(!tot) tot = {};

const totalCount = parseInt(tot.total || 0, 10);
const uav = parseInt(tot.uav || 0, 10);
const fighter = parseInt(tot.fighter || 0, 10);
const shelling = parseInt(tot.shelling || 0, 10);
const drone = parseInt(tot.drone || 0, 10);
const heli = parseInt(tot.helicopter || 0, 10);

const calcPct = (v) => totalCount > 0 ? `${Math.round((v / totalCount) * 100)}%` : ‘0%’;

document.getElementById(‘kpi-uav’).innerText = uav;
document.getElementById(‘kpi-uav-pct’).innerText = calcPct(uav);
document.getElementById(‘kpi-uav-bar’).style.width = calcPct(uav);

document.getElementById(‘kpi-fighter’).innerText = fighter;
document.getElementById(‘kpi-fighter-pct’).innerText = calcPct(fighter);
document.getElementById(‘kpi-fighter-bar’).style.width = calcPct(fighter);

document.getElementById(‘kpi-shelling’).innerText = shelling;
document.getElementById(‘kpi-shelling-pct’).innerText = calcPct(shelling);
document.getElementById(‘kpi-shelling-bar’).style.width = calcPct(shelling);

document.getElementById(‘kpi-drone’).innerText = drone;
document.getElementById(‘kpi-drone-pct’).innerText = calcPct(drone);
document.getElementById(‘kpi-drone-bar’).style.width = calcPct(drone);

document.getElementById(‘kpi-helicopter’).innerText = heli;
document.getElementById(‘kpi-helicopter-pct’).innerText = calcPct(heli);
document.getElementById(‘kpi-helicopter-bar’).style.width = calcPct(heli);

const periodLabel = p === ‘daily’ ? ‘اليوم’ : (p === ‘weekly’ ? ‘آخر 7 أيام’ : ‘هذا الشهر’);
document.getElementById(‘stats-total-badge’).innerText = `${totalCount} بلاغ (${periodLabel})`;
}

function setStatsMapMode(mode) {
state.statsMapMode = mode;
const btnMarkers = document.getElementById(‘btn-mode-markers’);
const btnHeat = document.getElementById(‘btn-mode-heat’);
if(btnMarkers) btnMarkers.classList.toggle(‘active’, mode === ‘markers’);
if(btnHeat) btnHeat.classList.toggle(‘active’, mode === ‘heat’);

const labelEl = document.getElementById(‘stats-map-mode-label’);
if(!state.maps.stats) return;

if(mode === ‘heat’) {
// Remove icon markers
state.statsMarkers.forEach(m => state.maps.stats.removeLayer(m));
// Add heat layer
if(!state.heatLayer && state.statsHeatPoints.length > 0) {
state.heatLayer = L.heatLayer(state.statsHeatPoints, {
radius: 32, blur: 24, maxZoom: 16, minOpacity: 0.45,
gradient: { 0.2: ‘#38bdf8’, 0.4: ‘#10b981’, 0.6: ‘#eab308’, 0.8: ‘#f97316’, 1.0: ‘#ef4444’ }
});
}
if(state.heatLayer) state.heatLayer.addTo(state.maps.stats);
if(labelEl) labelEl.innerHTML = ‘ خريطة الكثافة الحرارية (Heatmap)’;
} else {
// Remove heat layer
if(state.heatLayer) state.maps.stats.removeLayer(state.heatLayer);
// Re-add icon markers
state.statsMarkers.forEach(m => m.addTo(state.maps.stats));
if(labelEl) labelEl.innerHTML = ‘ عرض الرموز المباشرة’;
}
}

function recenterStatsMap() {
if(state.maps.stats) {
state.maps.stats.setView([33.8547, 35.8623], 9);
}
}

function filterStatsList(type) {
state.statsCurrentFilter = type;
document.querySelectorAll(‘#stats-type-filter .filter-chip’).forEach(c => {
c.classList.toggle(‘active’, c.dataset.type === type);
});
renderStatsList();
}

function renderStatsList() {
if(!state.statsData || !state.statsData.details) return;
const filter = state.statsCurrentFilter;
const rows = state.statsData.details.filter(r => {
if(filter === ‘all’) return true;
if(filter === ‘shelling’) return assaultTypes.includes(r.type);
if(filter === ‘drone’) return [‘drone’, ‘bomb_quad’].includes(r.type);
if(filter === ‘uav’) return [‘uav’, ‘raid_drone’, ‘bomb_drone’].includes(r.type);
if(filter === ‘fighter’) return [‘fighter’, ‘raid_fighter’].includes(r.type);
return r.type === filter;
});

const tb = document.getElementById(‘stats-table-body’);
const mobList = document.getElementById(‘stats-mobile-list’);
if(tb) tb.innerHTML = ”;
if(mobList) mobList.innerHTML = ”;

if(rows.length === 0) {
if(tb) tb.innerHTML = ‘

لا توجد بلاغات مسجلة لهذا النوع في هذا اليوم

‘;
if(mobList) mobList.innerHTML = ‘لا توجد بلاغات مسجلة لهذا النوع في هذا اليوم

‘;
return;
}

rows.forEach(r => {
const c = assaultTypes.includes(r.type) ? ‘text-shelling font-bold’ : (r.type===’uav’?’text-danger font-bold’:(r.type===’fighter’?’text-accent font-bold’:’text-warning font-bold’));
const s = translate.source[r.report_source] || ‘سماع’;
const sIcon = r.report_source === ‘visual’ ? ‘‘ : ‘‘;
const timeStr = formatBeirutTime(r.created_at);
const cityName = adminEscape(r.city_name || ‘منطقة غير محددة’);
const typeLabel = translate.type[r.type] || r.type;

// Desktop Table Row
if(tb) {
tb.innerHTML += `

${timeStr} ${cityName} ${eventIconMarkup(r.type,’table-event-icon’)}${typeLabel} ${sIcon} ${s} ${!isNaN(parseFloat(r.lat)) ? ` عرض` : ‘-‘}

`;
}

// Mobile Card Item
if(mobList) {
mobList.innerHTML += `

${eventIconMarkup(r.type, ‘w-7 h-7 object-contain drop-shadow-sm’)}

${typeLabel}
• ${sIcon} ${s}

${cityName}

${timeStr}
${!isNaN(parseFloat(r.lat)) ? ` الخريطة` : ”}

`;
}
});
}

function focusStatsReport(lat, lon) {
if(state.maps.stats) {
state.maps.stats.setView([lat, lon], 12, { animate: true });
const card = document.querySelector(‘.stats-map-card’);
if(card) window.scrollTo({ top: card.offsetTop – 60, behavior: ‘smooth’ });
}
}

const arabicDays = [‘الأحد’, ‘الاثنين’, ‘الثلاثاء’, ‘الأربعاء’, ‘الخميس’, ‘الجمعة’, ‘السبت’];

async function loadStats(){
const d = document.getElementById(‘stats-date’).value || getBeirutDateString();
const dateBadge = document.getElementById(‘stats-map-date’);
if(dateBadge) dateBadge.innerText = d;

// Single Unified Map
if(!state.maps.stats) {
state.maps.stats = L.map(‘stats-map’, {
zoomControl: false,
zoomSnap: 0.5,
scrollWheelZoom: false // Prevent touch/wheel trapping
}).setView([33.8547, 35.8623], 9);

L.tileLayer(‘https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png’, {
className: ‘ajwa-soft-tiles’,
maxZoom: 19,
attribution: ‘© OpenStreetMap’
}).addTo(state.maps.stats);

L.control.zoom({ position: ‘topright’ }).addTo(state.maps.stats);
}

try {
const r = await fetch(`${API_URL}?action=get_stats&date=${d}&t=${Date.now()}`);
const j = await r.json();
state.statsData = j;

// Update date subtitle
const dateObj = new Date(d);
const dayName = !isNaN(dateObj.getTime()) ? arabicDays[dateObj.getDay()] : ”;
const subEl = document.getElementById(‘period-sub-daily’);
if(subEl) subEl.innerText = dayName;

// Update KPIs
updateStatsKPIs();

// Clear markers
state.statsMarkers.forEach(mk => state.maps.stats.removeLayer(mk));
state.statsMarkers = [];
state.statsHeatPoints = [];

let geoReportsCount = 0;
const validDetails = j.details.filter(r => !isNaN(parseFloat(r.lat)) && !isNaN(parseFloat(r.lon)));
const dispersedCoords = (window.AjwaData && window.AjwaData.disperseCoordinates)
? window.AjwaData.disperseCoordinates(validDetails, r => parseFloat(r.lat), r => parseFloat(r.lon))
: (function(items){
const clusters = [];
items.forEach(item => {
const lat = parseFloat(item.lat), lon = parseFloat(item.lon);
let cluster = clusters.find(c => Math.hypot(lat – c.baseLat, (lon – c.baseLon) * Math.cos(c.baseLat * Math.PI / 180)) {
const n = c.items.length;
const cosLat = Math.cos(c.baseLat * Math.PI / 180) || 0.835;
if (n === 1) m.set(c.items[0], [c.baseLat, c.baseLon]);
else {
const r = n === 2 ? 0.0020 : (n {
const angle = (2 * Math.PI * i) / n – (Math.PI / 2);
m.set(it, [c.baseLat + r * Math.sin(angle), c.baseLon + (r / cosLat) * Math.cos(angle)]);
});
}
});
return m;
})(validDetails);

validDetails.forEach(r => {
geoReportsCount++;
const timeStr = formatBeirutTime(r.created_at);
const ic = L.divIcon({
html: `${eventIconMarkup(r.type,’map-event-icon’)}

`,
className: ‘custom-div-icon’,
iconSize: [40, 40],
iconAnchor: [20, 20],
popupAnchor: [0, -24]
});
const coords = dispersedCoords.get(r) || [parseFloat(r.lat), parseFloat(r.lon)];
const marker = L.marker(coords, {icon: ic}).bindPopup(`${translate.type[r.type] || r.type}
${r.city_name || ”}
${timeStr}`);
state.statsMarkers.push(marker);

let intensity = 0.55;
if(assaultTypes.includes(r.type)) intensity = 1.0;
state.statsHeatPoints.push([parseFloat(r.lat), parseFloat(r.lon), intensity]);
});

const countEl = document.getElementById(‘stats-map-count-badge’);
if(countEl) countEl.innerText = `${geoReportsCount} حدث محدد جغرافياً`;

// Set map layer based on current mode (default: markers, heatmap only on-demand)
setStatsMapMode(state.statsMapMode);
if(state.statsMarkers && state.statsMarkers.length > 0) {
try {
const group = L.featureGroup(state.statsMarkers);
state.maps.stats.fitBounds(group.getBounds().pad(0.25));
} catch(e) {}
} else if(state.maps.stats) {
state.maps.stats.setView([33.8547, 35.8623], 9);
}

// Render activity list
renderStatsList();

// Format Chart.js Activity curve
if(j.chart && j.chart.length > 0) {
const chartLabels = j.chart.map(i => {
try {
const dt = new Date(i.date);
return `${arabicDays[dt.getDay()]} ${dt.getDate()}`;
} catch(e) { return i.date; }
});
const chartValues = j.chart.map(i => i.count);
const peak = Math.max(…chartValues, 0);
const peakBadge = document.getElementById(‘chart-peak-badge’);
if(peakBadge) peakBadge.innerText = `أعلى نشاط: ${peak} بلاغ`;

if(state.chart) state.chart.destroy();
const canvas = document.getElementById(‘statsChart’);
if(canvas) {
const ctx = canvas.getContext(‘2d’);
const gradient = ctx.createLinearGradient(0, 0, 0, 240);
gradient.addColorStop(0, ‘rgba(56, 189, 248, 0.45)’);
gradient.addColorStop(1, ‘rgba(56, 189, 248, 0.0)’);

state.chart = new Chart(ctx, {
type: ‘line’,
data: {
labels: chartLabels,
datasets: [{
label: ‘عدد البلاغات’,
data: chartValues,
borderColor: ‘#38bdf8’,
borderWidth: 3,
backgroundColor: gradient,
fill: true,
tension: 0.35,
pointBackgroundColor: ‘#38bdf8’,
pointBorderColor: ‘#0f172a’,
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: ‘#0f172a’,
titleColor: ‘#38bdf8’,
bodyColor: ‘#ffffff’,
borderColor: ‘#334155’,
borderWidth: 1,
padding: 10,
displayColors: false,
callbacks: {
label: (context) => ` ${context.parsed.y} بلاغ مسجل`
}
}
},
scales: {
y: {
beginAtZero: true,
grid: { color: ‘rgba(255, 255, 255, 0.06)’ },
ticks: { color: ‘#94a3b8’, font: { size: 10 } }
},
x: {
grid: { display: false },
ticks: { color: ‘#94a3b8’, font: { size: 11, weight: ‘bold’ } }
}
}
}
});
}
}
} catch(x){
console.error(“loadStats error:”, x);
}
}

async function generateAISummary(){
if (!state.user || state.user.is_active != 1) return;
document.getElementById(‘modal-ai-analysis’).classList.remove(‘hidden’);
document.getElementById(‘ai-analysis-content’).innerHTML=’جاري التحليل…

‘;
try {
const d = document.getElementById(‘stats-date’).value || getBeirutDateString();
const r = await fetch(`${API_URL}?action=get_stats&date=${d}&t=${Date.now()}`);
const j = await r.json();
const c = `التاريخ: ${d}\n إجمالي: ${j.totals.total}\n المناطق: ${j.details.slice(0,10).map(x=>x.city_name).join(‘, ‘)}`;
const s = await callGemini(“لخص الوضع”, c, d);
document.getElementById(‘ai-analysis-content’).innerHTML = marked.parse(s);
} catch(x) {
document.getElementById(‘ai-analysis-content’).innerHTML = ‘فشل في التحليل’;
}
}

async function callGemini(p, c, d = null) {
if(!aiEnabled) return “غير مفعل”;
const payload = { prompt: p, context: c }; if(d) payload.date = d;
const r = await fetch(`${API_URL}?action=ask_ai`, { method: ‘POST’, body: JSON.stringify(payload) });
const j = await r.json(); return j.response || “خطأ”;
}

const adminEscape=value=>String(value??”).replace(/[&”‘]/g,c=>({‘&’:’&’,'<':'’:’>’,’”‘:’”‘,”‘”:”’}[c]));
const adminSearchText=value=>String(value??”).toLowerCase().trim();
async function refreshAdmin(){
const refreshButtons=document.querySelectorAll(‘#view-admin button[onclick=”refreshAdmin()”]’);refreshButtons.forEach(button=>button.disabled=true);
try{
const r=await fetch(`${API_URL}?action=admin_dashboard&admin_id=${state.user.id}&t=${Date.now()}`,{cache:’no-store’});const j=await r.json();
if(!r.ok||!Array.isArray(j.users)||!Array.isArray(j.reports))throw new Error(j.error||’تعذر تحميل بيانات الإدارة’);
state.adminData=j;
const userIds=new Set(j.users.map(item=>item.id)),reportIds=new Set(j.reports.map(item=>item.id));
state.adminSelection.users.forEach(id=>{if(!userIds.has(id))state.adminSelection.users.delete(id);});state.adminSelection.reports.forEach(id=>{if(!reportIds.has(id))state.adminSelection.reports.delete(id);});
document.getElementById(‘admin-total-users’).textContent=j.users.length;document.getElementById(‘admin-total-reports’).textContent=j.reports.length;document.getElementById(‘admin-total-reports-detail’).textContent=`${j.reports.filter(report=>Number(report.is_active)!==0).length} ظاهر · ${j.reports.filter(report=>Number(report.is_active)===0).length} مخفي`;document.getElementById(‘admin-banned-users’).textContent=j.users.filter(u=>Number(u.is_banned)===1).length;document.getElementById(‘admin-verified-users’).textContent=j.users.filter(u=>u.phone_verified_at).length;
document.getElementById(‘admin-users-tab-count’).textContent=j.users.length;document.getElementById(‘admin-reports-tab-count’).textContent=j.reports.length;document.getElementById(‘admin-push-count’).textContent=j.push_count||0;
renderAdminTables();
}catch(error){showAppModal(‘تعذر التحديث’,error.message||’فشل تحميل لوحة الإدارة’,’error’);}
finally{refreshButtons.forEach(button=>button.disabled=false);}
}

function switchAdminTab(tab){
state.adminTab=tab;
document.getElementById(‘admin-users’).classList.toggle(‘hidden’,tab!==’users’);document.getElementById(‘admin-reports’).classList.toggle(‘hidden’,tab!==’reports’);document.getElementById(‘admin-settings’).classList.toggle(‘hidden’,tab!==’settings’);
document.getElementById(‘admin-toolbar’).classList.toggle(‘hidden’,tab===’settings’);document.getElementById(‘admin-user-filters’).classList.toggle(‘hidden’,tab!==’users’);document.getElementById(‘admin-report-filters’).classList.toggle(‘hidden’,tab!==’reports’);
[‘users’,’reports’,’settings’].forEach(name=>{const button=document.getElementById(`btn-admin-${name}`);button.classList.toggle(‘is-active’,name===tab);button.setAttribute(‘aria-selected’,String(name===tab));});
const search=document.getElementById(‘admin-search’);if(search)search.placeholder=tab===’reports’?’ابحث بالمنطقة أو المبلّغ’:’ابحث بالاسم أو البريد أو الهاتف’;
if(tab===’settings’){document.getElementById(‘admin-bulk-bar’).classList.add(‘hidden’);loadAdminSettings();}else renderAdminTables();
}
function adminFiltersChanged(){state.pagination.adminUsers=1;state.pagination.adminReports=1;renderAdminTables();}
function clearAdminSearch(){const input=document.getElementById(‘admin-search’);input.value=””;input.focus();adminFiltersChanged();}
function resetAdminFilters(){
document.getElementById(‘admin-search’).value=””;[‘admin-filter-users’,’admin-filter-verification’,’admin-filter-reports’,’admin-filter-source’,’admin-filter-report-status’,’admin-filter-date’].forEach(id=>{const input=document.getElementById(id);if(input)input.value=”all”;});adminFiltersChanged();
}
async function loadAdminSettings(){try{const r=await fetch(`${API_URL}?action=get_admin_settings`);const j=await r.json();const input=document.getElementById(‘admin-gemini-key’);input.value=””;input.placeholder=j.gemini_configured?’المفتاح محفوظ — أدخل مفتاحاً جديداً لاستبداله’:’أدخل مفتاح Gemini’;}catch(x){}}
async function saveAdminSettings(){const k=document.getElementById(‘admin-gemini-key’).value;try{const r=await fetch(`${API_URL}?action=update_settings`,{method:’POST’,body:JSON.stringify({admin_id:state.user.id,gemini_key:k})});if(r.ok){showAppModal(‘تم’, “تم حفظ الإعدادات”, ‘success’);fetchConfig();}else showAppModal(‘خطأ’, “فشل الحفظ”, ‘error’);}catch(x){}}
function toggleKeyVisibility(){const i=document.getElementById(‘admin-gemini-key’);i.type=i.type===’password’?’text’:’password’;}

function renderPaginationControls(totalItems, itemsPerPage, currentPage, context) {
const totalPages = Math.ceil(totalItems / itemsPerPage); if(totalPages <= 1) return '';
let html = `السابق`;
let startPage = Math.max(1, currentPage – 2); let endPage = Math.min(totalPages, currentPage + 2);
for (let i = startPage; i {
if(context === ‘users’) state.pagination.adminUsers = page;
else if(context === ‘reports’) state.pagination.adminReports = page;
else if(context === ‘profile’) state.pagination.profileReports = page;

if(context === ‘profile’) renderMyReports();
else renderAdminTables();
}

function sortData(data, sortConfig) {
return […data].sort((a, b) => {
let va = a[sortConfig.col], vb = b[sortConfig.col];
if (typeof va === ‘string’) va = va.toLowerCase();
if (typeof vb === ‘string’) vb = vb.toLowerCase();
if (va vb) return sortConfig.dir === ‘asc’ ? 1 : -1;
return 0;
});
}

function adminSort(context, col) {
const current = state.adminSort[context];
if(current.col === col) current.dir = current.dir === ‘asc’ ? ‘desc’ : ‘asc’;
else { current.col = col; current.dir=”asc”; }
renderAdminTables();
}

function filteredAdminUsers(){
const query=adminSearchText(document.getElementById(‘admin-search’).value),status=document.getElementById(‘admin-filter-users’).value,verification=document.getElementById(‘admin-filter-verification’).value;
let users=state.adminData.users.filter(user=>[user.username,user.email,user.phone_number,user.registration_ip].some(value=>adminSearchText(value).includes(query)));
if(status===’active’)users=users.filter(user=>Number(user.is_active)===1&&Number(user.is_banned)===0);if(status===’banned’)users=users.filter(user=>Number(user.is_banned)===1);if(status===’inactive’)users=users.filter(user=>Number(user.is_active)===0);
if(verification===’phone’)users=users.filter(user=>user.phone_verified_at);if(verification===’email’)users=users.filter(user=>user.email_verified_at);if(verification===’unverified’)users=users.filter(user=>!user.phone_verified_at&&!user.email_verified_at);
return sortData(users,state.adminSort.users);
}
function filteredAdminReports(){
const query=adminSearchText(document.getElementById(‘admin-search’).value),type=document.getElementById(‘admin-filter-reports’).value,source=document.getElementById(‘admin-filter-source’).value,status=document.getElementById(‘admin-filter-report-status’).value,period=document.getElementById(‘admin-filter-date’).value;
let reports=state.adminData.reports.filter(report=>[report.city_name,report.username,translate.type[report.type],report.type].some(value=>adminSearchText(value).includes(query)));
if(type!==’all’)reports=reports.filter(report=>type===’assault’?assaultTypes.includes(report.type):report.type===type);if(source!==’all’)reports=reports.filter(report=>(report.report_source===’audio’?’auditory’:report.report_source)===source);
if(status===’active’)reports=reports.filter(report=>Number(report.is_active)!==0);if(status===’inactive’)reports=reports.filter(report=>Number(report.is_active)===0);
if(period!==’all’){const days=period===’today’?0:period===’7d’?6:29;const cutoff=new Date();cutoff.setHours(0,0,0,0);cutoff.setDate(cutoff.getDate()-days);reports=reports.filter(report=>{const parsed=new Date(String(report.created_at||”).replace(‘ ‘,’T’));return !Number.isNaN(parsed.getTime())&&parsed>=cutoff;});}
return sortData(reports,state.adminSort.reports);
}

function renderAdminTables(){
if(!state.adminData||!Array.isArray(state.adminData.users)||!Array.isArray(state.adminData.reports))return;
const itemsPerPage = parseInt(document.getElementById(‘admin-rows’).value);
const users=filteredAdminUsers();
const userPages=Math.max(1,Math.ceil(users.length/itemsPerPage));state.pagination.adminUsers=Math.min(state.pagination.adminUsers,userPages);const usersPage=state.pagination.adminUsers;
const usersSlice = users.slice((usersPage – 1) * itemsPerPage, usersPage * itemsPerPage);
state.adminVisibleIds.users=usersSlice.filter(user=>user.role!==’admin’).map(user=>user.id);
const ub = document.getElementById(‘admin-users-body’); ub.innerHTML=”;
if(!usersSlice.length)ub.innerHTML=’

لا توجد حسابات مطابقةغيّر الفلاتر أو امسح عبارة البحث.

‘;
usersSlice.forEach(u=>{
const isAdmin=u.role===’admin’,checked=state.adminSelection.users.has(u.id);let status=””;if(Number(u.is_banned)===1)status=”محظور”;else if(Number(u.is_active)===1)status=”نشط”;else status=”غير مفعل”;
const phoneVerification=`${u.phone_verified_at?’واتساب موثق’:’واتساب غير موثق’}${(!u.phone_verified_at&&u.phone_number&&!isAdmin)?`إرسال الرابط`:”}`;
const verification=`${phoneVerification}${u.email_verified_at?’بريد موثق’:’بريد غير موثق’}`;
const actions=isAdmin?’مدير النظام’:`${Number(u.is_active)===1?’تعطيل’:’تفعيل’}${Number(u.is_banned)===1?’فك الحظر’:’حظر’}كلمة المرورحذف`;
ub.innerHTML+=`

${isAdmin?’‘:“} ${adminEscape(String(u.username||’?’).trim().charAt(0)||’?’)}${adminEscape(u.username)}${adminEscape(u.registration_ip||’IP غير متاح’)} · ${adminEscape(String(u.created_at||”).split(‘ ‘)[0])} ${adminEscape(u.email)}${adminEscape(u.phone_number||’لا يوجد رقم’)}${verification} ${status} ${Number(u.report_count)||0} ${actions}

`;
});
document.getElementById(‘admin-users-pagination’).innerHTML = renderPaginationControls(users.length, itemsPerPage, usersPage, ‘users’);
document.getElementById(‘admin-users-result-count’).textContent=`عرض ${usersSlice.length} من ${users.length} عضواً`;

const reports=filteredAdminReports();const reportPages=Math.max(1,Math.ceil(reports.length/itemsPerPage));state.pagination.adminReports=Math.min(state.pagination.adminReports,reportPages);const reportsPage=state.pagination.adminReports;
const reportsSlice = reports.slice((reportsPage – 1) * itemsPerPage, reportsPage * itemsPerPage);
state.adminVisibleIds.reports=reportsSlice.map(report=>report.id);
const rb = document.getElementById(‘admin-reports-body’); rb.innerHTML=”;
if(!reportsSlice.length)rb.innerHTML=’

لا توجد بلاغات مطابقةغيّر النوع أو المصدر أو الفترة.

‘;
reportsSlice.forEach(r=>{
const c = assaultTypes.includes(r.type) ? ‘text-shelling’ : (r.type===’uav’?’text-danger’:(r.type===’fighter’?’text-accent’:’text-warning’));
const time = formatBeirutTime(r.created_at);
const date = formatBeirutDateTime(r.created_at).split(‘,’)[0];
const checked=state.adminSelection.reports.has(r.id),sourceLabel=(r.report_source===’visual’?’مشاهدة’:’سماع’),reportActive=Number(r.is_active)!==0;
rb.innerHTML+=`

${eventIconMarkup(r.type,’table-event-icon’)}${adminEscape(translate.type[r.type]||r.type)}${reportActive?’ظاهر للعامة’:’مخفي’}${adminEscape(String(r.id).slice(0,8))} ${adminEscape(r.city_name||’منطقة غير محددة’)}${sourceLabel} ${adminEscape(r.username||’مجهول’)} ${adminEscape(time)}${adminEscape(date)}

${reportActive?’إخفاء’:’إظهار’}تعديلحذف

`;
});
document.getElementById(‘admin-reports-pagination’).innerHTML = renderPaginationControls(reports.length, itemsPerPage, reportsPage, ‘reports’);
document.getElementById(‘admin-reports-result-count’).textContent=`عرض ${reportsSlice.length} من ${reports.length} بلاغاً`;
document.querySelectorAll(‘[data-sort-context]’).forEach(icon=>{const config=state.adminSort[icon.dataset.sortContext],active=config.col===icon.dataset.sortCol;icon.className=`sort-icon fa-solid ${active?(config.dir===’asc’?’fa-sort-up’:’fa-sort-down’):’fa-sort’}${active?’ sort-active’:”}`;});
[‘users’,’reports’].forEach(context=>{const all=document.getElementById(`admin-select-all-${context}`),ids=state.adminVisibleIds[context];if(all){all.checked=ids.length>0&&ids.every(id=>state.adminSelection[context].has(id));all.indeterminate=ids.some(id=>state.adminSelection[context].has(id))&&!all.checked;}});
updateAdminBulkBar();
}

function toggleAdminSelection(context,id,checked){if(checked)state.adminSelection[context].add(id);else state.adminSelection[context].delete(id);const all=document.getElementById(`admin-select-all-${context}`),ids=state.adminVisibleIds[context];if(all){all.checked=ids.length>0&&ids.every(item=>state.adminSelection[context].has(item));all.indeterminate=ids.some(item=>state.adminSelection[context].has(item))&&!all.checked;}updateAdminBulkBar();}
function toggleAdminPageSelection(context,checked){state.adminVisibleIds[context].forEach(id=>checked?state.adminSelection[context].add(id):state.adminSelection[context].delete(id));renderAdminTables();}
function clearAdminSelection(){if(state.adminTab===’users’||state.adminTab===’reports’)state.adminSelection[state.adminTab].clear();renderAdminTables();}
function updateAdminBulkBar(){const context=state.adminTab,count=(context===’users’||context===’reports’)?state.adminSelection[context].size:0,bar=document.getElementById(‘admin-bulk-bar’);bar.classList.toggle(‘hidden’,count===0||context===’settings’);document.getElementById(‘admin-selection-count’).textContent=count;document.getElementById(‘admin-user-bulk-actions’).classList.toggle(‘hidden’,context!==’users’);document.getElementById(‘admin-report-bulk-actions’).classList.toggle(‘hidden’,context!==’reports’);}
function bulkAdminAction(operation){const context=operation.endsWith(‘_reports’)?’reports’:’users’,ids=[…state.adminSelection[context]];if(!ids.length)return;const labels={activate_users:’تفعيل الحسابات المحددة’,deactivate_users:’تعطيل الحسابات المحددة’,ban_users:’حظر الحسابات المحددة’,unban_users:’فك حظر الحسابات المحددة’,delete_users:’حذف الحسابات المحددة نهائياً’,activate_reports:’إظهار البلاغات المحددة للعامة’,deactivate_reports:’إخفاء البلاغات المحددة عن العامة’,delete_reports:’حذف البلاغات المحددة نهائياً’};showConfirmModal(`هل تريد ${labels[operation]}؟`,async()=>{try{const response=await fetch(`${API_URL}?action=admin_bulk_action`,{method:’POST’,body:JSON.stringify({type:operation,target_ids:ids})});const result=await response.json();if(!response.ok)throw new Error(result.error||’فشل تنفيذ الإجراء’);state.adminSelection[context].clear();await refreshAdmin();showAppModal(‘تم التنفيذ’,`تم تحديث ${result.affected||ids.length} عنصراً`,’success’);}catch(error){showAppModal(‘تعذر التنفيذ’,error.message,’error’);}});}

async function openUserModal(uid){
const u=state.adminData.users.find(x=>x.id===uid);if(!u)return;
toggleModal(‘modal-user’, true);
document.getElementById(‘modal-user-content’).innerHTML = ‘

جاري التحميل…

‘;

try {
const r=await fetch(`${API_URL}?action=get_user_reports&target_id=${uid}&t=${Date.now()}`);
const j=await r.json();
const reps=j.data||[];
document.getElementById(‘modal-user-content’).innerHTML=`

IP

${u.registration_ip||’-‘}

تاريخ الانضمام

${u.created_at.split(‘ ‘)[0]}

آخر التقارير

${reps.map(x=>`

`).join(”)}

النوع المنطقة الوقت
${translate.type[x.type]} ${x.city_name} ${formatBeirutDateTime(x.created_at)}

`;
} catch(e) {
document.getElementById(‘modal-user-content’).innerHTML = ‘

فشل تحميل البيانات

‘;
}
}
async function adminAction(t,tid){
showConfirmModal(“هل أنت متأكد من هذا الإجراء؟”, async () => {
try{const response=await fetch(`${API_URL}?action=admin_action`,{method:’POST’,body:JSON.stringify({admin_id:state.user.id,target_id:tid,type:t})});const result=await response.json();if(!response.ok)throw new Error(result.error||’فشل تنفيذ الإجراء’);await refreshAdmin();showAppModal(‘تم’,’تم تنفيذ الإجراء بنجاح’,’success’);}catch(error){showAppModal(‘تعذر التنفيذ’,error.message,’error’);}
});
}

async function adminResendPhoneActivation(userId,button){
const original=button.innerHTML;
button.disabled=true;
button.innerHTML=’جارٍ الإرسال’;
try{
const response=await fetch(`${API_URL}?action=admin_action`,{method:’POST’,body:JSON.stringify({admin_id:state.user.id,target_id:userId,type:’resend_phone_activation’})});
const result=await response.json();
if(!response.ok)throw new Error(result.error||’تعذر إرسال رابط التوثيق’);
showAppModal(‘تم الإرسال’,result.message||’تم إرسال رابط التوثيق عبر واتساب’,’success’);
}catch(error){showAppModal(‘تعذر الإرسال’,error.message,’error’);}
finally{button.disabled=false;button.innerHTML=original;}
}

let editCityTimeout = null;
function handleEditCityInput(q) {
clearTimeout(editCityTimeout);
const resultsDiv = document.getElementById(‘edit-city-results’);
const spinner = document.getElementById(‘edit-city-spinner’);
if (!resultsDiv) return;
const query = (q || ”).trim();
if (query.length {
try {
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&countrycodes=lb&addressdetails=1&limit=25&accept-language=ar`);
const data = await res.json();
if (spinner) spinner.classList.add(‘hidden’);
resultsDiv.innerHTML = ”;

const allowedTypes = [‘city’, ‘town’, ‘village’, ‘hamlet’, ‘administrative’, ‘suburb’, ‘neighbourhood’, ‘locality’];
const filtered = Array.isArray(data) ? data.filter(item => {
if (item.address && item.address.country_code !== ‘lb’) return false;
const isAllowed = (item.class === ‘place’ && allowedTypes.includes(item.type)) ||
(item.class === ‘boundary’ && item.type === ‘administrative’) ||
allowedTypes.includes(item.type);
return isAllowed;
}) : [];

const itemsToShow = filtered.length ? filtered.slice(0, 7) : (Array.isArray(data) ? data.slice(0, 5) : []);

if (itemsToShow.length > 0) {
resultsDiv.classList.remove(‘hidden’);
itemsToShow.forEach(item => {
let cleanName = (item.address && (item.address.village || item.address.town || item.address.city || item.address.suburb || item.address.hamlet)) || item.name || query;
const opt = document.createElement(‘div’);
opt.className=”p-2.5 hover:bg-blue-600 hover:text-white cursor-pointer border-b border-gray-700 text-right text-xs transition-colors flex justify-between items-center”;
opt.innerHTML = `${cleanName}${parseFloat(item.lat).toFixed(4)}, ${parseFloat(item.lon).toFixed(4)}`;
opt.onclick = () => {
document.getElementById(‘edit-report-city’).value = cleanName;
const latEl = document.getElementById(‘edit-report-lat’);
const lonEl = document.getElementById(‘edit-report-lon’);
const badgeEl = document.getElementById(‘edit-coords-badge’);
if (latEl) latEl.value = item.lat;
if (lonEl) lonEl.value = item.lon;
if (badgeEl) badgeEl.innerText = `${parseFloat(item.lat).toFixed(4)}, ${parseFloat(item.lon).toFixed(4)}`;
resultsDiv.classList.add(‘hidden’);
};
resultsDiv.appendChild(opt);
});
} else {
resultsDiv.classList.add(‘hidden’);
}
} catch(e) {
if (spinner) spinner.classList.add(‘hidden’);
console.error(“Edit city search error:”, e);
}
}, 350);
}

function openEditReportModal(reportId) {
const report = state.adminData.reports.find(r => r.id === reportId); if(!report) return;
document.getElementById(‘edit-report-id’).value = report.id;
document.getElementById(‘edit-report-type’).value = report.type;
document.getElementById(‘edit-report-city’).value = report.city_name || ”;
document.getElementById(‘edit-report-source’).value = report.report_source || ‘visual’;

const latEl = document.getElementById(‘edit-report-lat’);
const lonEl = document.getElementById(‘edit-report-lon’);
const badgeEl = document.getElementById(‘edit-coords-badge’);
if (latEl) latEl.value = report.lat || ”;
if (lonEl) lonEl.value = report.lon || ”;
if (badgeEl) {
if (report.lat && report.lon) {
badgeEl.innerText = `${parseFloat(report.lat).toFixed(4)}, ${parseFloat(report.lon).toFixed(4)}`;
} else {
badgeEl.innerText=”غير محدد”;
}
}
const resEl = document.getElementById(‘edit-city-results’);
if (resEl) { resEl.innerHTML = ”; resEl.classList.add(‘hidden’); }
toggleModal(‘modal-edit-report’, true);
}

async function submitEditReport(e) {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());
data.admin_id = state.user.id;
try {
const res = await fetch(`${API_URL}?action=edit_report`, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(data)
});
const j = await res.json();
if (res.ok) {
showAppModal(‘نجاح’, “تم حفظ التعديلات بنجاح”, ‘success’);
toggleModal(‘modal-edit-report’, false);
refreshAdmin();
} else {
showAppModal(‘خطأ’, j.error || “تعذر حفظ التعديلات”, ‘error’);
}
} catch (err) {
showAppModal(‘خطأ’, “فشل الاتصال بالخادم”, ‘error’);
}
}

window.addEventListener(‘load’, async () => {
// 1. تهيئة الخريطة إذا كانت موجودة
try {
if(!state.maps.main && document.getElementById(‘map’)) {
state.maps.main = L.map(‘map’, {zoomControl:false,zoomSnap:.25}).setView([33.86, 35.86], window.innerWidth {
const hv = window.location.hash.slice(1);
if(hv && document.getElementById(`view-${hv}`)) {
router.navigate(hv);
}
});

// 5. التحقق من وجود إحداثيات في الرابط (للمشاركة)
const latParam = u.get(‘lat’);
const lonParam = u.get(‘lon’);
if (latParam && lonParam && Number.isFinite(Number(latParam)) && Number.isFinite(Number(lonParam)) && Math.abs(Number(latParam)) <= 90 && Math.abs(Number(lonParam)) {
if(state.maps.main) {
state.maps.main.setView([latParam, lonParam], 14);
L.popup()
.setLatLng([latParam, lonParam])
.setContent(‘

موقع البلاغ

‘)
.openOn(state.maps.main);
document.getElementById(‘map’).scrollIntoView({behavior: ‘smooth’});
}
}, 1500);
}

// جلب الإعدادات والتقارير
const savedRegion=localStorage.getItem(‘ajwa_push_region’);if(savedRegion&&document.getElementById(‘push-region’))document.getElementById(‘push-region’).value=savedRegion;
await fetchConfig();
fetchMapReports();
});

function urlBase64ToUint8Array(base64String) { const padding = ‘=’.repeat((4 – base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/-/g, ‘+’).replace(/_/g, ‘/’); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i {event.preventDefault();deferredInstallPrompt=event;if(localStorage.getItem(‘ajwa_install_dismissed’)!==’1′)document.getElementById(‘install-prompt’).hidden=false;});
async function installAjwaApp(){if(!deferredInstallPrompt)return;await deferredInstallPrompt.prompt();deferredInstallPrompt=null;document.getElementById(‘install-prompt’).hidden=true;}
function dismissInstall(){localStorage.setItem(‘ajwa_install_dismissed’,’1′);document.getElementById(‘install-prompt’).hidden=true;}
async function registerServiceWorker() { if (‘serviceWorker’ in navigator) { try { const registration = await navigator.serviceWorker.register(‘sw.js?v=20260917.7′,{updateViaCache:’none’}); if(‘PushManager’ in window) checkSubscription(registration); } catch (error) { console.error(‘SW Error:’, error); } } }
async function checkSubscription(registration) { const subscription = await registration.pushManager.getSubscription(); if (subscription) { const btnIcon = document.getElementById(‘notif-btn-icon’); if(btnIcon) btnIcon.className = “fa-solid fa-bell text-yellow-400 text-lg”; } else { const prompt = document.getElementById(‘push-prompt’); if(prompt) setTimeout(() => prompt.classList.add(‘show’), 5000); } }
function closePushPrompt() { const p = document.getElementById(‘push-prompt’); if(p) p.classList.remove(‘show’); }

async function subscribeToPush() {
if (!vapidPublicKey) return showAppModal(‘خطأ’, “خطأ في إعدادات الخادم”, ‘error’);
if (‘serviceWorker’ in navigator) {
try {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) });
const region=document.getElementById(‘push-region’)?.value||localStorage.getItem(‘ajwa_push_region’)||’all’;
const payload=subscription.toJSON();payload.region=region;localStorage.setItem(‘ajwa_push_region’,region);
await fetch(`${API_URL}?action=subscribe_push`, { method: ‘POST’, body: JSON.stringify(payload) });
showAppModal(‘تم’, region===’all’?”تم تفعيل تنبيهات كل لبنان “:`تم تفعيل تنبيهات ${region} `, ‘success’);
const icon = document.getElementById(‘notif-btn-icon’); if(icon) icon.className = “fa-solid fa-bell text-yellow-400 text-lg”;
closePushPrompt();
} catch (err) { console.error(err); showAppModal(‘خطأ’, “فشل التفعيل. تأكد من إعطاء الصلاحية.”, ‘error’); }
}
}

async function togglePushSubscription() {
if (‘serviceWorker’ in navigator) {
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.getSubscription();
if (sub) {
showConfirmModal(“هل تريد إيقاف التنبيهات؟”, async () => {
await sub.unsubscribe();
await fetch(`${API_URL}?action=unsubscribe_push`, {method:’POST’, body:JSON.stringify({endpoint: sub.endpoint})});
document.getElementById(‘notif-btn-icon’).className = “fa-regular fa-bell text-gray-400 text-lg”;
showAppModal(‘تم’, “تم إيقاف التنبيهات.”, ‘success’);
});
} else { subscribeToPush(); }
}
}

المصدر: ajwa2.com
فريق التحرير
بقلم

فريق التحرير

حساب النشر الجماعي لموقع بتوقيت بيروت، الذي تديره The Verificat Agency بإدارة محمد قاسم. ينشر الأخبار والمتابعات المنسوبة إلى مصادرها، ويستخدم أدوات مساعدة في الجلب والترجمة والنشر. هذه التسمية لا تشير إلى صحافي فرد. معلومات الإدارة وسياسة التحرير والتواصل والتصحيحات متاحة في صفحة فريق التحرير.

جميع مقالات الكاتب ←