Add endpoints and logic for fetching associations by beamtime
Introduced endpoints to fetch pucks, dewars, and samples by beamtime ID. Updated backend logic to ensure consistency between dewars, pucks, and samples assignments. Enhanced frontend to display and handle beamline-specific associations dynamically.
This commit is contained in:
parent
e341459590
commit
0fa038be94
@ -682,9 +682,16 @@ dewar_to_beamtime = {
|
||||
dewar.id: random.choice([1, 2]) for dewar in dewars # Or use actual beamtime ids
|
||||
}
|
||||
|
||||
# Update dewars and their pucks with consistent beamtime
|
||||
for dewar in dewars:
|
||||
dewar.beamtime_id = dewar_to_beamtime[dewar.id]
|
||||
|
||||
for puck in pucks:
|
||||
dewar_id = puck.dewar_id # Assuming puck has dewar_id
|
||||
assigned_beamtime = dewar_to_beamtime[dewar_id]
|
||||
puck.beamtime_id = (
|
||||
assigned_beamtime # Associate puck to the same beamtime as its dewar
|
||||
)
|
||||
|
||||
positions_with_samples = random.randint(1, 16)
|
||||
occupied_positions = random.sample(range(1, 17), positions_with_samples)
|
||||
@ -696,7 +703,7 @@ for puck in pucks:
|
||||
sample_name=f"Sample{sample_id_counter:03}",
|
||||
position=pos,
|
||||
puck_id=puck.id,
|
||||
beamtime_id=assigned_beamtime, # IMPORTANT: Use the dewar's beamtime
|
||||
beamtime_id=assigned_beamtime,
|
||||
)
|
||||
samples.append(sample)
|
||||
sample_id_counter += 1
|
||||
|
@ -718,3 +718,13 @@ async def get_single_shipment(id: int, db: Session = Depends(get_db)):
|
||||
except SQLAlchemyError as e:
|
||||
logging.error(f"Database error occurred: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@dewar_router.get(
|
||||
"/by-beamtime/{beamtime_id}",
|
||||
response_model=List[DewarSchema],
|
||||
operation_id="get_dewars_by_beamtime",
|
||||
)
|
||||
async def get_dewars_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)):
|
||||
"""List all dewars assigned to a given beamtime."""
|
||||
return db.query(DewarModel).filter(DewarModel.beamtime_id == beamtime_id).all()
|
||||
|
@ -683,3 +683,13 @@ async def assign_beamtime_to_puck(
|
||||
sample.beamtime_id = None if beamtime_id == 0 else beamtime_id
|
||||
db.commit()
|
||||
return {"status": "success", "puck_id": puck.id, "beamtime_id": beamtime_id}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-beamtime/{beamtime_id}",
|
||||
response_model=List[PuckSchema],
|
||||
operation_id="get_pucks_by_beamtime",
|
||||
)
|
||||
async def get_pucks_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)):
|
||||
"""List all pucks assigned to a given beamtime."""
|
||||
return db.query(PuckModel).filter(PuckModel.beamtime_id == beamtime_id).all()
|
||||
|
@ -471,3 +471,13 @@ async def get_results_for_run_and_sample(
|
||||
]
|
||||
|
||||
return formatted_results
|
||||
|
||||
|
||||
@router.get(
|
||||
"/by-beamtime/{beamtime_id}",
|
||||
response_model=List[SampleSchema],
|
||||
operation_id="get_samples_by_beamtime",
|
||||
)
|
||||
async def get_samples_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)):
|
||||
"""List all samples assigned to a given beamtime."""
|
||||
return db.query(SampleModel).filter(SampleModel.beamtime_id == beamtime_id).all()
|
||||
|
@ -6,34 +6,31 @@ 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 } = {
|
||||
X06SA: '#FF5733',
|
||||
X10SA: '#33FF57',
|
||||
X06DA: '#3357FF',
|
||||
Unknown: '#CCCCCC', // Gray color for unknown beamlines
|
||||
Unknown: '#CCCCCC',
|
||||
};
|
||||
|
||||
// Custom event interface
|
||||
interface CustomEvent extends EventInput {
|
||||
beamline: string;
|
||||
beamtime_shift: string;
|
||||
isSubmitted?: boolean; // Track if information is submitted
|
||||
beamtime_id?: number;
|
||||
isSubmitted?: boolean;
|
||||
}
|
||||
|
||||
// Define experiment modes
|
||||
const experimentModes = ['SDU-Scheduled', 'SDU-queued', 'Remote', 'In-person'];
|
||||
|
||||
// Utility function to darken a hex color
|
||||
const darkenColor = (color: string, percent: number): string => {
|
||||
const num = parseInt(color.slice(1), 16); // Convert hex to number
|
||||
const amt = Math.round(2.55 * percent); // Calculate amount to darken
|
||||
const r = (num >> 16) + amt; // Red
|
||||
const g = (num >> 8 & 0x00FF) + amt; // Green
|
||||
const b = (num & 0x0000FF) + amt; // Blue
|
||||
|
||||
// Ensure values stay within 0-255 range
|
||||
const newColor = (0x1000000 + (r < 255 ? (r < 0 ? 0 : r) : 255) * 0x10000 + (g < 255 ? (g < 0 ? 0 : g) : 255) * 0x100 + (b < 255 ? (b < 0 ? 0 : b) : 255)).toString(16).slice(1);
|
||||
const num = parseInt(color.slice(1), 16);
|
||||
const amt = Math.round(2.55 * percent);
|
||||
const r = (num >> 16) + amt;
|
||||
const g = (num >> 8 & 0x00FF) + amt;
|
||||
const b = (num & 0x0000FF) + amt;
|
||||
const newColor = (0x1000000 + (r < 255 ? (r < 0 ? 0 : r) : 255) * 0x10000
|
||||
+ (g < 255 ? (g < 0 ? 0 : g) : 255) * 0x100
|
||||
+ (b < 255 ? (b < 0 ? 0 : b) : 255)).toString(16).slice(1);
|
||||
return `#${newColor}`;
|
||||
};
|
||||
|
||||
@ -43,7 +40,8 @@ const Calendar: React.FC = () => {
|
||||
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[]} }>({});
|
||||
// eventId => { dewars: [dewar_id], pucks: [puck_id] }
|
||||
const [eventAssociations, setEventAssociations] = useState<{ [eventId: string]: { dewars: string[], pucks: string[] } }>({});
|
||||
const [userDetails, setUserDetails] = useState({
|
||||
name: '',
|
||||
firstName: '',
|
||||
@ -52,103 +50,94 @@ const Calendar: React.FC = () => {
|
||||
extAccount: '',
|
||||
experimentMode: experimentModes[0],
|
||||
});
|
||||
const [shipments, setShipments] = useState<any[]>([]); // State for shipments
|
||||
const [selectedDewars, setSelectedDewars] = useState<string[]>([]); // Track selected dewars for the experiment
|
||||
const [shipments, setShipments] = useState<any[]>([]);
|
||||
|
||||
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
|
||||
// Load all beamtime events AND their current associations (on mount)
|
||||
useEffect(() => {
|
||||
fetchBeamtimes();
|
||||
const fetchAll = async () => {
|
||||
setIsLoading(true);
|
||||
setFetchError(null);
|
||||
try {
|
||||
const beamtimes = await BeamtimesService.getMyBeamtimesProtectedBeamtimesMyBeamtimesGet();
|
||||
const formattedEvents: CustomEvent[] = beamtimes.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);
|
||||
|
||||
// Fetch associations for all
|
||||
const assoc: { [id: string]: { dewars: string[]; pucks: string[] } } = {};
|
||||
await Promise.all(
|
||||
beamtimes.map(async (bm: any) => {
|
||||
const [dewars, pucks] = await Promise.all([
|
||||
DewarsService.getDewarsByBeamtime(bm.id),
|
||||
PucksService.getPucksByBeamtime(bm.id),
|
||||
]);
|
||||
const eventId = `${bm.beamline}-${bm.start_date}`;
|
||||
assoc[eventId] = {
|
||||
dewars: dewars.map((d: any) => d.id),
|
||||
pucks: pucks.map((p: any) => p.id),
|
||||
};
|
||||
})
|
||||
);
|
||||
setEventAssociations(assoc);
|
||||
|
||||
} catch (error) {
|
||||
setFetchError('Failed to load beamtime data. Please try again later.');
|
||||
setEvents([]);
|
||||
setEventAssociations({});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchAll();
|
||||
}, []);
|
||||
|
||||
// When an event is selected, fetch up-to-date dewar list
|
||||
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);
|
||||
const dewarsWithPucks = await DewarsService.getRecentDewarsWithPucks();
|
||||
setShipments(dewarsWithPucks);
|
||||
} catch (err) {
|
||||
// Optionally handle error
|
||||
console.error("Failed to fetch dewars with pucks", err);
|
||||
setShipments([]);
|
||||
}
|
||||
};
|
||||
fetchDewars();
|
||||
} else {
|
||||
setShipments([]);
|
||||
}
|
||||
}, [eventDetails]); // Fetch dewars when an event is selected
|
||||
}, [eventDetails]);
|
||||
|
||||
|
||||
//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();
|
||||
//}, []);
|
||||
// Refresh associations after (un)assign action
|
||||
const refetchEventAssociations = async (beamtimeId: number, eventId: string) => {
|
||||
const [dewars, pucks] = await Promise.all([
|
||||
DewarsService.getDewarsByBeamtime(beamtimeId),
|
||||
PucksService.getPucksByBeamtime(beamtimeId),
|
||||
]);
|
||||
setEventAssociations(prev => ({
|
||||
...prev,
|
||||
[eventId]: {
|
||||
dewars: dewars.map((d: any) => d.id),
|
||||
pucks: pucks.map((p: any) => p.id),
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
const handleEventClick = (eventInfo: any) => {
|
||||
const clickedEventId = eventInfo.event.id;
|
||||
setSelectedEventId(clickedEventId);
|
||||
|
||||
const selectedEvent = events.find(event => event.id === clickedEventId) || null;
|
||||
setEventDetails(selectedEvent);
|
||||
const selected = events.find(event => event.id === clickedEventId) || null;
|
||||
setEventDetails(selected);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||
@ -159,23 +148,15 @@ const Calendar: React.FC = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (eventDetails) {
|
||||
const updatedEvents = events.map(event =>
|
||||
event.id === eventDetails.id
|
||||
? { ...event, isSubmitted: true, selectedDewars } // Associate selected dewars
|
||||
: event
|
||||
setEvents(prev =>
|
||||
prev.map(event =>
|
||||
event.id === eventDetails.id ? { ...event, isSubmitted: true } : event
|
||||
)
|
||||
);
|
||||
setEvents(updatedEvents);
|
||||
}
|
||||
|
||||
console.log('User Details:', userDetails);
|
||||
console.log('Selected Dewars:', selectedDewars);
|
||||
|
||||
// Reset user details and selected dewars after submission
|
||||
setUserDetails({
|
||||
name: '',
|
||||
firstName: '',
|
||||
@ -184,143 +165,48 @@ const Calendar: React.FC = () => {
|
||||
extAccount: '',
|
||||
experimentMode: experimentModes[0],
|
||||
});
|
||||
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;
|
||||
// Unified assign/unassign for Dewars
|
||||
const handleDewarAssignment = async (dewarId: string) => {
|
||||
if (!selectedEventId) return;
|
||||
const event = events.find(e => e.id === selectedEventId)!;
|
||||
const beamtimeId = event.beamtime_id;
|
||||
if (beamtimeId == null) {
|
||||
console.error("No numeric beamtime_id found for selected event:", event);
|
||||
return;
|
||||
if (!beamtimeId) return;
|
||||
// Is this dewar already assigned here?
|
||||
const assigned = eventAssociations[selectedEventId]?.dewars.includes(dewarId);
|
||||
try {
|
||||
if (assigned) {
|
||||
await DewarsService.assignDewarToBeamtime(Number(dewarId), 0);
|
||||
} else {
|
||||
await DewarsService.assignDewarToBeamtime(Number(dewarId), Number(beamtimeId));
|
||||
}
|
||||
await refetchEventAssociations(Number(beamtimeId), selectedEventId);
|
||||
} catch (e) {
|
||||
// Optionally report error
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
// Unified assign/unassign for Pucks
|
||||
const handlePuckAssignment = async (puckId: string) => {
|
||||
if (!selectedEventId) return;
|
||||
const event = events.find(e => e.id === selectedEventId)!;
|
||||
const beamtimeId = event.beamtime_id;
|
||||
if (beamtimeId == null) {
|
||||
console.error("No numeric beamtime_id found for selected event:", event);
|
||||
return;
|
||||
if (!beamtimeId) return;
|
||||
const assigned = eventAssociations[selectedEventId]?.pucks.includes(puckId);
|
||||
try {
|
||||
if (assigned) {
|
||||
await PucksService.assignPuckToBeamtime(Number(puckId), 0);
|
||||
} else {
|
||||
await PucksService.assignPuckToBeamtime(Number(puckId), Number(beamtimeId));
|
||||
}
|
||||
await refetchEventAssociations(Number(beamtimeId), selectedEventId);
|
||||
} catch (e) {
|
||||
// Optionally report error
|
||||
}
|
||||
};
|
||||
|
||||
// Get current association state
|
||||
const prev = eventAssociations[selectedEventId] || { dewars: [], pucks: [] };
|
||||
const isAssigned = prev.pucks.includes(puckId);
|
||||
|
||||
if (!isAssigned) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// For displaying badge in calendar and UI
|
||||
const eventContent = (eventInfo: any) => {
|
||||
const beamline = eventInfo.event.extendedProps.beamline || 'Unknown';
|
||||
const isSelected = selectedEventId === eventInfo.event.id;
|
||||
@ -328,8 +214,6 @@ const Calendar: React.FC = () => {
|
||||
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)
|
||||
: isSelected
|
||||
@ -359,6 +243,18 @@ const Calendar: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// Used in Dewar/Puck assign status reporting
|
||||
function getAssignedEventForDewar(dewarId: string) {
|
||||
return Object.entries(eventAssociations).find(([eid, assoc]) =>
|
||||
assoc.dewars.includes(dewarId)
|
||||
);
|
||||
}
|
||||
function getAssignedEventForPuck(puckId: string) {
|
||||
return Object.entries(eventAssociations).find(([eid, assoc]) =>
|
||||
assoc.pucks.includes(puckId)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="calendar-container">
|
||||
<h2>Beamline Calendar</h2>
|
||||
@ -385,9 +281,20 @@ const Calendar: React.FC = () => {
|
||||
<h4>Select Dewars</h4>
|
||||
<ul>
|
||||
{shipments.map(dewar => {
|
||||
const thisEvent = eventAssociations[selectedEventId] || { dewars: [], pucks: [] };
|
||||
const thisEvent = eventAssociations[selectedEventId!] || { dewars: [], pucks: [] };
|
||||
const dewarAssigned = thisEvent.dewars.includes(dewar.id);
|
||||
|
||||
const [assocEventId, assoc] = getAssignedEventForDewar(dewar.id) || [];
|
||||
const assocEvent = assocEventId
|
||||
? events.find(ev => ev.id === assocEventId)
|
||||
: null;
|
||||
const assocShift = assocEvent?.beamtime_shift;
|
||||
const assocDate = assocEvent?.start;
|
||||
const assocBeamline = assocEvent?.beamline;
|
||||
|
||||
const currentShift = eventDetails?.beamtime_shift;
|
||||
const isAssignedToThis = assocShift && currentShift && assocShift === currentShift;
|
||||
|
||||
return (
|
||||
<li key={dewar.id}>
|
||||
<label>
|
||||
@ -398,92 +305,57 @@ const Calendar: React.FC = () => {
|
||||
/>
|
||||
<b>{dewar.dewar_name}</b>
|
||||
</label>
|
||||
{!dewarAssigned && dewar.pucks && dewar.pucks.length > 0 && (
|
||||
{/* List all pucks in this Dewar, each with assign button */}
|
||||
{Array.isArray(dewar.pucks) && dewar.pucks.length > 0 && (
|
||||
<ul>
|
||||
{(dewar.pucks || []).map(puck => {
|
||||
// Find eventId for this puck, if assigned
|
||||
const associatedEventId = Object.keys(eventAssociations).find(eid =>
|
||||
eventAssociations[eid]?.pucks.includes(puck.id)
|
||||
);
|
||||
const associatedEvent = associatedEventId
|
||||
? events.find(ev => ev.id === associatedEventId)
|
||||
{dewar.pucks.map(puck => {
|
||||
const [pAssocEventId] = getAssignedEventForPuck(puck.id) || [];
|
||||
const pAssocEvent = pAssocEventId
|
||||
? events.find(ev => ev.id === pAssocEventId)
|
||||
: null;
|
||||
|
||||
const associatedShift = associatedEvent?.beamtime_shift;
|
||||
const associatedDate = associatedEvent?.start;
|
||||
const associatedBeamline = associatedEvent?.beamline;
|
||||
|
||||
const currentShift = eventDetails?.beamtime_shift;
|
||||
const isAssignedToCurrentShift = associatedShift && currentShift && associatedShift === currentShift;
|
||||
|
||||
const pAssocShift = pAssocEvent?.beamtime_shift;
|
||||
const pAssocDate = pAssocEvent?.start;
|
||||
const pAssocBeamline = pAssocEvent?.beamline;
|
||||
const isAssignedHere = pAssocShift && currentShift && pAssocShift === currentShift;
|
||||
return (
|
||||
<div key={puck.id} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{puck.name}</span>
|
||||
<li key={puck.id} style={{marginLeft:8}}>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
background: isAssignedToCurrentShift ? '#4CAF50' : (associatedShift ? '#B3E5B3' : '#e0e0e0'),
|
||||
color: isAssignedToCurrentShift ? 'white' : 'black',
|
||||
border: isAssignedToCurrentShift ? '1px solid #388e3c' : '1px solid #bdbdbd',
|
||||
background: isAssignedHere ? '#4CAF50' : (pAssocShift ? '#B3E5B3' : '#e0e0e0'),
|
||||
color: isAssignedHere ? 'white' : 'black',
|
||||
border: isAssignedHere ? '1px solid #388e3c' : '1px solid #bdbdbd',
|
||||
borderRadius: 4,
|
||||
padding: '4px 10px',
|
||||
padding: '2px 10px',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onClick={() => handlePuckAssignment(puck.id)}
|
||||
>
|
||||
{puck.puck_name}
|
||||
{puck.puck_name || puck.name}
|
||||
</button>
|
||||
{associatedEvent && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
color: isAssignedToCurrentShift ? 'green' : '#388e3c',
|
||||
fontWeight: isAssignedToCurrentShift ? 700 : 400
|
||||
}}
|
||||
>
|
||||
← Assigned to shift: {associatedShift}
|
||||
{associatedDate && (
|
||||
<> on {new Date(associatedDate).toLocaleDateString()}</>
|
||||
)}
|
||||
{associatedBeamline && (
|
||||
<> ({associatedBeamline})</>
|
||||
)}
|
||||
</span>
|
||||
{pAssocEvent && (
|
||||
<span style={{
|
||||
marginLeft: 8,
|
||||
color: isAssignedHere ? 'green' : '#388e3c',
|
||||
fontWeight: isAssignedHere ? 700 : 400
|
||||
}}>
|
||||
← Assigned to: {pAssocShift} {pAssocDate && <>on {new Date(pAssocDate).toLocaleDateString()}</>} {pAssocBeamline && <>({pAssocBeamline})</>}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
{/* Show associated shift, date, and beamline for Dewar, if any */}
|
||||
{(() => {
|
||||
const associatedEventId = Object.keys(eventAssociations).find(eid =>
|
||||
eventAssociations[eid]?.dewars.includes(dewar.id)
|
||||
);
|
||||
const associatedEvent = associatedEventId
|
||||
? events.find(ev => ev.id === associatedEventId)
|
||||
: null;
|
||||
const associatedShift = associatedEvent?.beamtime_shift;
|
||||
const associatedDate = associatedEvent?.start;
|
||||
const associatedBeamline = associatedEvent?.beamline;
|
||||
|
||||
const currentShift = eventDetails?.beamtime_shift;
|
||||
const isAssignedToCurrentShift = associatedShift && currentShift && associatedShift === currentShift;
|
||||
|
||||
return associatedEvent && (
|
||||
<span style={{ marginLeft: 8, color: isAssignedToCurrentShift ? 'green' : '#388e3c', fontWeight: isAssignedToCurrentShift ? 700 : 400 }}>
|
||||
← Assigned to shift: {associatedShift}
|
||||
{associatedDate && (
|
||||
<> on {new Date(associatedDate).toLocaleDateString()}</>
|
||||
)}
|
||||
{associatedBeamline && (
|
||||
<> ({associatedBeamline})</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
{/* Show dewar assignment info if not to this shift */}
|
||||
{assocEvent && (
|
||||
<span style={{marginLeft:8, color:isAssignedToThis?'green':'#388e3c', fontWeight:isAssignedToThis?700:400}}>
|
||||
← Assigned to: {assocShift}
|
||||
{assocDate && <> on {new Date(assocDate).toLocaleDateString()}</>}
|
||||
{assocBeamline && <> ({assocBeamline})</>}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@ -562,4 +434,4 @@ const Calendar: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Calendar;
|
||||
export default Calendar;
|
Loading…
x
Reference in New Issue
Block a user