List and edit user aircraft

This commit is contained in:
2026-03-23 13:09:49 -04:00
parent d2c9bc0370
commit fd0e521186
3 changed files with 338 additions and 7 deletions
+65 -2
View File
@@ -1,9 +1,10 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import func
from app.api.deps import get_db, get_current_active_user
from app.models.ppr import Aircraft, UserAircraft
from app.schemas.ppr import Aircraft as AircraftSchema, UserAircraftCreate
from app.schemas.ppr import Aircraft as AircraftSchema, UserAircraftCreate, UserAircraft as UserAircraftSchema
from app.models.ppr import User
router = APIRouter()
@@ -161,4 +162,66 @@ async def save_user_aircraft(
db.commit()
db.refresh(user_aircraft)
return {"message": "Aircraft saved successfully", "id": user_aircraft.id}
return {"message": "Aircraft saved successfully", "id": user_aircraft.id}
@router.get("/user-aircraft", response_model=List[UserAircraftSchema])
async def get_user_aircraft(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Get all user-defined aircraft types.
"""
user_aircraft = db.query(UserAircraft).order_by(UserAircraft.created_at.desc()).all()
return user_aircraft
@router.put("/user-aircraft/{registration}", response_model=dict)
async def update_user_aircraft(
registration: str,
aircraft: UserAircraftCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Update a user-defined aircraft type.
"""
# Find the existing user aircraft
existing = db.query(UserAircraft).filter(
UserAircraft.registration == registration.upper()
).first()
if not existing:
raise HTTPException(status_code=404, detail="User aircraft not found")
# Update the type
existing.type_code = aircraft.type_code.upper()
existing.updated_at = func.current_timestamp()
db.commit()
return {"message": "Aircraft updated successfully"}
@router.delete("/user-aircraft/{registration}", response_model=dict)
async def delete_user_aircraft(
registration: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Delete a user-defined aircraft type.
"""
# Find the existing user aircraft
existing = db.query(UserAircraft).filter(
UserAircraft.registration == registration.upper()
).first()
if not existing:
raise HTTPException(status_code=404, detail="User aircraft not found")
db.delete(existing)
db.commit()
return {"message": "Aircraft deleted successfully"}
+1 -1
View File
@@ -50,7 +50,7 @@ services:
cpus: '1'
memory: 1G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 10s
retries: 3
+272 -4
View File
@@ -30,6 +30,7 @@
</button>
<div class="dropdown-menu" id="adminDropdownMenu">
<a href="#" onclick="window.location.href = '/reports'">📊 Reports</a>
<a href="#" onclick="openUserAircraftModal(); closeAdminDropdown()" id="user-aircraft-dropdown">✈️ User Aircraft</a>
<a href="#" onclick="openUserManagementModal(); closeAdminDropdown()" id="user-management-dropdown" style="display: none;">👥 User Management</a>
</div>
</div>
@@ -1048,6 +1049,86 @@
<!-- Success Notification -->
<div id="notification" class="notification"></div>
<!-- User Aircraft Management Modal -->
<div id="userAircraftModal" class="modal">
<div class="modal-content" style="max-width: 1000px;">
<div class="modal-header">
<h2>User Aircraft Management</h2>
<button class="close" onclick="closeModal('userAircraftModal')">&times;</button>
</div>
<div class="modal-body">
<div class="quick-actions" style="margin-bottom: 1rem;">
<div class="form-group" style="display: inline-block; margin-right: 1rem;">
<label for="user-aircraft-search" style="display: inline; margin-right: 0.5rem;">Search:</label>
<input type="text" id="user-aircraft-search" placeholder="Filter by registration or type..." style="width: 250px;">
</div>
<button class="btn btn-info" onclick="loadUserAircraft()">
🔄 Refresh
</button>
</div>
<div id="user-aircraft-loading" class="loading">
<div class="spinner"></div>
Loading user aircraft...
</div>
<div id="user-aircraft-table-content" style="display: none;">
<table>
<thead>
<tr>
<th>Registration</th>
<th>Type</th>
<th>Added By</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="user-aircraft-table-body">
</tbody>
</table>
</div>
<div id="user-aircraft-no-data" class="no-data" style="display: none;">
<h3>No user aircraft found</h3>
<p>No custom aircraft types have been saved yet.</p>
</div>
</div>
</div>
</div>
<!-- User Aircraft Edit Modal -->
<div id="userAircraftEditModal" class="modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h2 id="user-aircraft-edit-title">Edit User Aircraft</h2>
<button class="close" onclick="closeModal('userAircraftEditModal')">&times;</button>
</div>
<div class="modal-body">
<form id="user-aircraft-edit-form">
<div class="form-group full-width">
<label for="edit-aircraft-registration">Registration *</label>
<input type="text" id="edit-aircraft-registration" name="registration" required readonly style="background-color: #f5f5f5;">
</div>
<div class="form-group full-width">
<label for="edit-aircraft-type">Aircraft Type *</label>
<input type="text" id="edit-aircraft-type" name="type_code" required>
</div>
<div class="form-actions">
<button type="button" class="btn btn-info" onclick="closeModal('userAircraftEditModal')">
Cancel
</button>
<button type="submit" class="btn btn-warning">
💾 Save Changes
</button>
<button type="button" class="btn btn-danger" onclick="deleteUserAircraft()">
🗑️ Delete
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Timestamp Modal for Landing/Departure -->
<div id="timestampModal" class="modal">
<div class="modal-content" style="max-width: 400px;">
@@ -3468,6 +3549,182 @@
}
});
// ==================== USER AIRCRAFT MANAGEMENT ====================
async function openUserAircraftModal() {
if (!accessToken) return;
document.getElementById('userAircraftModal').style.display = 'block';
await loadUserAircraft();
// Set up search functionality
const searchInput = document.getElementById('user-aircraft-search');
searchInput.addEventListener('input', filterUserAircraft);
}
async function loadUserAircraft() {
if (!accessToken) return;
document.getElementById('user-aircraft-loading').style.display = 'block';
document.getElementById('user-aircraft-table-content').style.display = 'none';
document.getElementById('user-aircraft-no-data').style.display = 'none';
try {
const response = await authenticatedFetch('/api/v1/aircraft/user-aircraft');
if (!response.ok) {
throw new Error('Failed to fetch user aircraft');
}
const userAircraft = await response.json();
displayUserAircraft(userAircraft);
} catch (error) {
console.error('Error loading user aircraft:', error);
if (error.message !== 'Session expired. Please log in again.') {
showNotification('Error loading user aircraft', true);
}
}
document.getElementById('user-aircraft-loading').style.display = 'none';
}
function displayUserAircraft(userAircraft) {
const tbody = document.getElementById('user-aircraft-table-body');
const searchInput = document.getElementById('user-aircraft-search');
// Store original data for filtering
tbody._originalData = userAircraft;
if (userAircraft.length === 0) {
document.getElementById('user-aircraft-no-data').style.display = 'block';
return;
}
// Apply current filter if any
const filterText = searchInput.value.trim().toLowerCase();
const filteredAircraft = filterText ?
userAircraft.filter(ua =>
ua.registration.toLowerCase().includes(filterText) ||
ua.type_code.toLowerCase().includes(filterText) ||
ua.created_by.toLowerCase().includes(filterText)
) : userAircraft;
if (filteredAircraft.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 2rem;">No aircraft match the search criteria</td></tr>';
} else {
tbody.innerHTML = '';
filteredAircraft.forEach(aircraft => {
const row = document.createElement('tr');
// Format created date
const createdDate = aircraft.created_at ? formatDateTime(aircraft.created_at) : '-';
row.innerHTML = `
<td><strong>${aircraft.registration}</strong></td>
<td>${aircraft.type_code}</td>
<td>${aircraft.created_by}</td>
<td>${createdDate}</td>
<td>
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); openUserAircraftEditModal('${aircraft.registration}', '${aircraft.type_code}')" title="Edit Aircraft">
✏️
</button>
</td>
`;
tbody.appendChild(row);
});
}
document.getElementById('user-aircraft-table-content').style.display = 'block';
}
function filterUserAircraft() {
const tbody = document.getElementById('user-aircraft-table-body');
if (tbody._originalData) {
displayUserAircraft(tbody._originalData);
}
}
function openUserAircraftEditModal(registration, typeCode) {
document.getElementById('edit-aircraft-registration').value = registration;
document.getElementById('edit-aircraft-type').value = typeCode;
document.getElementById('user-aircraft-edit-title').textContent = `Edit ${registration}`;
document.getElementById('userAircraftEditModal').style.display = 'block';
// Auto-focus on type field
setTimeout(() => {
document.getElementById('edit-aircraft-type').focus();
}, 100);
}
// User aircraft edit form submission
document.getElementById('user-aircraft-edit-form').addEventListener('submit', async function(e) {
e.preventDefault();
if (!accessToken) return;
const registration = document.getElementById('edit-aircraft-registration').value.trim();
const typeCode = document.getElementById('edit-aircraft-type').value.trim();
if (!registration || !typeCode) {
showNotification('Registration and type are required', true);
return;
}
try {
const response = await authenticatedFetch(`/api/v1/aircraft/user-aircraft/${registration}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
registration: registration,
type_code: typeCode
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || 'Failed to update aircraft');
}
closeModal('userAircraftEditModal');
await loadUserAircraft(); // Refresh list
showNotification('Aircraft updated successfully!');
} catch (error) {
console.error('Error updating aircraft:', error);
showNotification(`Error updating aircraft: ${error.message}`, true);
}
});
async function deleteUserAircraft() {
if (!accessToken) return;
const registration = document.getElementById('edit-aircraft-registration').value.trim();
if (!confirm(`Are you sure you want to delete the aircraft entry for ${registration}? This action cannot be undone.`)) {
return;
}
try {
const response = await authenticatedFetch(`/api/v1/aircraft/user-aircraft/${registration}`, {
method: 'DELETE'
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || 'Failed to delete aircraft');
}
closeModal('userAircraftEditModal');
await loadUserAircraft(); // Refresh list
showNotification('Aircraft deleted successfully!');
} catch (error) {
console.error('Error deleting aircraft:', error);
showNotification(`Error deleting aircraft: ${error.message}`, true);
}
}
// Update user role detection and UI visibility
async function updateUserRole() {
if (!accessToken) {
@@ -3485,16 +3742,27 @@
// Show user management in dropdown only for administrators
const userManagementDropdown = document.getElementById('user-management-dropdown');
// Show user aircraft for operators and administrators
const userAircraftDropdown = document.getElementById('user-aircraft-dropdown');
if (currentUserRole && currentUserRole.toUpperCase() === 'ADMINISTRATOR') {
userManagementDropdown.style.display = 'block';
if (userManagementDropdown) userManagementDropdown.style.display = 'block';
if (userAircraftDropdown) userAircraftDropdown.style.display = 'block';
} else if (currentUserRole && currentUserRole.toUpperCase() === 'OPERATOR') {
if (userManagementDropdown) userManagementDropdown.style.display = 'none';
if (userAircraftDropdown) userAircraftDropdown.style.display = 'block';
} else {
userManagementDropdown.style.display = 'none';
if (userManagementDropdown) userManagementDropdown.style.display = 'none';
if (userAircraftDropdown) userAircraftDropdown.style.display = 'none';
}
}
} catch (error) {
console.error('Error updating user role:', error);
// Hide user management by default on error
document.getElementById('user-management-dropdown').style.display = 'none';
// Hide admin features by default on error
const userManagementDropdown = document.getElementById('user-management-dropdown');
const userAircraftDropdown = document.getElementById('user-aircraft-dropdown');
if (userManagementDropdown) userManagementDropdown.style.display = 'none';
if (userAircraftDropdown) userAircraftDropdown.style.display = 'none';
}
}