Add beamtime assignment functionality for dewars and pucks
Implemented API endpoints and frontend logic to assign/unassign beamtime to dewars and pucks. Enhanced schemas, models, and styles while refactoring related frontend components for better user experience and data handling.
This commit is contained in:
parent
26f8870d04
commit
9e5734f060
@ -96,6 +96,7 @@ class Dewar(Base):
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
pgroups = Column(String(255), nullable=False)
|
||||
dewar_name = Column(String(255), nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.now, nullable=False)
|
||||
dewar_type_id = Column(Integer, ForeignKey("dewar_types.id"), nullable=True)
|
||||
dewar_serial_number_id = Column(
|
||||
Integer, ForeignKey("dewar_serial_numbers.id"), nullable=True
|
||||
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
import hashlib
|
||||
from fastapi import APIRouter, HTTPException, status, Depends, Response
|
||||
@ -21,7 +22,10 @@ from app.schemas import (
|
||||
Sample,
|
||||
Puck,
|
||||
SampleEventResponse,
|
||||
DewarSchema, # Clearer name for schema
|
||||
DewarSchema,
|
||||
loginData,
|
||||
DewarWithPucksResponse,
|
||||
PuckResponse,
|
||||
)
|
||||
from app.models import (
|
||||
Dewar as DewarModel,
|
||||
@ -44,6 +48,7 @@ from reportlab.pdfgen import canvas
|
||||
from app.crud import (
|
||||
get_shipment_by_id,
|
||||
)
|
||||
from app.routers.auth import get_current_user
|
||||
|
||||
|
||||
dewar_router = APIRouter()
|
||||
@ -543,6 +548,64 @@ def get_all_serial_numbers(db: Session = Depends(get_db)):
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@dewar_router.get(
|
||||
"/recent-dewars-with-pucks",
|
||||
response_model=List[DewarWithPucksResponse],
|
||||
operation_id="getRecentDewarsWithPucks",
|
||||
)
|
||||
async def get_recent_dewars_with_pucks(
|
||||
db: Session = Depends(get_db), current_user: loginData = Depends(get_current_user)
|
||||
):
|
||||
# Get the timestamp for two months ago
|
||||
two_months_ago = datetime.now() - timedelta(days=60)
|
||||
|
||||
# Query dewars for this user created in the last 2 months
|
||||
dewars = (
|
||||
db.query(DewarModel)
|
||||
.options(joinedload(DewarModel.pucks)) # Eager load pucks
|
||||
.filter(
|
||||
DewarModel.pgroups.in_(current_user.pgroups),
|
||||
DewarModel.created_at >= two_months_ago,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
result = []
|
||||
for dewar in dewars:
|
||||
pucks = db.query(PuckModel).filter(PuckModel.dewar_id == dewar.id).all()
|
||||
result.append(
|
||||
DewarWithPucksResponse(
|
||||
id=dewar.id,
|
||||
dewar_name=dewar.dewar_name,
|
||||
created_at=dewar.created_at,
|
||||
pucks=[
|
||||
PuckResponse(id=puck.id, puck_name=puck.puck_name) for puck in pucks
|
||||
],
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@dewar_router.patch(
|
||||
"/dewar/{dewar_id}/assign-beamtime", operation_id="assignDewarToBeamtime"
|
||||
)
|
||||
async def assign_beamtime_to_dewar(
|
||||
dewar_id: int,
|
||||
beamtime_id: int, # Use Query if you want this from ?beamtime_id=...
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
|
||||
if not dewar: # <- Move check earlier!
|
||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||
if beamtime_id == 0:
|
||||
dewar.beamtime_id = None
|
||||
else:
|
||||
dewar.beamtime_id = beamtime_id
|
||||
db.commit()
|
||||
db.refresh(dewar)
|
||||
return {"status": "success", "dewar_id": dewar.id, "beamtime_id": beamtime_id}
|
||||
|
||||
|
||||
@dewar_router.get("/{dewar_id}", response_model=Dewar)
|
||||
async def get_dewar(dewar_id: int, db: Session = Depends(get_db)):
|
||||
dewar = (
|
||||
|
@ -658,3 +658,21 @@ async def get_pucks_by_slot(slot_identifier: str, db: Session = Depends(get_db))
|
||||
)
|
||||
|
||||
return pucks
|
||||
|
||||
|
||||
@router.patch("/puck/{puck_id}/assign-beamtime", operation_id="assignPuckToBeamtime")
|
||||
async def assign_beamtime_to_puck(
|
||||
puck_id: int,
|
||||
beamtime_id: int, # If you want ?beamtime_id=123 in the query
|
||||
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")
|
||||
if beamtime_id == 0:
|
||||
puck.beamtime_id = None
|
||||
else:
|
||||
puck.beamtime_id = beamtime_id
|
||||
db.commit()
|
||||
db.refresh(puck)
|
||||
return {"status": "success", "puck_id": puck.id, "beamtime_id": beamtime_id}
|
||||
|
@ -568,6 +568,7 @@ class DewarBase(BaseModel):
|
||||
tracking_number: str
|
||||
number_of_pucks: Optional[int] = None
|
||||
number_of_samples: Optional[int] = None
|
||||
created_at: Optional[datetime] = None
|
||||
status: str
|
||||
contact_id: Optional[int]
|
||||
return_address_id: Optional[int]
|
||||
@ -584,6 +585,7 @@ class DewarCreate(DewarBase):
|
||||
class Dewar(DewarBase):
|
||||
id: int
|
||||
pgroups: str
|
||||
created_at: Optional[datetime] = None
|
||||
shipment_id: Optional[int]
|
||||
contact: Optional[Contact]
|
||||
return_address: Optional[Address]
|
||||
@ -772,6 +774,18 @@ class PuckWithTellPosition(BaseModel):
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PuckResponse(BaseModel):
|
||||
id: int
|
||||
puck_name: str
|
||||
|
||||
|
||||
class DewarWithPucksResponse(BaseModel):
|
||||
id: int
|
||||
dewar_name: str
|
||||
created_at: datetime
|
||||
pucks: List[PuckResponse]
|
||||
|
||||
|
||||
class Beamtime(BaseModel):
|
||||
id: int
|
||||
pgroups: str
|
||||
|
@ -4,12 +4,13 @@ import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import '../styles/Calendar.css';
|
||||
import { BeamtimesService, DewarsService, PucksService } from '../../openapi';
|
||||
|
||||
// Define colors for each beamline
|
||||
const beamlineColors: { [key: string]: string } = {
|
||||
PXI: '#FF5733',
|
||||
PXII: '#33FF57',
|
||||
PXIII: '#3357FF',
|
||||
X06SA: '#FF5733',
|
||||
X10SA: '#33FF57',
|
||||
X06DA: '#3357FF',
|
||||
Unknown: '#CCCCCC', // Gray color for unknown beamlines
|
||||
};
|
||||
|
||||
@ -38,8 +39,11 @@ const darkenColor = (color: string, percent: number): string => {
|
||||
|
||||
const Calendar: React.FC = () => {
|
||||
const [events, setEvents] = useState<CustomEvent[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [fetchError, setFetchError] = useState<string | null>(null);
|
||||
const [selectedEventId, setSelectedEventId] = useState<string | null>(null);
|
||||
const [eventDetails, setEventDetails] = useState<CustomEvent | null>(null);
|
||||
const [eventAssociations, setEventAssociations] = useState<{ [eventId: string]: {dewars: string[], pucks: string[]} }>({});
|
||||
const [userDetails, setUserDetails] = useState({
|
||||
name: '',
|
||||
firstName: '',
|
||||
@ -51,76 +55,94 @@ const Calendar: React.FC = () => {
|
||||
const [shipments, setShipments] = useState<any[]>([]); // State for shipments
|
||||
const [selectedDewars, setSelectedDewars] = useState<string[]>([]); // Track selected dewars for the experiment
|
||||
|
||||
const fetchBeamtimes = async () => {
|
||||
setIsLoading(true);
|
||||
setFetchError(null);
|
||||
|
||||
try {
|
||||
const beamtimeData = await BeamtimesService.getMyBeamtimesProtectedBeamtimesMyBeamtimesGet(); // Replace with actual API function
|
||||
const formattedEvents: CustomEvent[] = beamtimeData.map((beamtime: any) => ({
|
||||
id: `${beamtime.beamline}-${beamtime.start_date}`,
|
||||
title: `${beamtime.beamline}: ${beamtime.shift}`,
|
||||
start: beamtime.start_date,
|
||||
end: beamtime.end_date,
|
||||
beamtime_id: beamtime.id,
|
||||
beamline: beamtime.beamline || 'Unknown',
|
||||
beamtime_shift: beamtime.shift || 'Unknown',
|
||||
backgroundColor: beamlineColors[beamtime.beamline] || beamlineColors.Unknown,
|
||||
borderColor: '#000',
|
||||
textColor: '#fff',
|
||||
}));
|
||||
|
||||
setEvents(formattedEvents);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch beamtime data:', error);
|
||||
setFetchError('Failed to load beamtime data. Please try again later.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch beamtimes on component mount
|
||||
useEffect(() => {
|
||||
const fetchEvents = async () => {
|
||||
try {
|
||||
const response = await fetch('/beamtimedb.json');
|
||||
const data = await response.json();
|
||||
const events: CustomEvent[] = [];
|
||||
|
||||
data.beamtimes.forEach((beamtime: any) => {
|
||||
const date = new Date(beamtime.date);
|
||||
beamtime.shifts.forEach((shift: any) => {
|
||||
const beamline = shift.beamline || 'Unknown';
|
||||
const beamtime_shift = shift.beamtime_shift || 'morning';
|
||||
|
||||
const event: CustomEvent = {
|
||||
id: `${beamline}-${date.toISOString()}-${beamtime_shift}`,
|
||||
start: new Date(date.setHours(0, 0, 0)),
|
||||
end: new Date(date.setHours(23, 59, 59)),
|
||||
title: `${beamline}: ${beamtime_shift}`,
|
||||
beamline,
|
||||
beamtime_shift,
|
||||
isSubmitted: false,
|
||||
};
|
||||
|
||||
events.push(event);
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Fetched events array:', events);
|
||||
setEvents(events);
|
||||
} catch (error) {
|
||||
console.error('Error fetching events:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchShipments = async () => {
|
||||
try {
|
||||
const response = await fetch('/shipmentdb.json');
|
||||
|
||||
// Check for HTTP errors
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Parse the JSON response
|
||||
const data = await response.json();
|
||||
|
||||
const availableDewars: any[] = [];
|
||||
|
||||
data.shipments.forEach(shipment => {
|
||||
if (shipment.shipment_status === "In Transit") {
|
||||
shipment.dewars.forEach(dewar => {
|
||||
if (dewar.shippingStatus === "shipped" && dewar.returned === "") {
|
||||
availableDewars.push(dewar);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Available Dewars:', availableDewars);
|
||||
setShipments(availableDewars);
|
||||
} catch (error) {
|
||||
console.error('Error fetching shipments:', error);
|
||||
// Optionally display the error to the user in the UI
|
||||
}
|
||||
};
|
||||
|
||||
fetchEvents();
|
||||
fetchShipments();
|
||||
fetchBeamtimes();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Only fetch if you want to update dewars when eventDetails change,
|
||||
// or run only on initial mount by passing [] as deps
|
||||
if (eventDetails) {
|
||||
const fetchDewars = async () => {
|
||||
try {
|
||||
// This name depends on your codegen's operationId / function name!
|
||||
const dewars = await DewarsService.getRecentDewarsWithPucks();
|
||||
setShipments(dewars);
|
||||
} catch (err) {
|
||||
// Optionally handle error
|
||||
console.error("Failed to fetch dewars with pucks", err);
|
||||
}
|
||||
};
|
||||
fetchDewars();
|
||||
}
|
||||
}, [eventDetails]); // Fetch dewars when an event is selected
|
||||
|
||||
|
||||
//const fetchShipments = async () => {
|
||||
// try {
|
||||
// const response = await fetch('/shipmentdb.json');
|
||||
//
|
||||
// // Check for HTTP errors
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`HTTP error! status: ${response.status}`);
|
||||
// }
|
||||
//
|
||||
// // Parse the JSON response
|
||||
// const data = await response.json();
|
||||
//
|
||||
// const availableDewars: any[] = [];
|
||||
//
|
||||
// data.shipments.forEach(shipment => {
|
||||
// if (shipment.shipment_status === "In Transit") {
|
||||
// shipment.dewars.forEach(dewar => {
|
||||
// if (dewar.shippingStatus === "shipped" && dewar.returned === "") {
|
||||
// availableDewars.push(dewar);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// console.log('Available Dewars:', availableDewars);
|
||||
// setShipments(availableDewars);
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching shipments:', error);
|
||||
// // Optionally display the error to the user in the UI
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// fetchEvents();
|
||||
// fetchShipments();
|
||||
//}, []);
|
||||
|
||||
const handleEventClick = (eventInfo: any) => {
|
||||
const clickedEventId = eventInfo.event.id;
|
||||
setSelectedEventId(clickedEventId);
|
||||
@ -137,15 +159,6 @@ const Calendar: React.FC = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDewarSelection = (dewarId: string) => {
|
||||
setSelectedDewars(prevSelectedDewars => {
|
||||
if (prevSelectedDewars.includes(dewarId)) {
|
||||
return prevSelectedDewars.filter(id => id !== dewarId); // Remove if already selected
|
||||
} else {
|
||||
return [...prevSelectedDewars, dewarId]; // Add if not selected
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@ -173,11 +186,151 @@ const Calendar: React.FC = () => {
|
||||
});
|
||||
setSelectedDewars([]); // Reset selected dewars
|
||||
};
|
||||
const assignDewarToBeamtime = async (dewarId: number, beamtimeId: number) => {
|
||||
return DewarsService.assignDewarToBeamtime(dewarId, beamtimeId);
|
||||
};
|
||||
|
||||
const unassignDewarFromBeamtime = async (dewarId: number) => {
|
||||
// Pass "0" as the special value (if that's how you unassign)
|
||||
return DewarsService.assignDewarToBeamtime(dewarId, 0); // or whatever your backend supports
|
||||
};
|
||||
|
||||
const assignPuckToBeamtime = async (puckId: number, beamtimeId: number) => {
|
||||
return PucksService.assignPuckToBeamtime(puckId, beamtimeId);
|
||||
};
|
||||
|
||||
// For unassignment (beamtime_id = 0)
|
||||
const unassignPuckFromBeamtime = async (puckId: number) => {
|
||||
return PucksService.assignPuckToBeamtime(puckId, 0);
|
||||
};
|
||||
|
||||
|
||||
|
||||
function handleDewarAssignment(dewarId: string) {
|
||||
const event = events.find(e => e.id === selectedEventId);
|
||||
if (!event) return;
|
||||
const beamtimeId = event.beamtime_id;
|
||||
if (beamtimeId == null) {
|
||||
console.error("No numeric beamtime_id found for selected event:", event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find out if it's already assigned to this shift!
|
||||
const prev = eventAssociations[selectedEventId] || { dewars: [], pucks: [] };
|
||||
const isAssigned = prev.dewars.includes(dewarId);
|
||||
|
||||
if (!isAssigned) {
|
||||
// Assign it immediately
|
||||
assignDewarToBeamtime(Number(dewarId), Number(beamtimeId))
|
||||
.then(() => {
|
||||
setEventAssociations(prevAssoc => {
|
||||
const updated = prevAssoc[selectedEventId] || { dewars: [], pucks: [] };
|
||||
return {
|
||||
...prevAssoc,
|
||||
[selectedEventId]: {
|
||||
...updated,
|
||||
dewars: [...updated.dewars, dewarId]
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("Failed to assign dewar to beamtime", e);
|
||||
});
|
||||
} else {
|
||||
// Unassign (patch to None) immediately
|
||||
unassignDewarFromBeamtime(Number(dewarId))
|
||||
.then(() => {
|
||||
setEventAssociations(prevAssoc => {
|
||||
const updated = prevAssoc[selectedEventId] || { dewars: [], pucks: [] };
|
||||
return {
|
||||
...prevAssoc,
|
||||
[selectedEventId]: {
|
||||
...updated,
|
||||
dewars: updated.dewars.filter(id => id !== dewarId)
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("Failed to unassign dewar from beamtime", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handlePuckAssignment(puckId: string) {
|
||||
const event = events.find(e => e.id === selectedEventId);
|
||||
if (!event) return;
|
||||
const beamtimeId = event.beamtime_id;
|
||||
if (beamtimeId == null) {
|
||||
console.error("No numeric beamtime_id found for selected event:", event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find out if it's already assigned to this shift!
|
||||
const prev = eventAssociations[selectedEventId] || { dewars: [], pucks: [] };
|
||||
const isAssigned = prev.pucks.includes(puckId);
|
||||
|
||||
if (!isAssigned) {
|
||||
// Assign it immediately
|
||||
assignPuckToBeamtime(Number(puckId), Number(beamtimeId))
|
||||
.then(() => {
|
||||
setEventAssociations(prevAssoc => {
|
||||
const updated = prevAssoc[selectedEventId] || { dewars: [], pucks: [] };
|
||||
return {
|
||||
...prevAssoc,
|
||||
[selectedEventId]: {
|
||||
...updated,
|
||||
pucks: [...updated.pucks, puckId]
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("Failed to assign puck to beamtime", e);
|
||||
});
|
||||
} else {
|
||||
// Unassign (patch to None) immediately
|
||||
unassignPuckFromBeamtime(Number(puckId))
|
||||
.then(() => {
|
||||
setEventAssociations(prevAssoc => {
|
||||
const updated = prevAssoc[selectedEventId] || { dewars: [], pucks: [] };
|
||||
return {
|
||||
...prevAssoc,
|
||||
[selectedEventId]: {
|
||||
...updated,
|
||||
pucks: updated.pucks.filter(id => id !== puckId)
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("Failed to unassign puck from beamtime", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findAssociatedEventForDewar(dewarId: string): string | undefined {
|
||||
for (const [eventId, assoc] of Object.entries(eventAssociations)) {
|
||||
if (assoc.dewars.includes(dewarId)) return eventId;
|
||||
}
|
||||
}
|
||||
function findAssociatedEventForPuck(puckId: string): string | undefined {
|
||||
for (const [eventId, assoc] of Object.entries(eventAssociations)) {
|
||||
if (assoc.pucks.includes(puckId)) return eventId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const eventContent = (eventInfo: any) => {
|
||||
const beamline = eventInfo.event.extendedProps.beamline || 'Unknown';
|
||||
const isSelected = selectedEventId === eventInfo.event.id;
|
||||
const isSubmitted = eventInfo.event.extendedProps.isSubmitted;
|
||||
const hasAssociations =
|
||||
eventAssociations[eventInfo.event.id]?.dewars.length > 0 ||
|
||||
eventAssociations[eventInfo.event.id]?.pucks.length > 0;
|
||||
|
||||
|
||||
const backgroundColor = isSubmitted
|
||||
? darkenColor(beamlineColors[beamline] || beamlineColors.Unknown, -20)
|
||||
@ -203,6 +356,7 @@ const Calendar: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
{eventInfo.event.title}
|
||||
{hasAssociations && <span style={{marginLeft: 8}}>🧊</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -232,17 +386,64 @@ const Calendar: React.FC = () => {
|
||||
|
||||
<h4>Select Dewars</h4>
|
||||
<ul>
|
||||
{shipments.map(dewar => (
|
||||
<li key={dewar.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={dewar.id}
|
||||
checked={selectedDewars.includes(dewar.id)}
|
||||
onChange={() => handleDewarSelection(dewar.id)}
|
||||
/>
|
||||
<label htmlFor={dewar.id}>{dewar.dewar_name} (Pucks: {dewar.number_of_pucks})</label>
|
||||
</li>
|
||||
))}
|
||||
{shipments.map(dewar => {
|
||||
// Are *all* pucks assigned to this event? (Assigned at Dewar level)
|
||||
const thisEvent = eventAssociations[selectedEventId] || {dewars: [], pucks: []};
|
||||
const dewarAssigned = thisEvent.dewars.includes(dewar.id);
|
||||
const puckAssignments = thisEvent.pucks;
|
||||
|
||||
return (
|
||||
<li key={dewar.id}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={dewarAssigned}
|
||||
onChange={() => handleDewarAssignment(dewar.id)}
|
||||
/>
|
||||
<b>{dewar.dewar_name}</b>
|
||||
</label>
|
||||
{!dewarAssigned && dewar.pucks && dewar.pucks.length > 0 && (
|
||||
<ul>
|
||||
{(dewar.pucks || []).map(puck => {
|
||||
const isAssigned =
|
||||
!!eventAssociations[selectedEventId]?.pucks.includes(puck.id);
|
||||
|
||||
return (
|
||||
<div key={puck.id} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{puck.name}</span>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
background: isAssigned ? '#4CAF50' : '#e0e0e0',
|
||||
color: isAssigned ? 'white' : 'black',
|
||||
border: isAssigned ? '1px solid #388e3c' : '1px solid #bdbdbd',
|
||||
borderRadius: 4,
|
||||
padding: '4px 10px',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onClick={() => handlePuckAssignment(dewar.id, puck.id)}
|
||||
>
|
||||
{isAssigned ? puck.puck_name : puck.puck_name}
|
||||
</button>
|
||||
{/* Optionally show assignment info, as before */}
|
||||
{findAssociatedEventForPuck(puck.id) && (
|
||||
<span style={{marginLeft: 8, color: 'green'}}>← Assigned to shift: {findAssociatedEventForPuck(puck.id)}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
</ul>
|
||||
)}
|
||||
{/* Show associated shift, if any */}
|
||||
{findAssociatedEventForDewar(dewar.id) && (
|
||||
<span style={{marginLeft: 8, color: 'green'}}>← Assigned to shift: {findAssociatedEventForDewar(dewar.id)}</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<h4>User Information</h4>
|
||||
|
@ -1,46 +1,17 @@
|
||||
.calendar-container {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Styling each day cell */
|
||||
.fc-daygrid-day-frame {
|
||||
position: relative; /* Ensure positioning for child elements */
|
||||
border: 1px solid #e0e0e0; /* Grid cell border for better visibility */
|
||||
}
|
||||
|
||||
/* Event styling */
|
||||
.fc-event {
|
||||
border-radius: 3px; /* Rounded corners for events */
|
||||
padding: 4px; /* Padding for events */
|
||||
font-size: 12px; /* Font size for event text */
|
||||
cursor: pointer; /* Pointer cursor for events */
|
||||
box-sizing: border-box; /* Include padding in the width/height */
|
||||
}
|
||||
|
||||
/* Selected event styling */
|
||||
.fc-event-selected {
|
||||
border: 2px solid black; /* Border for selected events */
|
||||
}
|
||||
|
||||
/* Optional: Add hover effect for events */
|
||||
.fc-event:hover {
|
||||
background-color: #FF7043; /* Change color on hover */
|
||||
}
|
||||
|
||||
.event-details {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.event-details h3 {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.event-details label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.fc-event-shift {
|
||||
position: absolute !important; /* Enables proper alignment */
|
||||
font-size: 12px; /* Text size for better clarity */
|
||||
line-height: 1.2; /* Improve readability */
|
||||
height: auto !important; /* Flexible height based on content */
|
||||
min-height: 25px; /* Ensure adequate space vertically */
|
||||
width: 28% !important; /* Prevent events from spanning full cell width */
|
||||
border: 1px solid #555; /* Consistent event border */
|
||||
border-radius: 4px; /* Rounded corners */
|
||||
background-color: rgba(255, 255, 255, 0.9); /* Default background */
|
||||
white-space: nowrap; /* Prevent text wrapping */
|
||||
overflow: hidden; /* Hide overflowing content */
|
||||
text-overflow: ellipsis; /* Show '...' for long titles */
|
||||
display: flex; /* Align content vertically and horizontally */
|
||||
justify-content: center; /* Center horizontal alignment */
|
||||
align-items: center; /* Center vertical alignment */
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user