diff --git a/backend/app/main.py b/backend/app/main.py index 98c9913..cb9df90 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -5,7 +5,7 @@ from fastapi.middleware.cors import CORSMiddleware from app import ssl_heidi from pathlib import Path -from app.routers import address, contact, proposal, dewar, shipment, puck, spreadsheet, logistics, auth +from app.routers import address, contact, proposal, dewar, shipment, puck, spreadsheet, logistics, auth, sample from app.database import Base, engine, SessionLocal, load_sample_data app = FastAPI() @@ -48,6 +48,7 @@ app.include_router(shipment.router, prefix="/shipments", tags=["shipments"]) app.include_router(puck.router, prefix="/pucks", tags=["pucks"]) app.include_router(spreadsheet.router, tags=["spreadsheet"]) app.include_router(logistics.router, prefix="/logistics", tags=["logistics"]) +app.include_router(sample.router, prefix="/samples", tags=["samples"]) if __name__ == "__main__": diff --git a/backend/app/routers/sample.py b/backend/app/routers/sample.py new file mode 100644 index 0000000..31786ae --- /dev/null +++ b/backend/app/routers/sample.py @@ -0,0 +1,36 @@ +from fastapi import APIRouter, HTTPException, status, Depends +from sqlalchemy.orm import Session +from typing import List +from app.schemas import Puck as PuckSchema, Sample as SampleSchema, SampleEventCreate +from app.models import Puck as PuckModel, Sample as SampleModel, SampleEvent as SampleEventModel +from app.dependencies import get_db +import logging + +router = APIRouter() + + +@router.get("/{puck_id}/samples", response_model=List[SampleSchema]) +async def get_samples_with_events(puck_id: str, db: Session = Depends(get_db)): + puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first() + if not puck: + raise HTTPException(status_code=404, detail="Puck not found") + + samples = db.query(SampleModel).filter(SampleModel.puck_id == puck_id).all() + + for sample in samples: + sample.events = db.query(SampleEventModel).filter(SampleEventModel.sample_id == sample.id).all() + + return samples + +@router.get("/pucks-samples", response_model=List[PuckSchema]) +async def get_all_pucks_with_samples_and_events(db: Session = Depends(get_db)): + logging.info("Fetching all pucks with samples and events") + + pucks = db.query(PuckModel).all() + logging.info(f"Found {len(pucks)} pucks in the database") + for puck in pucks: + logging.info(f"Puck ID: {puck.id}, Name: {puck.puck_name}") + + if not pucks: + raise HTTPException(status_code=404, detail="No pucks found in the database") # More descriptive + return pucks diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 1ac9a13..8626c73 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -137,6 +137,7 @@ class Sample(BaseModel): puck_id: int crystalname: Optional[str] = Field(None) positioninpuck: Optional[int] = Field(None) + events: List[SampleEventCreate] = [] class SampleCreate(BaseModel): diff --git a/frontend/src/App.css b/frontend/src/App.css index 9144621..6cd936f 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -35,4 +35,62 @@ to { transform: rotate(360deg); } +} + +.sample-tracker-container { + padding: 10px; + border: 1px solid #ddd; + border-radius: 5px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + max-width: 80%; /* Reduce this percentage to fit your needs */ + width: auto; /* Auto width for responsiveness */ + margin: 0 auto; /* Center the tracker horizontally */ + overflow-x: auto; /* Allow horizontal scrolling if necessary */ +} + +.sample-tracker { + display: flex; + flex-direction: column; + align-items: center; +} + +.pucks-container { + display: flex; + flex-wrap: nowrap; + align-items: flex-start; +} + +.puck-column { + display: flex; + flex-direction: column; + align-items: center; + margin: 0 10px; +} + +.puck-label { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + height: 100px; + margin-bottom: 5px; +} + +.puck-label span { + display: block; +} + +.samples { + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; +} + +.sample-dot { + width: 15px; + height: 15px; + border-radius: 50%; + display: inline-block; + border: 1px solid lightgray; } \ No newline at end of file diff --git a/frontend/src/components/SampleTracker.tsx b/frontend/src/components/SampleTracker.tsx new file mode 100644 index 0000000..75a9b2d --- /dev/null +++ b/frontend/src/components/SampleTracker.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from 'react'; +import { SamplesService } from '../../openapi'; + +interface Event { + event_type: string; +} + +interface Sample { + id: number; + sample_name: string; + position: number; + puck_id: number; + crystalname?: string; + positioninpuck?: number; + events: Event[]; +} + +interface Puck { + id: number; + puck_name: string; + puck_type: string; + puck_location_in_dewar: number; + dewar_id: number; + samples: Sample[]; // Check that the API returns this attribute +} + +const SampleTracker: React.FC = () => { + const [pucks, setPucks] = useState([]); + + + useEffect(() => { + const fetchPucks = async () => { + try { + const data: Puck[] = await SamplesService.getAllPucksWithSamplesAndEventsSamplesPucksSamplesGet(); + console.log(data); // Log the data to inspect it + setPucks(data); + } catch (error) { + console.error('Error fetching pucks', error); + } + }; + fetchPucks(); + }, []); + + const getSampleColor = (events: Event[] = []) => { + const hasMounted = events.some(e => e.event_type === 'Mounted'); + const hasUnmounted = events.some(e => e.event_type === 'Unmounted'); + const hasLost = events.some(e => e.event_type === 'Lost'); + const hasFailed = events.some(e => e.event_type === 'Failed'); + + if (hasFailed) return 'red'; + if (hasLost) return 'orange'; + if (hasMounted && hasUnmounted) return 'green'; + + return 'gray'; + }; + + return ( +
+
+

All Pucks and Samples

+
+ {pucks.map((puck) => ( +
+
+ {puck.puck_name.split('').map((char, i) => ( + {char} + ))} +
+
+ {Array.from({length: 16}).map((_, index) => { + const sample = puck.samples.find(s => s.position === index + 1); + return ( +
e.event_type === 'Lost') + ? '1px solid red' : '1px solid lightgray', + }} + >
+ ); + })} +
+
+ ))} +
+
+
+ ); +}; + +export default SampleTracker; \ No newline at end of file diff --git a/frontend/src/pages/ResultsView.tsx b/frontend/src/pages/ResultsView.tsx index 3abbc37..632898e 100644 --- a/frontend/src/pages/ResultsView.tsx +++ b/frontend/src/pages/ResultsView.tsx @@ -1,8 +1,15 @@ // components/ResultView.tsx + import React from 'react'; +import SampleTracker from '../components/SampleTracker'; const ResultsView: React.FC = () => { - return
Results page
; + return ( +
+

Results Page

+ +
+ ); }; export default ResultsView; \ No newline at end of file