added sample tracker on the frontend
This commit is contained in:
parent
1a1a710edf
commit
7b00db3c0d
@ -5,7 +5,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from app import ssl_heidi
|
from app import ssl_heidi
|
||||||
from pathlib import Path
|
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
|
from app.database import Base, engine, SessionLocal, load_sample_data
|
||||||
|
|
||||||
app = FastAPI()
|
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(puck.router, prefix="/pucks", tags=["pucks"])
|
||||||
app.include_router(spreadsheet.router, tags=["spreadsheet"])
|
app.include_router(spreadsheet.router, tags=["spreadsheet"])
|
||||||
app.include_router(logistics.router, prefix="/logistics", tags=["logistics"])
|
app.include_router(logistics.router, prefix="/logistics", tags=["logistics"])
|
||||||
|
app.include_router(sample.router, prefix="/samples", tags=["samples"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
36
backend/app/routers/sample.py
Normal file
36
backend/app/routers/sample.py
Normal file
@ -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
|
@ -137,6 +137,7 @@ class Sample(BaseModel):
|
|||||||
puck_id: int
|
puck_id: int
|
||||||
crystalname: Optional[str] = Field(None)
|
crystalname: Optional[str] = Field(None)
|
||||||
positioninpuck: Optional[int] = Field(None)
|
positioninpuck: Optional[int] = Field(None)
|
||||||
|
events: List[SampleEventCreate] = []
|
||||||
|
|
||||||
|
|
||||||
class SampleCreate(BaseModel):
|
class SampleCreate(BaseModel):
|
||||||
|
@ -35,4 +35,62 @@
|
|||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
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;
|
||||||
}
|
}
|
95
frontend/src/components/SampleTracker.tsx
Normal file
95
frontend/src/components/SampleTracker.tsx
Normal file
@ -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<Puck[]>([]);
|
||||||
|
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="sample-tracker-container">
|
||||||
|
<div className="sample-tracker">
|
||||||
|
<h2>All Pucks and Samples</h2>
|
||||||
|
<div className="pucks-container">
|
||||||
|
{pucks.map((puck) => (
|
||||||
|
<div key={puck.id} className="puck-column">
|
||||||
|
<div className="puck-label">
|
||||||
|
{puck.puck_name.split('').map((char, i) => (
|
||||||
|
<span key={i}>{char}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="samples">
|
||||||
|
{Array.from({length: 16}).map((_, index) => {
|
||||||
|
const sample = puck.samples.find(s => s.position === index + 1);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="sample-dot"
|
||||||
|
style={{
|
||||||
|
backgroundColor: sample
|
||||||
|
? getSampleColor(sample.events)
|
||||||
|
: 'transparent',
|
||||||
|
border: sample && sample.events.some(e => e.event_type === 'Lost')
|
||||||
|
? '1px solid red' : '1px solid lightgray',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SampleTracker;
|
@ -1,8 +1,15 @@
|
|||||||
// components/ResultView.tsx
|
// components/ResultView.tsx
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import SampleTracker from '../components/SampleTracker';
|
||||||
|
|
||||||
const ResultsView: React.FC = () => {
|
const ResultsView: React.FC = () => {
|
||||||
return <div>Results page</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Results Page</h1>
|
||||||
|
<SampleTracker />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ResultsView;
|
export default ResultsView;
|
Loading…
x
Reference in New Issue
Block a user