diff --git a/backend/app/api/api.py b/backend/app/api/api.py index 9a324e0..3d25739 100644 --- a/backend/app/api/api.py +++ b/backend/app/api/api.py @@ -1,8 +1,9 @@ from fastapi import APIRouter -from app.api.endpoints import auth, pprs, public +from app.api.endpoints import auth, pprs, public, aircraft api_router = APIRouter() api_router.include_router(auth.router, prefix="/auth", tags=["authentication"]) api_router.include_router(pprs.router, prefix="/pprs", tags=["pprs"]) -api_router.include_router(public.router, prefix="/public", tags=["public"]) \ No newline at end of file +api_router.include_router(public.router, prefix="/public", tags=["public"]) +api_router.include_router(aircraft.router, prefix="/aircraft", tags=["aircraft"]) \ No newline at end of file diff --git a/backend/app/api/endpoints/aircraft.py b/backend/app/api/endpoints/aircraft.py new file mode 100644 index 0000000..b6dba8b --- /dev/null +++ b/backend/app/api/endpoints/aircraft.py @@ -0,0 +1,60 @@ +from typing import List, Optional +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.orm import Session +from app.api.deps import get_db, get_current_active_user +from app.models.ppr import Aircraft +from app.schemas.ppr import Aircraft as AircraftSchema +from app.models.ppr import User + +router = APIRouter() + + +@router.get("/lookup/{registration}", response_model=List[AircraftSchema]) +async def lookup_aircraft_by_registration( + registration: str, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_active_user) +): + """ + Lookup aircraft by registration (clean match). + Removes non-alphanumeric characters from input for matching. + """ + # Clean the input registration (remove non-alphanumeric characters) + clean_input = ''.join(c for c in registration if c.isalnum()).upper() + + if len(clean_input) < 4: + return [] + + # Query aircraft table using clean_reg column + aircraft_list = db.query(Aircraft).filter( + Aircraft.clean_reg.like(f"{clean_input}%") + ).limit(10).all() + + return aircraft_list + + +@router.get("/search", response_model=List[AircraftSchema]) +async def search_aircraft( + q: Optional[str] = Query(None, description="Search query for registration, type, or manufacturer"), + limit: int = Query(10, ge=1, le=100, description="Maximum number of results"), + db: Session = Depends(get_db), + current_user: User = Depends(get_current_active_user) +): + """ + Search aircraft by registration, type code, or manufacturer name. + """ + if not q or len(q) < 2: + return [] + + # Clean search term + clean_query = ''.join(c for c in q if c.isalnum()).upper() + + # Search across multiple fields + aircraft_list = db.query(Aircraft).filter( + (Aircraft.clean_reg.like(f"%{clean_query}%")) | + (Aircraft.type_code.like(f"%{q.upper()}%")) | + (Aircraft.manufacturer_name.like(f"%{q}%")) | + (Aircraft.model.like(f"%{q}%")) + ).limit(limit).all() + + return aircraft_list \ No newline at end of file diff --git a/backend/app/schemas/ppr.py b/backend/app/schemas/ppr.py index 9a5b559..ad963f7 100644 --- a/backend/app/schemas/ppr.py +++ b/backend/app/schemas/ppr.py @@ -164,13 +164,15 @@ class Airport(AirportBase): class AircraftBase(BaseModel): icao24: Optional[str] = None registration: Optional[str] = None - manufacturericao: Optional[str] = None - typecode: Optional[str] = None - manufacturername: Optional[str] = None + manufacturer_icao: Optional[str] = None + type_code: Optional[str] = None + manufacturer_name: Optional[str] = None model: Optional[str] = None clean_reg: Optional[str] = None class Aircraft(AircraftBase): + id: int + class Config: from_attributes = True \ No newline at end of file diff --git a/web/admin.html b/web/admin.html index 7e621ef..0f2c089 100644 --- a/web/admin.html +++ b/web/admin.html @@ -41,16 +41,25 @@ padding: 2rem; } - .controls { - background: white; - padding: 1.5rem; - border-radius: 8px; + .top-menu { + background: #2c3e50; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; box-shadow: 0 2px 10px rgba(0,0,0,0.1); - margin-bottom: 2rem; + } + + .menu-left { + display: flex; + gap: 1rem; + align-items: center; + } + + .menu-right { display: flex; gap: 1rem; align-items: center; - flex-wrap: wrap; } .btn { @@ -421,14 +430,48 @@ margin-bottom: 1rem; } + /* Aircraft Lookup Styles */ + #aircraft-lookup-results { + margin-top: 0.5rem; + padding: 0.5rem; + background-color: #f8f9fa; + border-radius: 4px; + font-size: 0.9rem; + min-height: 20px; + border: 1px solid #e9ecef; + } + + .aircraft-match { + padding: 0.3rem; + background-color: #e8f5e8; + border: 1px solid #c3e6c3; + border-radius: 4px; + font-family: 'Courier New', monospace; + font-weight: bold; + } + + .aircraft-no-match { + color: #6c757d; + font-style: italic; + } + + .aircraft-searching { + color: #007bff; + } + @media (max-width: 768px) { .container { padding: 1rem; } - .controls { + .top-menu { flex-direction: column; - align-items: stretch; + gap: 1rem; + padding: 1rem; + } + + .menu-left, .menu-right { + justify-content: center; } .form-grid { @@ -452,28 +495,20 @@
-