2026-04-15-4
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Map Refinement (Place Audit) logic
|
||||
*/
|
||||
|
||||
const refinement = {
|
||||
state: {
|
||||
candidates: []
|
||||
},
|
||||
|
||||
init: async () => {
|
||||
console.log('📍 Initializing Map Refinement Logic...');
|
||||
await refinement.fetchCandidates();
|
||||
},
|
||||
|
||||
fetchCandidates: async () => {
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
const res = await fetch('/api/map-refinement/candidates?status=PENDING', { headers });
|
||||
if (res.ok) {
|
||||
refinement.state.candidates = await res.json();
|
||||
refinement.renderTable();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch candidates', error);
|
||||
}
|
||||
},
|
||||
|
||||
renderTable: () => {
|
||||
const tbody = document.getElementById('refinement-table-body');
|
||||
if (!tbody) return;
|
||||
|
||||
if (refinement.state.candidates.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="py-20 text-center text-slate-500">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<i data-lucide="map-pin" class="w-8 h-8 opacity-20"></i>
|
||||
<p>No pending location suggestions.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = refinement.state.candidates.map(c => `
|
||||
<tr class="hover:bg-white/[0.02] transition-colors border-b border-white/[0.03]">
|
||||
<td class="px-8 py-6">
|
||||
<div class="font-bold text-white">${c.name_ar || c.name}</div>
|
||||
<div class="text-[10px] text-slate-500 uppercase font-bold mt-1">${c.country}</div>
|
||||
</td>
|
||||
<td class="px-8 py-6">
|
||||
<span class="px-2 py-1 rounded-lg bg-blue-500/10 text-blue-400 text-[10px] font-black uppercase tracking-wider">${c.category || 'General'}</span>
|
||||
</td>
|
||||
<td class="px-8 py-6 font-mono text-xs text-slate-400">
|
||||
${parseFloat(c.latitude).toFixed(5)}, ${parseFloat(c.longitude).toFixed(5)}
|
||||
</td>
|
||||
<td class="px-8 py-6 text-sm text-slate-400 font-medium">
|
||||
${c.submittedBy || 'System User'}
|
||||
</td>
|
||||
<td class="px-8 py-6 text-right space-x-2">
|
||||
<button onclick="refinement.reject('${c.id}')" class="p-2.5 rounded-xl bg-red-500/10 text-red-400 hover:bg-red-500/20 transition-all" title="Reject">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onclick="refinement.approve('${c.id}')" class="p-2.5 rounded-xl bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20 transition-all border border-emerald-500/20 shadow-lg shadow-emerald-500/10" title="Approve">
|
||||
<i data-lucide="check" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
lucide.createIcons();
|
||||
},
|
||||
|
||||
approve: async (id) => {
|
||||
if (!confirm('Are you sure you want to approve this location and add it to the production map?')) return;
|
||||
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
const res = await fetch(`/api/map-refinement/candidates/${id}/approve`, {
|
||||
method: 'PATCH',
|
||||
headers
|
||||
});
|
||||
if (res.ok) {
|
||||
await refinement.fetchCandidates();
|
||||
} else {
|
||||
alert('Approval failed.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
|
||||
reject: async (id) => {
|
||||
const reason = prompt('Please enter a reason for rejection:');
|
||||
if (reason === null) return;
|
||||
|
||||
const headers = auth.getAuthHeader();
|
||||
try {
|
||||
const res = await fetch(`/api/map-refinement/candidates/${id}/reject`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ reason })
|
||||
});
|
||||
if (res.ok) {
|
||||
await refinement.fetchCandidates();
|
||||
} else {
|
||||
alert('Rejection failed.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user