BIG update: Update to 0.5.1. Added:
-Project management -Modem Managerment -Modem/unit pairing and more
This commit is contained in:
@@ -11,7 +11,7 @@ Provides API endpoints for the Projects system:
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException, Query
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, and_
|
||||
from sqlalchemy import func, and_, or_
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from collections import OrderedDict
|
||||
@@ -147,6 +147,107 @@ async def get_projects_stats(request: Request, db: Session = Depends(get_db)):
|
||||
})
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Project Search (Smart Autocomplete)
|
||||
# ============================================================================
|
||||
|
||||
def _build_project_display(project: Project) -> str:
|
||||
"""Build display string from project fields: 'xxxx-YY - Client - Name'"""
|
||||
parts = []
|
||||
if project.project_number:
|
||||
parts.append(project.project_number)
|
||||
if project.client_name:
|
||||
parts.append(project.client_name)
|
||||
if project.name:
|
||||
parts.append(project.name)
|
||||
return " - ".join(parts) if parts else project.id
|
||||
|
||||
|
||||
@router.get("/search", response_class=HTMLResponse)
|
||||
async def search_projects(
|
||||
request: Request,
|
||||
q: str = Query("", description="Search term"),
|
||||
db: Session = Depends(get_db),
|
||||
limit: int = Query(10, le=50),
|
||||
):
|
||||
"""
|
||||
Fuzzy search across project fields for autocomplete.
|
||||
Searches: project_number, client_name, name (project/site name)
|
||||
Returns HTML partial for HTMX dropdown.
|
||||
"""
|
||||
if not q.strip():
|
||||
# Return recent active projects when no search term
|
||||
projects = db.query(Project).filter(
|
||||
Project.status != "archived"
|
||||
).order_by(Project.updated_at.desc()).limit(limit).all()
|
||||
else:
|
||||
search_term = f"%{q}%"
|
||||
projects = db.query(Project).filter(
|
||||
and_(
|
||||
Project.status != "archived",
|
||||
or_(
|
||||
Project.project_number.ilike(search_term),
|
||||
Project.client_name.ilike(search_term),
|
||||
Project.name.ilike(search_term),
|
||||
)
|
||||
)
|
||||
).order_by(Project.updated_at.desc()).limit(limit).all()
|
||||
|
||||
# Build display data for each project
|
||||
projects_data = [{
|
||||
"id": p.id,
|
||||
"project_number": p.project_number,
|
||||
"client_name": p.client_name,
|
||||
"name": p.name,
|
||||
"display": _build_project_display(p),
|
||||
"status": p.status,
|
||||
} for p in projects]
|
||||
|
||||
return templates.TemplateResponse("partials/project_search_results.html", {
|
||||
"request": request,
|
||||
"projects": projects_data,
|
||||
"query": q,
|
||||
"show_create": len(projects) == 0 and q.strip(),
|
||||
})
|
||||
|
||||
|
||||
@router.get("/search-json")
|
||||
async def search_projects_json(
|
||||
q: str = Query("", description="Search term"),
|
||||
db: Session = Depends(get_db),
|
||||
limit: int = Query(10, le=50),
|
||||
):
|
||||
"""
|
||||
Fuzzy search across project fields - JSON response.
|
||||
For programmatic/API consumption.
|
||||
"""
|
||||
if not q.strip():
|
||||
projects = db.query(Project).filter(
|
||||
Project.status != "archived"
|
||||
).order_by(Project.updated_at.desc()).limit(limit).all()
|
||||
else:
|
||||
search_term = f"%{q}%"
|
||||
projects = db.query(Project).filter(
|
||||
and_(
|
||||
Project.status != "archived",
|
||||
or_(
|
||||
Project.project_number.ilike(search_term),
|
||||
Project.client_name.ilike(search_term),
|
||||
Project.name.ilike(search_term),
|
||||
)
|
||||
)
|
||||
).order_by(Project.updated_at.desc()).limit(limit).all()
|
||||
|
||||
return [{
|
||||
"id": p.id,
|
||||
"project_number": p.project_number,
|
||||
"client_name": p.client_name,
|
||||
"name": p.name,
|
||||
"display": _build_project_display(p),
|
||||
"status": p.status,
|
||||
} for p in projects]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Project CRUD
|
||||
# ============================================================================
|
||||
@@ -161,6 +262,7 @@ async def create_project(request: Request, db: Session = Depends(get_db)):
|
||||
|
||||
project = Project(
|
||||
id=str(uuid.uuid4()),
|
||||
project_number=form_data.get("project_number"), # TMI ID: xxxx-YY format
|
||||
name=form_data.get("name"),
|
||||
description=form_data.get("description"),
|
||||
project_type_id=form_data.get("project_type_id"),
|
||||
@@ -197,6 +299,7 @@ async def get_project(project_id: str, db: Session = Depends(get_db)):
|
||||
|
||||
return {
|
||||
"id": project.id,
|
||||
"project_number": project.project_number,
|
||||
"name": project.name,
|
||||
"description": project.description,
|
||||
"project_type_id": project.project_type_id,
|
||||
|
||||
Reference in New Issue
Block a user