Files
ppr-ng/web/admin.html
2025-10-21 20:23:58 +00:00

1078 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PPR Admin Interface</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
color: #333;
}
.header {
background: linear-gradient(135deg, #2c3e50, #3498db);
color: white;
padding: 1rem 2rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header h1 {
margin: 0;
font-size: 1.8rem;
}
.header .user-info {
float: right;
font-size: 0.9rem;
opacity: 0.9;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.controls {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 2rem;
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.btn {
padding: 0.7rem 1.5rem;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
}
.btn-success {
background-color: #27ae60;
color: white;
}
.btn-success:hover {
background-color: #229954;
}
.btn-warning {
background-color: #f39c12;
color: white;
}
.btn-warning:hover {
background-color: #e67e22;
}
.btn-danger {
background-color: #e74c3c;
color: white;
}
.btn-danger:hover {
background-color: #c0392b;
}
.btn-sm {
padding: 0.4rem 0.8rem;
font-size: 0.8rem;
}
.filter-group {
display: flex;
gap: 0.5rem;
align-items: center;
}
.filter-group label {
font-weight: 500;
color: #555;
}
.filter-group select, .filter-group input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.ppr-table {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.table-header {
background: #34495e;
color: white;
padding: 1rem;
font-weight: 500;
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 0.8rem;
text-align: left;
border-bottom: 1px solid #eee;
font-size: 0.9rem;
}
th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
position: sticky;
top: 0;
}
tbody tr {
cursor: pointer;
transition: background-color 0.2s ease;
}
tbody tr:hover {
background-color: #f8f9fa;
}
.status {
display: inline-block;
padding: 0.3rem 0.6rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
}
.status.new { background: #e3f2fd; color: #1565c0; }
.status.confirmed { background: #e8f5e8; color: #2e7d32; }
.status.landed { background: #fff3e0; color: #ef6c00; }
.status.departed { background: #fce4ec; color: #c2185b; }
.status.canceled { background: #ffebee; color: #d32f2f; }
.status.deleted { background: #f3e5f5; color: #7b1fa2; }
.no-data {
text-align: center;
padding: 3rem;
color: #666;
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: white;
margin: 5% auto;
padding: 0;
border-radius: 8px;
width: 90%;
max-width: 800px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.modal-header {
background: #34495e;
color: white;
padding: 1rem 1.5rem;
border-radius: 8px 8px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
margin: 0;
font-size: 1.3rem;
}
.close {
color: white;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
border: none;
background: none;
}
.close:hover {
opacity: 0.7;
}
.modal-body {
padding: 1.5rem;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group.full-width {
grid-column: 1 / -1;
}
.form-group label {
font-weight: 600;
margin-bottom: 0.3rem;
color: #555;
}
.form-group input, .form-group select, .form-group textarea {
padding: 0.6rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
}
.form-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.journal-section {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.journal-entries {
max-height: 200px;
overflow-y: auto;
border: 1px solid #eee;
border-radius: 4px;
padding: 1rem;
background-color: #f9f9f9;
}
.journal-entry {
margin-bottom: 0.8rem;
padding-bottom: 0.8rem;
border-bottom: 1px solid #ddd;
}
.journal-entry:last-child {
border-bottom: none;
margin-bottom: 0;
}
.journal-meta {
font-size: 0.8rem;
color: #666;
margin-bottom: 0.3rem;
}
.journal-text {
font-size: 0.9rem;
color: #333;
}
.quick-actions {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
}
.controls {
flex-direction: column;
align-items: stretch;
}
.form-grid {
grid-template-columns: 1fr;
}
.modal-content {
width: 95%;
margin: 2% auto;
}
}
</style>
</head>
<body>
<div class="header">
<h1>✈️ PPR Administration</h1>
<div class="user-info">
Logged in as: <span id="current-user">Loading...</span> |
<a href="#" onclick="logout()" style="color: white;">Logout</a>
</div>
<div style="clear: both;"></div>
</div>
<div class="container">
<div class="controls">
<button class="btn btn-success" onclick="openNewPPRModal()">
New PPR Entry
</button>
<div class="filter-group">
<label for="viewDate">Date:</label>
<input type="date" id="viewDate" onchange="loadPPRs()">
</div>
<button class="btn btn-primary" onclick="loadPPRs()">
🔄 Refresh
</button>
<div class="filter-group" style="margin-left: auto;">
<label>
<input type="checkbox" id="showAllStatuses" onchange="loadPPRs()">
Show all statuses (including departed/canceled)
</label>
</div>
</div>
<!-- Arrivals Table -->
<div class="ppr-table">
<div class="table-header">
🛬 Today's Arrivals - <span id="arrivals-count">0</span> entries (NEW & CONFIRMED)
</div>
<div id="arrivals-loading" class="loading">
<div class="spinner"></div>
Loading arrivals...
</div>
<div id="arrivals-table-content" style="display: none;">
<table>
<thead>
<tr>
<th>Registration</th>
<th>Type</th>
<th>Captain</th>
<th>From</th>
<th>ETA Time</th>
<th>POB</th>
<th>Fuel</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="arrivals-table-body">
</tbody>
</table>
</div>
<div id="arrivals-no-data" class="no-data" style="display: none;">
<h3>No arrivals for today</h3>
<p>No NEW or CONFIRMED arrivals scheduled for today.</p>
</div>
</div>
<!-- Departures Table -->
<div class="ppr-table" style="margin-top: 2rem;">
<div class="table-header">
🛫 Today's Departures - <span id="departures-count">0</span> entries (LANDED)
</div>
<div id="departures-loading" class="loading">
<div class="spinner"></div>
Loading departures...
</div>
<div id="departures-table-content" style="display: none;">
<table>
<thead>
<tr>
<th>Registration</th>
<th>Type</th>
<th>Captain</th>
<th>To</th>
<th>ETD Time</th>
<th>POB</th>
<th>Fuel</th>
<th>Landed</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="departures-table-body">
</tbody>
</table>
</div>
<div id="departures-no-data" class="no-data" style="display: none;">
<h3>No departures for today</h3>
<p>No aircraft currently landed and ready to depart.</p>
</div>
</div>
</div>
<!-- PPR Detail/Edit Modal -->
<div id="pprModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modal-title">PPR Details</h2>
<button class="close" onclick="closePPRModal()">&times;</button>
</div>
<div class="modal-body">
<div class="quick-actions">
<button id="btn-confirm" class="btn btn-success btn-sm" onclick="updateStatus('CONFIRMED')">
✓ Confirm
</button>
<button id="btn-landed" class="btn btn-warning btn-sm" onclick="updateStatus('LANDED')">
🛬 Landed
</button>
<button id="btn-departed" class="btn btn-primary btn-sm" onclick="updateStatus('DEPARTED')">
🛫 Departed
</button>
<button id="btn-cancel" class="btn btn-danger btn-sm" onclick="updateStatus('CANCELED')">
❌ Cancel
</button>
</div>
<form id="ppr-form">
<input type="hidden" id="ppr-id" name="id">
<div class="form-grid">
<div class="form-group">
<label for="ac_reg">Aircraft Registration *</label>
<input type="text" id="ac_reg" name="ac_reg" required>
</div>
<div class="form-group">
<label for="ac_type">Aircraft Type *</label>
<input type="text" id="ac_type" name="ac_type" required>
</div>
<div class="form-group">
<label for="ac_call">Callsign</label>
<input type="text" id="ac_call" name="ac_call">
</div>
<div class="form-group">
<label for="captain">Captain *</label>
<input type="text" id="captain" name="captain" required>
</div>
<div class="form-group">
<label for="in_from">Arriving From *</label>
<input type="text" id="in_from" name="in_from" required placeholder="ICAO Code">
</div>
<div class="form-group">
<label for="eta">ETA *</label>
<input type="datetime-local" id="eta" name="eta" required>
</div>
<div class="form-group">
<label for="pob_in">POB Inbound *</label>
<input type="number" id="pob_in" name="pob_in" required min="1">
</div>
<div class="form-group">
<label for="fuel">Fuel Required</label>
<select id="fuel" name="fuel">
<option value="">None</option>
<option value="100LL">100LL</option>
<option value="JET A1">JET A1</option>
<option value="FULL">Full Tanks</option>
</select>
</div>
<div class="form-group">
<label for="out_to">Departing To</label>
<input type="text" id="out_to" name="out_to" placeholder="ICAO Code">
</div>
<div class="form-group">
<label for="etd">ETD</label>
<input type="datetime-local" id="etd" name="etd">
</div>
<div class="form-group">
<label for="pob_out">POB Outbound</label>
<input type="number" id="pob_out" name="pob_out" min="1">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email">
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone">
</div>
<div class="form-group full-width">
<label for="notes">Notes</label>
<textarea id="notes" name="notes" rows="3"></textarea>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-danger" id="delete-btn" onclick="deletePPR()" style="display: none;">
🗑️ Delete
</button>
<button type="button" class="btn btn-primary" onclick="closePPRModal()">
Cancel
</button>
<button type="submit" class="btn btn-success">
💾 Save Changes
</button>
</div>
</form>
<div class="journal-section" id="journal-section">
<h3>Activity Journal</h3>
<div id="journal-entries" class="journal-entries">
Loading journal...
</div>
</div>
</div>
</div>
</div>
<script>
let currentUser = null;
let accessToken = null;
let currentPPRId = null;
let isNewPPR = false;
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
authenticate();
});
// Authentication
function authenticate() {
const username = prompt('Username:');
const password = prompt('Password:');
if (!username || !password) {
alert('Authentication required');
return;
}
fetch('/api/v1/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`
})
.then(response => response.json())
.then(data => {
if (data.access_token) {
accessToken = data.access_token;
currentUser = username;
document.getElementById('current-user').textContent = username;
loadPPRs();
setDefaultDates();
} else {
alert('Authentication failed');
authenticate();
}
})
.catch(error => {
console.error('Auth error:', error);
alert('Authentication failed');
authenticate();
});
}
function logout() {
accessToken = null;
currentUser = null;
location.reload();
}
function setDefaultDates() {
const today = new Date();
document.getElementById('viewDate').value = today.toISOString().split('T')[0];
}
// Load PPR records - now loads both arrivals and departures
async function loadPPRs() {
if (!accessToken) return;
// Load both arrivals and departures simultaneously
await Promise.all([loadArrivals(), loadDepartures()]);
}
// Load arrivals (NEW and CONFIRMED status)
async function loadArrivals() {
document.getElementById('arrivals-loading').style.display = 'block';
document.getElementById('arrivals-table-content').style.display = 'none';
document.getElementById('arrivals-no-data').style.display = 'none';
try {
const viewDate = document.getElementById('viewDate').value;
const showAll = document.getElementById('showAllStatuses').checked;
let url = '/api/v1/pprs/?limit=1000';
if (viewDate) {
url += `&date_from=${viewDate}&date_to=${viewDate}`;
}
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch arrivals');
}
const allPPRs = await response.json();
// Filter for arrivals (NEW, CONFIRMED, or all if showAll is checked)
let arrivals;
if (showAll) {
// Show all PPRs with ETA for the selected date
arrivals = allPPRs.filter(ppr => ppr.eta);
} else {
// Show only NEW and CONFIRMED with ETA
arrivals = allPPRs.filter(ppr =>
(ppr.status === 'NEW' || ppr.status === 'CONFIRMED') && ppr.eta
);
}
displayArrivals(arrivals);
} catch (error) {
console.error('Error loading arrivals:', error);
alert('Error loading arrivals');
}
document.getElementById('arrivals-loading').style.display = 'none';
}
// Load departures (LANDED status)
async function loadDepartures() {
document.getElementById('departures-loading').style.display = 'block';
document.getElementById('departures-table-content').style.display = 'none';
document.getElementById('departures-no-data').style.display = 'none';
try {
const viewDate = document.getElementById('viewDate').value;
const showAll = document.getElementById('showAllStatuses').checked;
let url = '/api/v1/pprs/?limit=1000';
if (viewDate) {
url += `&date_from=${viewDate}&date_to=${viewDate}`;
}
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch departures');
}
const allPPRs = await response.json();
// Filter for departures (LANDED, or include DEPARTED if showAll is checked)
let departures;
if (showAll) {
departures = allPPRs.filter(ppr =>
ppr.status === 'LANDED' || ppr.status === 'DEPARTED'
);
} else {
departures = allPPRs.filter(ppr => ppr.status === 'LANDED');
}
displayDepartures(departures);
} catch (error) {
console.error('Error loading departures:', error);
alert('Error loading departures');
}
document.getElementById('departures-loading').style.display = 'none';
}
function displayArrivals(arrivals) {
const tbody = document.getElementById('arrivals-table-body');
const recordCount = document.getElementById('arrivals-count');
recordCount.textContent = arrivals.length;
if (arrivals.length === 0) {
document.getElementById('arrivals-no-data').style.display = 'block';
return;
}
tbody.innerHTML = '';
document.getElementById('arrivals-table-content').style.display = 'block';
arrivals.forEach(ppr => {
const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id);
row.innerHTML = `
<td><strong>${ppr.ac_reg}</strong></td>
<td>${ppr.ac_type}</td>
<td>${ppr.captain}</td>
<td>${ppr.in_from}</td>
<td>${formatTimeOnly(ppr.eta)}</td>
<td>${ppr.pob_in}</td>
<td>${ppr.fuel || '-'}</td>
<td><span class="status ${ppr.status.toLowerCase()}">${ppr.status}</span></td>
<td>
<button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); openPPRModal(${ppr.id})">
Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
function displayDepartures(departures) {
const tbody = document.getElementById('departures-table-body');
const recordCount = document.getElementById('departures-count');
recordCount.textContent = departures.length;
if (departures.length === 0) {
document.getElementById('departures-no-data').style.display = 'block';
return;
}
tbody.innerHTML = '';
document.getElementById('departures-table-content').style.display = 'block';
departures.forEach(ppr => {
const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id);
row.innerHTML = `
<td><strong>${ppr.ac_reg}</strong></td>
<td>${ppr.ac_type}</td>
<td>${ppr.captain}</td>
<td>${ppr.out_to || '-'}</td>
<td>${ppr.etd ? formatTimeOnly(ppr.etd) : '-'}</td>
<td>${ppr.pob_out || ppr.pob_in}</td>
<td>${ppr.fuel || '-'}</td>
<td>${ppr.landed_dt ? formatTimeOnly(ppr.landed_dt) : '-'}</td>
<td>
<button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); openPPRModal(${ppr.id})">
Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
function formatTimeOnly(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
}
function formatDateTime(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
}
// Modal functions
function openNewPPRModal() {
isNewPPR = true;
currentPPRId = null;
document.getElementById('modal-title').textContent = 'New PPR Entry';
document.getElementById('delete-btn').style.display = 'none';
document.getElementById('journal-section').style.display = 'none';
document.querySelector('.quick-actions').style.display = 'none';
// Clear form
document.getElementById('ppr-form').reset();
document.getElementById('ppr-id').value = '';
document.getElementById('pprModal').style.display = 'block';
}
async function openPPRModal(pprId) {
if (!accessToken) return;
isNewPPR = false;
currentPPRId = pprId;
document.getElementById('modal-title').textContent = 'Edit PPR Entry';
document.getElementById('delete-btn').style.display = 'inline-block';
document.querySelector('.quick-actions').style.display = 'flex';
try {
const response = await fetch(`/api/v1/pprs/${pprId}`, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch PPR details');
}
const ppr = await response.json();
populateForm(ppr);
await loadJournal(pprId); // Always load journal when opening a PPR
document.getElementById('pprModal').style.display = 'block';
} catch (error) {
console.error('Error loading PPR details:', error);
alert('Error loading PPR details');
}
}
function populateForm(ppr) {
Object.keys(ppr).forEach(key => {
const field = document.getElementById(key);
if (field) {
if (key === 'eta' || key === 'etd') {
if (ppr[key]) {
field.value = new Date(ppr[key]).toISOString().slice(0, 16);
}
} else {
field.value = ppr[key] || '';
}
}
});
}
async function loadJournal(pprId) {
try {
const response = await fetch(`/api/v1/pprs/${pprId}/journal`, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch journal');
}
const entries = await response.json();
displayJournal(entries);
} catch (error) {
console.error('Error loading journal:', error);
document.getElementById('journal-entries').innerHTML = 'Error loading journal entries';
}
}
function displayJournal(entries) {
const container = document.getElementById('journal-entries');
if (entries.length === 0) {
container.innerHTML = '<p>No journal entries yet.</p>';
} else {
container.innerHTML = entries.map(entry => `
<div class="journal-entry">
<div class="journal-meta">
${formatDateTime(entry.entry_dt)} by ${entry.user}
</div>
<div class="journal-text">${entry.entry}</div>
</div>
`).join('');
}
// Always show journal section when displaying entries
document.getElementById('journal-section').style.display = 'block';
}
function closePPRModal() {
document.getElementById('pprModal').style.display = 'none';
currentPPRId = null;
isNewPPR = false;
}
// Form submission
document.getElementById('ppr-form').addEventListener('submit', async function(e) {
e.preventDefault();
if (!accessToken) return;
const formData = new FormData(this);
const pprData = {};
formData.forEach((value, key) => {
if (key !== 'id' && value.trim() !== '') {
if (key === 'pob_in' || key === 'pob_out') {
pprData[key] = parseInt(value);
} else {
pprData[key] = value;
}
}
});
try {
let response;
if (isNewPPR) {
response = await fetch('/api/v1/pprs/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify(pprData)
});
} else {
response = await fetch(`/api/v1/pprs/${currentPPRId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify(pprData)
});
}
if (!response.ok) {
throw new Error('Failed to save PPR');
}
closePPRModal();
loadPPRs(); // Refresh both tables
alert(isNewPPR ? 'PPR created successfully!' : 'PPR updated successfully!');
} catch (error) {
console.error('Error saving PPR:', error);
alert('Error saving PPR');
}
});
// Status update functions
async function updateStatus(status) {
if (!currentPPRId || !accessToken) return;
try {
const response = await fetch(`/api/v1/pprs/${currentPPRId}/status`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({ status: status })
});
if (!response.ok) {
throw new Error('Failed to update status');
}
await loadJournal(currentPPRId); // Refresh journal
loadPPRs(); // Refresh both tables
alert(`Status updated to ${status}`);
} catch (error) {
console.error('Error updating status:', error);
alert('Error updating status');
}
}
async function deletePPR() {
if (!currentPPRId || !accessToken) return;
if (!confirm('Are you sure you want to delete this PPR? This action cannot be undone.')) {
return;
}
try {
const response = await fetch(`/api/v1/pprs/${currentPPRId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to delete PPR');
}
closePPRModal();
loadPPRs(); // Refresh both tables
alert('PPR deleted successfully!');
} catch (error) {
console.error('Error deleting PPR:', error);
alert('Error deleting PPR');
}
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('pprModal');
if (event.target === modal) {
closePPRModal();
}
}
</script>
</body>
</html>