322 lines
12 KiB
TypeScript
322 lines
12 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import FullCalendar, { EventInput } from '@fullcalendar/react';
|
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
|
import timeGridPlugin from '@fullcalendar/timegrid';
|
|
import interactionPlugin from '@fullcalendar/interaction';
|
|
import '../styles/Calendar.css';
|
|
|
|
// Define colors for each beamline
|
|
const beamlineColors: { [key: string]: string } = {
|
|
PXI: '#FF5733',
|
|
PXII: '#33FF57',
|
|
PXIII: '#3357FF',
|
|
Unknown: '#CCCCCC', // Gray color for unknown beamlines
|
|
};
|
|
|
|
// Custom event interface
|
|
interface CustomEvent extends EventInput {
|
|
beamline: string;
|
|
beamtime_shift: string;
|
|
isSubmitted?: boolean; // Track if information is submitted
|
|
}
|
|
|
|
// 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);
|
|
return `#${newColor}`;
|
|
};
|
|
|
|
const Calendar: React.FC = () => {
|
|
const [events, setEvents] = useState<CustomEvent[]>([]);
|
|
const [selectedEventId, setSelectedEventId] = useState<string | null>(null);
|
|
const [eventDetails, setEventDetails] = useState<CustomEvent | null>(null);
|
|
const [userDetails, setUserDetails] = useState({
|
|
name: '',
|
|
firstName: '',
|
|
phone: '',
|
|
email: '',
|
|
extAccount: '',
|
|
experimentMode: experimentModes[0],
|
|
});
|
|
const [shipments, setShipments] = useState<any[]>([]); // State for shipments
|
|
const [selectedDewars, setSelectedDewars] = useState<string[]>([]); // Track selected dewars for the experiment
|
|
|
|
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();
|
|
}, []);
|
|
|
|
const handleEventClick = (eventInfo: any) => {
|
|
const clickedEventId = eventInfo.event.id;
|
|
setSelectedEventId(clickedEventId);
|
|
|
|
const selectedEvent = events.find(event => event.id === clickedEventId) || null;
|
|
setEventDetails(selectedEvent);
|
|
};
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
|
const { name, value } = e.target;
|
|
setUserDetails(prevDetails => ({
|
|
...prevDetails,
|
|
[name]: value,
|
|
}));
|
|
};
|
|
|
|
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();
|
|
|
|
if (eventDetails) {
|
|
const updatedEvents = events.map(event =>
|
|
event.id === eventDetails.id
|
|
? { ...event, isSubmitted: true, selectedDewars } // Associate selected dewars
|
|
: event
|
|
);
|
|
setEvents(updatedEvents);
|
|
}
|
|
|
|
console.log('User Details:', userDetails);
|
|
console.log('Selected Dewars:', selectedDewars);
|
|
|
|
// Reset user details and selected dewars after submission
|
|
setUserDetails({
|
|
name: '',
|
|
firstName: '',
|
|
phone: '',
|
|
email: '',
|
|
extAccount: '',
|
|
experimentMode: experimentModes[0],
|
|
});
|
|
setSelectedDewars([]); // Reset selected dewars
|
|
};
|
|
|
|
const eventContent = (eventInfo: any) => {
|
|
const beamline = eventInfo.event.extendedProps.beamline || 'Unknown';
|
|
const isSelected = selectedEventId === eventInfo.event.id;
|
|
const isSubmitted = eventInfo.event.extendedProps.isSubmitted;
|
|
|
|
const backgroundColor = isSubmitted
|
|
? darkenColor(beamlineColors[beamline] || beamlineColors.Unknown, -20)
|
|
: isSelected
|
|
? '#FFD700'
|
|
: (beamlineColors[beamline] || beamlineColors.Unknown);
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
backgroundColor: backgroundColor,
|
|
color: 'white',
|
|
border: isSelected ? '2px solid black' : 'none',
|
|
borderRadius: '3px',
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
height: '100%',
|
|
width: '100%',
|
|
cursor: 'pointer',
|
|
overflow: 'hidden',
|
|
boxSizing: 'border-box',
|
|
}}
|
|
>
|
|
{eventInfo.event.title}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="calendar-container">
|
|
<h2>Beamline Calendar</h2>
|
|
<FullCalendar
|
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
|
initialView="dayGridMonth"
|
|
events={events}
|
|
eventContent={eventContent}
|
|
height={700}
|
|
headerToolbar={{
|
|
left: 'prev,next',
|
|
center: 'title',
|
|
right: 'dayGridMonth',
|
|
}}
|
|
eventClick={handleEventClick}
|
|
/>
|
|
|
|
{eventDetails && (
|
|
<div className="event-details">
|
|
<h3>Event Details</h3>
|
|
<p><strong>Beamline:</strong> {eventDetails.beamline}</p>
|
|
<p><strong>Shift:</strong> {eventDetails.beamtime_shift}</p>
|
|
|
|
<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>
|
|
))}
|
|
</ul>
|
|
|
|
<h4>User Information</h4>
|
|
<form onSubmit={handleSubmit}>
|
|
<label>
|
|
Name:
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={userDetails.name}
|
|
onChange={handleInputChange}
|
|
/>
|
|
</label>
|
|
<br />
|
|
<label>
|
|
First Name:
|
|
<input
|
|
type="text"
|
|
name="firstName"
|
|
value={userDetails.firstName}
|
|
onChange={handleInputChange}
|
|
/>
|
|
</label>
|
|
<br />
|
|
<label>
|
|
Phone:
|
|
<input
|
|
type="text"
|
|
name="phone"
|
|
value={userDetails.phone}
|
|
onChange={handleInputChange}
|
|
/>
|
|
</label>
|
|
<br />
|
|
<label>
|
|
Email:
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
value={userDetails.email}
|
|
onChange={handleInputChange}
|
|
/>
|
|
</label>
|
|
<br />
|
|
<label>
|
|
External Account:
|
|
<input
|
|
type="text"
|
|
name="extAccount"
|
|
value={userDetails.extAccount}
|
|
onChange={handleInputChange}
|
|
/>
|
|
</label>
|
|
<br />
|
|
<label>
|
|
Experiment Mode:
|
|
<select
|
|
name="experimentMode"
|
|
value={userDetails.experimentMode}
|
|
onChange={handleInputChange}
|
|
>
|
|
{experimentModes.map(mode => (
|
|
<option key={mode} value={mode}>{mode}</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<br />
|
|
<button type="submit">Submit</button>
|
|
</form>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Calendar;
|