implement add/remove shigfts from the UI, set transport required

This commit is contained in:
Dobromir Popov
2024-04-19 18:40:29 +03:00
parent fd43ac3004
commit b06c7be9b0
2 changed files with 186 additions and 144 deletions

View File

@ -10,7 +10,7 @@ const common = require('src/helpers/common');
function ShiftComponent({ shift, onShiftSelect, isSelected, onPublisherSelect, allPublishersInfo }) {
const [isDeleted, setIsDeleted] = useState(false);
const [assignments, setAssignments] = useState(shift.assignments);
const [isModalOpen, setIsModalOpen] = useState(false);
const [useFilterDate, setUseFilterDate] = useState(true);
@ -24,24 +24,14 @@ function ShiftComponent({ shift, onShiftSelect, isSelected, onPublisherSelect, a
}, [shift.assignments]);
const handleShiftClick = (shiftId) => {
// console.log("onShiftSelect prop:", onShiftSelect);
// console.log("Shift clicked:", shift);
//shift.selectedPublisher = selectedPublisher;
if (onShiftSelect) {
onShiftSelect(shift);
}
};
const handlePublisherClick = (publisher) => {
//toggle selected
// if (selectedPublisher != null) {
// setSelectedPublisher(null);
// }
// else {
setSelectedPublisher(publisher);
console.log("Publisher clicked:", publisher, "selected publisher:", selectedPublisher);
shift.selectedPublisher = publisher;
if (onShiftSelect) {
@ -54,6 +44,17 @@ function ShiftComponent({ shift, onShiftSelect, isSelected, onPublisherSelect, a
common.copyToClipboard(null, publisher.firstName + ' ' + publisher.lastName);
}
const deleteShift = async (id) => {
try {
console.log("Removing shift with id:", id);
await axiosInstance.delete("/api/data/shifts/" + id);
setIsDeleted(true);
} catch (error) {
console.error("Error removing shift:", error);
}
}
const removeAssignment = async (id) => {
try {
console.log("Removing assignment with id:", id);
@ -100,137 +101,162 @@ function ShiftComponent({ shift, onShiftSelect, isSelected, onPublisherSelect, a
} catch (error) { }
}
async function toggleRequiresTransport(shiftId): Promise<void> {
try {
shift.requiresTransport = !shift.requiresTransport;
const { data } = await axiosInstance.put("/api/data/shifts/" + shiftId,
{ requiresTransport: shift.requiresTransport })
.then(() => {
console.log("shift '" + shiftId + "' transport required:" + shift.requiresTransport);
// setTransportProvided(assignments.some(ass => ass.isWithTransport))
});
} catch (error) { }
}
return (
<div className={`flow w-full p-4 py-2 border-2 border-gray-300 rounded-md my-1 ${isSelected ? 'bg-gray-200' : ''}`}
onClick={handleShiftClick} onDoubleClick={copyAllPublisherNames}>
{/* Time Window Header */}
<div className="flex justify-between items-center mb-2 border-b pb-1">
<span className="text-lg font-semibold">
{`${common.getTimeRange(new Date(shift.startTime), new Date(shift.endTime))}`}
{/* {shift.requiresTransport && (<LocalShippingIcon />)} */}
</span>
<>{!isDeleted && (
<div className={`flow w-full p-4 py-2 border-2 border-gray-300 rounded-md my-1 ${isSelected ? 'bg-gray-200' : ''}`}
onClick={handleShiftClick} onDoubleClick={copyAllPublisherNames}>
{/* Time Window Header */}
<div className="flex justify-between items-center mb-2 border-b pb-1">
<span className="flex text-lg font-semibold">
{`${common.getTimeRange(new Date(shift.startTime), new Date(shift.endTime))}`}
{/* {shift.requiresTransport && (<LocalShippingIcon />)} */}
{/* Toggle for Transport Requirement */}
<label className="ml-4 flex items-center">
<input type="checkbox" checked={shift.requiresTransport}
onChange={() => toggleRequiresTransport(shift.id)}
className="form-checkbox h-5 w-5 text-green-600" />
<span className="ml-2 text-sm text-gray-700">транспорт</span>
</label>
</span>
{/* Copy All Names Button */}
<button onClick={copyAllPublisherNames} className="bg-green-500 text-white py-1 px-2 text-sm rounded-md">
копирай имената {/* Placeholder for Copy icon */}
</button>
{/* Hint Message */}
{showCopyHint && (
<div className="absolute top-0 right-0 p-2 bg-green-200 text-green-800 rounded">
Имената са копирани
</div>
)}
</div>
{/* Copy All Names Button */}
<button onClick={copyAllPublisherNames} className="bg-green-500 text-white py-1 px-2 text-sm rounded-md">
копирай имената {/* Placeholder for Copy icon */}
</button>
{/* Hint Message */}
{showCopyHint && (
<div className="absolute top-0 right-0 p-2 bg-green-200 text-green-800 rounded">
Имената са копирани
</div>
)}
</div>
{/* Assignments */}
{assignments.map((ass, index) => {
const publisherInfo = allPublishersInfo.find(info => info?.id === ass.publisher.id) || ass.publisher;
{/* Assignments */}
{assignments.map((ass, index) => {
const publisherInfo = allPublishersInfo.find(info => info?.id === ass.publisher.id) || ass.publisher;
// Determine border styles
let borderStyles = '';
let canTransport = false;
if (selectedPublisher && selectedPublisher.id === ass.publisher.id) {
borderStyles += 'border-2 border-blue-300'; // Bottom border for selected publishers
}
else {
if (publisherInfo.availabilityCount == 0) //user has never the form
{
borderStyles = 'border-2 border-orange-300 ';
// Determine border styles
let borderStyles = '';
let canTransport = false;
if (selectedPublisher && selectedPublisher.id === ass.publisher.id) {
borderStyles += 'border-2 border-blue-300'; // Bottom border for selected publishers
}
else
//if there is no publisherInfo - draw red border - publisher is no longer available for the day!
if (!publisherInfo.availabilities || publisherInfo.availabilities.length == 0) {
borderStyles = 'border-2 border-red-500 ';
else {
if (publisherInfo.availabilityCount == 0) //user has never the form
{
borderStyles = 'border-2 border-orange-300 ';
}
else {
else
//if there is no publisherInfo - draw red border - publisher is no longer available for the day!
if (!publisherInfo.availabilities || publisherInfo.availabilities.length == 0) {
borderStyles = 'border-2 border-red-500 ';
}
else {
// checkig if the publisher is available for this assignment
const av = publisherInfo.availabilities?.find(av =>
av.startTime <= shift.startTime && av.endTime >= shift.endTime
);
if (av) {
borderStyles += 'border-l-2 border-blue-500 '; // Left border for specific availability conditions
ass.canTransport = av.isWithTransportIn || av.isWithTransportOut;
}
if (publisherInfo.hasUpToDateAvailabilities) {
//add green right border
borderStyles += 'border-r-2 border-green-300';
}
//the pub is the same time as last month
// if (publisherInfo.availabilities?.some(av =>
// (!av.dayOfMonth || av.isFromPreviousMonth) &&
// av.startTime <= ass.startTime &&
// av.endTime >= ass.endTime)) {
// borderStyles += 'border-t-2 border-yellow-500 '; // Left border for specific availability conditions
// }
// checkig if the publisher is available for this assignment
const av = publisherInfo.availabilities?.find(av =>
av.startTime <= shift.startTime && av.endTime >= shift.endTime
);
if (av) {
borderStyles += 'border-l-2 border-blue-500 '; // Left border for specific availability conditions
ass.canTransport = av.isWithTransportIn || av.isWithTransportOut;
}
if (publisherInfo.hasUpToDateAvailabilities) {
//add green right border
borderStyles += 'border-r-2 border-green-300';
}
}
//the pub is the same time as last month
// if (publisherInfo.availabilities?.some(av =>
// (!av.dayOfMonth || av.isFromPreviousMonth) &&
// av.startTime <= ass.startTime &&
// av.endTime >= ass.endTime)) {
// borderStyles += 'border-t-2 border-yellow-500 '; // Left border for specific availability conditions
// }
return (
<div key={index}
className={`flow space-x-2 rounded-md px-2 py-1 my-1 ${ass.isConfirmed ? 'bg-green-100' : 'bg-gray-100'} ${borderStyles}`}
>
<div className="flex justify-between items-center" onClick={() => handlePublisherClick(ass.publisher)}>
<span className="text-gray-700">{publisherInfo.firstName} {publisherInfo.lastName}</span>
<div className="flex items-left" >
{/* //if shift.isWithTransport, add trnsport button toggle, which sets ass.isWithTransportIn */}
{shift.requiresTransport && (
<span
onClick={ass.canTransport ? () => toggleTransport(ass) : undefined}
className={`material-icons ${ass.isWithTransport ? 'text-green-500 font-bold' : (transportProvided ? 'text-gray-400 ' : 'text-orange-400 font-bold')} ${ass.canTransport ? ' cursor-pointer' : 'cursor-not-allowed'} px-3 py-1 ml-2 rounded-md`}
>
{ass.isWithTransport ? "транспорт" : ass.canTransport ? "може транспорт" : "без транспорт"} <LocalShippingIcon />
</span>
)}
<button onClick={() => removeAssignment(ass.id)} className="text-white bg-red-500 hover:bg-red-600 px-3 py-1 ml-2 rounded-md" >
махни
</button>
}
}
return (
<div key={index}
className={`flow space-x-2 rounded-md px-2 py-1 my-1 ${ass.isConfirmed ? 'bg-green-100' : 'bg-gray-100'} ${borderStyles}`}
>
<div className="flex justify-between items-center" onClick={() => handlePublisherClick(ass.publisher)}>
<span className="text-gray-700">{publisherInfo.firstName} {publisherInfo.lastName}</span>
<div className="flex items-left" >
{/* //if shift.isWithTransport, add trnsport button toggle, which sets ass.isWithTransportIn */}
{shift.requiresTransport && (
<span
onClick={ass.canTransport ? () => toggleTransport(ass) : undefined}
className={`material-icons ${ass.isWithTransport ? 'text-green-500 font-bold' : (transportProvided ? 'text-gray-400 ' : 'text-orange-400 font-bold')} ${ass.canTransport ? ' cursor-pointer' : 'cursor-not-allowed'} px-3 py-1 ml-2 rounded-md`}
>
{ass.isWithTransport ? "транспорт" : ass.canTransport ? "може транспорт" : "без транспорт"} <LocalShippingIcon />
</span>
)}
<button onClick={() => removeAssignment(ass.id)} className="text-white bg-red-500 hover:bg-red-600 px-3 py-1 ml-2 rounded-md" >
махни
</button>
</div>
</div>
</div>
</div>
);
})}
);
})}
{/* This is a placeholder for the dropdown to add a publisher. You'll need to implement or integrate a dropdown component */}
{/* This is a placeholder for the dropdown to add a publisher. You'll need to implement or integrate a dropdown component */}
<div className="flex space-x-2 items-center">
{/* Add Button */}
<button onClick={() => setIsModalOpen(true)} className="bg-blue-500 text-white p-2 py-1 rounded-md">
добави {/* Placeholder for Add icon */}
</button>
</div>
<div className="flex space-x-2 items-center">
{/* Add Button */}
<button onClick={() => setIsModalOpen(true)} className="bg-blue-500 text-white p-2 py-1 rounded-md">
добави участник{/* Placeholder for Add icon */}
</button>
{assignments.length == 0 && (
<button onClick={() => deleteShift(shift.id)} className="bg-red-500 text-white p-2 py-1 rounded-md"
>изтрий смяната</button>
)}
</div>
{/* Modal for Publisher Search
{/* Modal for Publisher Search
forDate={new Date(shift.startTime)}
*/}
<Modal isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
forDate={new Date(shift.startTime)}
useFilterDate={useFilterDate}
onUseFilterDateChange={(value) => setUseFilterDate(value)}>
<Modal isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
forDate={new Date(shift.startTime)}
useFilterDate={useFilterDate}
onUseFilterDateChange={(value) => setUseFilterDate(value)}>
<PublisherSearchBox
selectedId={null}
isFocused={isModalOpen}
filterDate={useFilterDate ? new Date(shift.startTime) : null}
onChange={(publisher) => {
// Add publisher as assignment logic
setIsModalOpen(false);
addAssignment(publisher, shift.id);
}}
showAllAuto={true}
showSearch={true}
showList={false}
/>
</Modal>
</div >
<PublisherSearchBox
selectedId={null}
isFocused={isModalOpen}
filterDate={useFilterDate ? new Date(shift.startTime) : null}
onChange={(publisher) => {
// Add publisher as assignment logic
setIsModalOpen(false);
addAssignment(publisher, shift.id);
}}
showAllAuto={true}
showSearch={true}
showList={false}
/>
</Modal>
</div >
)}</>
);
}

View File

@ -140,6 +140,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
onChange(selectedDate);
}
}
const handleShiftSelection = (selectedShift) => {
setSelectedShiftId(selectedShift.id);
const updatedPubs = availablePubs.map(pub => {
@ -535,6 +536,38 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
await axiosInstance.get(`/api/?action=copyOldAvailabilities&date=${common.getISODateOnly(value)}`);
}
async function handleCreateNewShift(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
//get last shift end time
let lastShift = shifts.sort((a, b) => new Date(b.endTime).getTime() - new Date(a.endTime).getTime())[0];
//default to 9:00 if no shifts
if (!lastShift) {
//get cart event id
var dayName = common.DaysOfWeekArray[value.getDayEuropean()];
const cartEvent = events.find(event => event.dayofweek == dayName);
lastShift = {
endTime: new Date(value.setHours(9, 0, 0, 0)),
cartEventId: cartEvent.id
};
}
const lastShiftEndTime = new Date(lastShift.endTime);
//add 90 minutes
const newShiftEndTime = new Date(lastShiftEndTime.getTime() + 90 * 60000);
await axiosInstance.post(`/api/data/shifts`, {
name: "Нова смяна",
startTime: lastShiftEndTime,
endTime: newShiftEndTime,
isPublished: false,
cartEvent: { connect: { id: lastShift.cartEventId } }
}).then((response) => {
console.log("New shift created:", response.data);
// setShifts([...shifts, response.data]);
handleCalDateChange(value);
}
).catch((error) => {
console.error("Error creating new shift:", error);
});
}
return (
<>
<Layout>
@ -621,22 +654,6 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
</div>
</div>
)}
{/* <button className={`button m-2 bg-blue-800 ${isOperationInProgress ? 'disabled' : ''}`} onClick={importShifts}>
{isOperationInProgress ? <div className="spinner"></div> : 'Import shifts (and missing Publishers) from WORD'}
</button>
// <button className="button m-2 bg-blue-800" onClick={() => generateShifts()}>Generate empty shifts</button>
// <button className="button m-2 bg-blue-800" onClick={() => generateShifts(true)}>Copy last month shifts</button>
// <button className="button m-2 bg-blue-800" onClick={() => generateShifts(true, true)}>Generate Auto shifts</button>
// <button className="button m-2 bg-blue-800" onClick={() => generateShifts(false, true, value)}>Generate Auto shifts DAY</button>
// <button className="button m-2" onClick={fetchShifts}>Fetch shifts</button>
// <button className="button m-2" onClick={sendMails}>Send mails</button>
// <button className="button m-2" onClick={generateXLS}>Generate XLSX</button>
// <button className="button m-2" onClick={async () => {
// await axiosInstance.get(`/api/shiftgenerate?action=delete&date=${common.getISODateOnly(value)}`);
// }
// }>Delete shifts (selected date's month)</button>
// <button className="button m-2" onClick={generateMonthlyStatistics}>Generate statistics</button>
*/}
</div>
</div>
{/* progress bar holder */}
@ -737,6 +754,12 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
allPublishersInfo={availablePubs} />
))}
</div>
<button
className="mt-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={handleCreateNewShift}
>
Добави нова смяна
</button>
</div>
</div>
@ -907,13 +930,6 @@ export const getServerSideProps = async (context) => {
const url = `/api/data/shifts?where={"startTime":{"$and":[{"$gte":"${common.getISODateOnly(firstDayOfMonth)}","$lt":"${common.getISODateOnly(lastDayOfMonth)}"}]}}`;
const prismaClient = common.getPrismaClient();
// let events = await prismaClient.cartEvent.findMany({ where: { isActive: true } });
// events = events.map(event => ({
// ...event,
// // Convert Date objects to ISO strings
// startTime: event.startTime.toISOString(),
// endTime: event.endTime.toISOString(),
// }));
const { data: events } = await axios.get(`/api/data/cartevents?where={"isActive":true}`);
//const { data: shifts } = await axios.get(url);