v0.1.19 - Throughput overview & responsive header
- Throughput: new "Throughput Overview" header with Gauge icon and brand-green badge icons on each card (Today, This week, 4-week average, Horse Mix, Grain Mix) - Throughput: inline rolling-range selector (7d / 4w / 6w / 12w, default 4 weeks) driving the customer-mix cards; stats window widened to 12 weeks so switching range is a pure client-side re-filter - Throughput: cards collapse to a single even 5-across row on laptop and up, with container-query value text that scales to each card's width - Throughput: date logic pinned to Australian Eastern time (fixes the day-early date); This week subtitle shows the Mon-Sun date range - Throughput: subtler tinted add-form; removed the inline-entry kicker and the "Open full form" link - Topbar: fix cramped laptop header - action toggles no longer wrap above the user button; search drops to its own row earlier Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -13,16 +13,21 @@ from app.schemas.throughput import (
|
||||
ThroughputEntryCreate,
|
||||
ThroughputEntryRead,
|
||||
ThroughputEntryUpdate,
|
||||
ThroughputImportResult,
|
||||
ThroughputProductCreate,
|
||||
ThroughputProductRead,
|
||||
ThroughputProductUpdate,
|
||||
)
|
||||
from app.services.throughput_service import (
|
||||
calculate_kg,
|
||||
import_entries_from_file,
|
||||
normalise_staff_name,
|
||||
serialize_entry,
|
||||
)
|
||||
|
||||
# Uploaded files larger than this are rejected before we read them into memory.
|
||||
_MAX_IMPORT_BYTES = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
router = APIRouter(prefix="/api/throughput", tags=["operations-throughput"])
|
||||
|
||||
MODULE_KEY = "operations_throughput"
|
||||
@@ -184,6 +189,33 @@ def create_entry(
|
||||
return serialize_entry(entry)
|
||||
|
||||
|
||||
@router.post("/import", response_model=ThroughputImportResult)
|
||||
def import_entries(
|
||||
file: UploadFile = File(...),
|
||||
session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
content = file.file.read()
|
||||
if not content:
|
||||
raise HTTPException(status_code=400, detail="The uploaded file is empty.")
|
||||
if len(content) > _MAX_IMPORT_BYTES:
|
||||
raise HTTPException(status_code=413, detail="File is too large. Keep uploads under 10 MB.")
|
||||
|
||||
try:
|
||||
result = import_entries_from_file(
|
||||
db,
|
||||
filename=file.filename or "upload.csv",
|
||||
content=content,
|
||||
tenant_id=session.tenant_id,
|
||||
created_by=session.email,
|
||||
)
|
||||
except ValueError as exc:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/entries/{entry_id}", response_model=ThroughputEntryRead)
|
||||
def get_entry(
|
||||
entry_id: int,
|
||||
@@ -233,7 +265,10 @@ def update_entry(
|
||||
@router.delete("/entries/{entry_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_entry(
|
||||
entry_id: int,
|
||||
session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "manage")),
|
||||
# Correcting a mistaken run is part of day-to-day operating, so deleting an
|
||||
# entry sits at the same "edit" level as adding/editing one. (No throughput
|
||||
# role is granted "manage", so requiring it here would 403 everyone.)
|
||||
session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
entry = db.scalar(
|
||||
|
||||
Reference in New Issue
Block a user