//import { getToken } from "next-auth/jwt"; import axiosServer from '../../src/axiosServer'; import { getToken } from "next-auth/jwt"; import type { NextApiRequest, NextApiResponse } from "next"; import { Prisma, PrismaClient, DayOfWeek, Publisher, Shift } from "@prisma/client"; import { levenshteinEditDistance } from "levenshtein-edit-distance"; import { filterPublishers, /* other functions */ } from './index'; import CAL from "../../src/helpers/calendar"; //const common = require("@common"); import common from "../../src/helpers/common"; import { Axios } from 'axios'; export default handler; async function handler(req: NextApiRequest, res: NextApiResponse) { console.log(req.url); console.log(req.query); const prisma = common.getPrismaClient(); // If you don't have the NEXTAUTH_SECRET environment variable set, // you will have to pass your secret as `secret` to `getToken` const axios = await axiosServer({ req: req, res: res }); const token = await getToken({ req: req }); if (!token) { // If no token or invalid token, return unauthorized status return res.status(401).json({ message: "Unauthorized" }); } // const token = req.headers.authorization.split('Bearer ')[1] // const { user } = await verify(token, process.env.NEXTAUTH_SECRET, { // maxAge: 30 * 24 * 60 * 60, // 30 days // }) // if (!user.roles.includes('admin')) { // res.status(401).json({ message: 'Unauthorized' }) // return // } // // if (!user.role == "adminer") { // if (token?.userRole !== "adminer") { // res.status(401).json({ message: "Unauthorized" }); // console.log("not authorized"); // return; // } // var result = { error: "Not authorized" }; var action = req.query.action; switch (action) { case "generate": var result = await GenerateSchedule(axios, req.query.date?.toString() || common.getISODateOnly(new Date()), common.parseBool(req.query.copyFromPreviousMonth), common.parseBool(req.query.autoFill), common.parseBool(req.query.forDay)); res.send(JSON.stringify(result.error?.toString())); break; case "delete": result = await DeleteSchedule(axios, req.query.date, common.parseBool(req.query.forDay)); res.send("deleted"); // JSON.stringify(result, null, 2) break; case "createcalendarevent": //CAL.GenerateICS(); result = await CreateCalendarForUser(req.query.id); res.send(result); // JSON.stringify(result, null, 2) break; case "test": var data = prisma.shift.findMany({ where: { isActive: true } }); res.send({ action: "OK", shifts: data, locations: prisma.location.findMany({ take: 10, // Limit the number of records to 10 orderBy: { name: 'asc' // Replace 'someField' with a field you want to sort by }, }) }); break; default: res.send("Invalid action"); break; } } // handle /api/data/schedule?date=2021-08-01&time=08:00:00&duration=60&service=1&provider=1 //Fix bugs in this code: async function GenerateSchedule(axios: Axios, date: string, copyFromPreviousMonth: boolean = false, autoFill: boolean = false, forDay: Boolean) { let missingPublishers: any[] = []; let publishersWithChangedPref: any[] = []; const prisma = common.getPrismaClient(); try { const monthInfo = common.getMonthDatesInfo(new Date(date)); const lastMonthInfo = common.getMonthDatesInfo(new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 1, 1)); //delete all shifts for this month if (forDay) { // Delete shifts only for the specific day await DeleteShiftsForDay(monthInfo.date); } else { // Delete all shifts for the entire month await DeleteShiftsForMonth(monthInfo); } console.log("finding shifts for previous 3 months for statistics (between " + new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 3, 1).toISOString() + " and " + monthInfo.firstDay.toISOString() + ")"); const { data: events } = await axios.get(`/api/data/cartevents?where={"isActive":{"$eq":true}}`); //// let [shiftsLastMonth, publishers] = await getShiftsAndPublishersForPreviousMonths(lastMonthInfo); //use filterPublishers from /pages/api/data/index.ts to get publishers with stats let shiftsLastMonth = await getShiftsFromLastMonth(lastMonthInfo); let publishers = await filterPublishers("id,firstName,lastName", null, lastMonthInfo.firstMonday, true, true, false); //let publishersWithStatsNew = await filterPublishers("id,firstName,lastName", null, monthInfo.firstMonday, true, true, false); //foreach day of the month check if there is an event for this day //if there is an event, then generate shifts for this day based on shiftduration and event start and end time //####################################################GPT########################################################### let shiftAssignments = []; let day = monthInfo.firstMonday; // Start from forDay if provided, otherwise start from first Monday let endDate = monthInfo.lastSunday; // End at forDay + 1 day if provided, otherwise end at last Sunday let dayNr = 1; // Start from the day number of forDay, or 1 for the entire month let weekNr = 1; // Start from the week number of forDay, or 1 for the entire month if (forDay) { day = monthInfo.date; endDate.setDate(monthInfo.date.getDate() + 1); dayNr = monthInfo.date.getDate(); weekNr = common.getWeekNumber(monthInfo.date); } let publishersThisWeek: any[] = []; console.log("\r\n"); console.log("###############################################"); console.log(" SHIFT GENERATION STARTED for " + common.getISODateOnly(monthInfo.date)); console.log("###############################################"); while (day < endDate) { const dayOfM = day.getDate(); let dayName = common.DaysOfWeekArray[day.getDayEuropean()]; console.log("[day " + dayNr + "] " + dayName + " " + dayOfM); //ToDo: rename event to cartEvent const event = events.find((event: { dayofweek: string }) => { return event.dayofweek == dayName; }); if (!event) { console.log("no event for " + dayName); day.setDate(day.getDate() + 1); continue; } event.startTime = new Date(event.startTime); event.endTime = new Date(event.endTime); var startTime = new Date(day); startTime.setHours(event.startTime.getHours()); startTime.setMinutes(event.startTime.getMinutes()); var endTime = new Date(day); endTime.setHours(event.endTime.getHours()); endTime.setMinutes(event.endTime.getMinutes()); var shiftStart = new Date(startTime); var shiftEnd = new Date(startTime); shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration); var shiftNr = 0; while (shiftEnd <= endTime) { shiftNr++; const __shiftName = String(shiftStart.getHours()).padStart(2, "0") + ":" + String(shiftStart.getMinutes()).padStart(2, "0") + " - " + String(shiftEnd.getHours()).padStart(2, "0") + ":" + String(shiftEnd.getMinutes()).padStart(2, "0"); shiftAssignments = []; let isTransportRequired = shiftNr == 1 || shiftEnd.getTime() == endTime.getTime(); console.log("[shift " + shiftNr + "] " + __shiftName + ", transport: " + (isTransportRequired ? "yes" : "no") + ", " + shiftStart.toLocaleTimeString() + " - " + shiftEnd.toLocaleTimeString() + " (end time: " + endTime.toLocaleTimeString() + ", " + event.shiftDuration + " min)"); if (autoFill || copyFromPreviousMonth) { // ########################################### // shift cache !!! // ########################################### // get last month attendance for this shift for each week, same day of the week and same shift const shiftLastMonthSameDay = getShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr); if (shiftLastMonthSameDay) { console.log("shiftCache: loaded shifts from '" + shiftLastMonthSameDay.startTime + "' for: " + day); //log shiftLastMonthSameDay.assignments.publisher names console.log("last month attendance for shift " + shiftNr + " (" + __shiftName + ") : " + shiftLastMonthSameDay.assignments.map((a: { publisher: { firstName: string; lastName: string; }; }) => a.publisher.firstName + " " + a.publisher.lastName).join(", ")); for (var i = 0; i < shiftLastMonthSameDay.assignments.length; i++) { let sameP = shiftLastMonthSameDay.assignments[i].publisher; let name = sameP.firstName + " " + sameP.lastName; console.log("shiftCache: considerig publisher: " + sameP.firstName + " " + sameP.lastName + ". Checking if he is available for this shift..."); //get availability for the same dayofweek and time (< startTime, > endTime) OR exact date (< startTime, > endTime) // Query for exact date match let availability = (await prisma.availability.findMany({ where: { publisherId: sameP.id, dayOfMonth: dayOfM, startTime: { lte: shiftStart, }, endTime: { gte: shiftEnd, }, }, }))[0] || null; if (copyFromPreviousMonth) { //copy from previous month without checking availability console.log("shiftCache: copy from previous month. Аvailability is " + (availability ? "available" : "not available") + ". Adding him to the new scedule as " + (availability ? "confirmed" : "tentative") + "."); shiftAssignments.push({ publisherId: sameP.id, isConfirmed: availability ? false : true }); } else { // check if the person filled the form this month const allAvailabilities = await prisma.availability.findMany({ where: { publisherId: sameP.id, isFromPreviousAssignment: false, }, }); // // ?? get the date on the same weeknr and dayofweek last month, and check if there is an availability for the same day of the week and required time // if (!availability) { // // check if there is an availability for the same day of the week and required time // availability = allAvailabilities.filter((a: { dayofweek: any; startTime: Date; endTime: Date; }) => { // return a.dayofweek === event.dayofweek && a.startTime <= startTime && a.endTime >= endTime; // })[0] || null; // } // var availability = allAvailabilities.find((a) => { // return (a.dayofweek === event.dayofweek && a.dayOfMonth == null) || a.dayOfMonth == dayOfM; // }); //publishers not filled the form will not have an email with @, but rather as 'firstname.lastname'. //We will add them to the schedule as manual override until they fill the form //ToDo this logic is not valid in all cases. if (!availability && sameP.email.includes("@")) { if (!publishersWithChangedPref.includes(name)) { //publishersWithChangedPref.push(name); } console.log("shiftCache: publisher is not available for this shift. Available days: " + allAvailabilities.filter((a: { dayOfMonth: any; }) => a.dayOfMonth === dayOfM).map((a) => a.dayofweek + " " + a.dayOfMonth).join(", ")); //continue; } if (availability) { console.log("shiftCache: publisher is available for this shift. Available days: " + availability.dayofweek + " " + availability.dayOfMonth + " " + availability.startTime + " - " + availability.endTime); console.log("shiftCache: publisher is available for this shift OR manual override is set. Adding him to the new scedule."); shiftAssignments.push({ publisherId: sameP.id }); } else { // skip publishers without availability now // console.warn("NO publisher availability found! for previous assignment for " + name + ". Assuming he does not have changes in his availability. !!! ADD !!! him to the new scedule but mark him as missing."); // if (!missingPublishers.includes(name)) { // missingPublishers.push(name); // } // try { // console.log("shiftCache: publisher was last month assigned to this shift but he is not in the system. Adding him to the system with id: " + sameP.id); // shiftAssignments.push({ publisherId: sameP.id, }); // } catch (e) { // console.error(`shiftCache: error adding MANUAL publisher to the system(${sameP.email} ${sameP.firstName} ${sameP.lastName}): ` + e); // } } } } // ########################################### // shift CACHE END // ########################################### console.log("searching available publisher for " + dayName + " " + __shiftName); if (!copyFromPreviousMonth) { /* We chave the following data: availabilities:(6) [{…}, {…}, {…}, {…}, {…}, {…}] currentDayAssignments:0 currentMonthAssignments:2 currentMonthAvailability:(2) [{…}, {…}] currentMonthAvailabilityDaysCount:2 currentMonthAvailabilityHoursCount:3 currentWeekAssignments:0 firstName:'Алесия' id:'clqjtcrqj0008oio8kan5lkjn' lastName:'Сейз' previousMonthAssignments:2 */ // until we reach event.numberOfPublishers, we will try to fill the shift with publishers from allAvailablePublishers with the following priority: // do multiple passes, reecalculating availabilityIndex for each publisher after each pass. // !!! Never assign the same publisher twice to the same day! (currentDayAssignments > 0) // PASS 1: Prioritize publishers with little currentMonthAvailabilityHoursCount ( < 5 ), as they may not have another opportunity to serve this month // PASS 2: try to fill normally based on availabilityIndex, excluding those who were assigned this week // PASS 3: try to fill normally based on availabilityIndex, including those who were assigned this week and weighting the desiredShiftsPerMonth // PASS 4: include those without availability this month - based on old availabilities and assignments for this day of the week. // push found publisers to shiftAssignments with: .push({ publisherId: publisher.id }); and update publisher stats in new function: addAssignmentToPublisher(shiftAssignments, publisher) // ---------------------------------- new code ---------------------------------- // // get all publishers who are available for this SPECIFIC day and WEEKDAY const queryParams = new URLSearchParams({ action: 'filterPublishers', assignments: 'true', availabilities: 'true', date: common.getISODateOnly(shiftStart), select: 'id,firstName,lastName,isActive,desiredShiftsPerMonth' }); let allAvailablePublishers = (await axios.get(`/api/?${queryParams.toString()}`)).data; let availablePublishers = allAvailablePublishers; let publishersNeeded = Math.max(0, event.numberOfPublishers - shiftAssignments.length); // LEVEL 1: Prioritize publishers with little currentMonthAvailabilityHoursCount ( < 5 ), as they may not have another opportunity to serve this month // get publishers with little currentMonthAvailabilityHoursCount ( < 5 ) // let availablePublishers = allAvailablePublishers.filter((p: { currentMonthAvailabilityHoursCount: number; }) => p.currentMonthAvailabilityHoursCount < 5); // // log all available publishers with their currentMonthAvailabilityHoursCount // console.info("PASS 1: availablePublishers for this shift with currentMonthAvailabilityHoursCount < 5: " + availablePublishers.length + " (" + publishersNeeded + " needed)"); // availablePublishers.slice(0, publishersNeeded).forEach((p: { id: any; }) => { addAssignmentToPublisher(shiftAssignments, p); }); // publishersNeeded = Math.max(0, event.numberOfPublishers - shiftAssignments.length); // LEVEL 2+3: try to fill normally based on availabilityIndex, excluding those who were assigned this week // get candidates that are not assigned this week, and which have not been assigned this month as mutch as the last month. // calculate availabilityIndex for each publisher based on various factors: // 1. currentMonthAssignments - lastMonth (weight 50%) // 2. desiredShiftsPerMonth (weight 30%) // 3. publisher type (weight 20%) - regular, auxiliary, pioneer, special, bethel, etc.. (see publisherType in publisher model). exclude betelites who were assigned this month. (index =) //calculate availabilityIndex: allAvailablePublishers.forEach((p: { currentMonthAssignments: number; desiredShiftsPerMonth: number; publisherType: string; }) => { // 1. currentMonthAssignments - lastMonth (weight 50%) // 2. desiredShiftsPerMonth (weight 30%) // 3. publisher type (weight 20%) - regular, auxiliary, pioneer, special, bethel, etc.. (see publisherType in publisher model). exclude betelites who were assigned this month. (index =) p.availabilityIndex = Math.round(((p.currentMonthAssignments - p.previousMonthAssignments) * 0.5 + p.desiredShiftsPerMonth * 0.3 + (p.publisherType === "bethelite" ? 0 : 1) * 0.2) * 100) / 100; }); // use the availabilityIndex to sort the publishers // LEVEL 2: remove those who are already assigned this week (currentWeekAssignments > 0), order by !availabilityIndex availablePublishers = allAvailablePublishers.filter((p: { currentWeekAssignments: number; }) => p.currentWeekAssignments === 0) .sort((a: { availabilityIndex: number; }, b: { availabilityIndex: number; }) => a.availabilityIndex - b.availabilityIndex); console.warn("PASS 2: availablePublishers for this shift after removing already assigned this week: " + availablePublishers.length + " (" + publishersNeeded + " needed)"); availablePublishers.slice(0, publishersNeeded).forEach((p: { id: any; }) => { addAssignmentToPublisher(shiftAssignments, p); }); publishersNeeded = Math.max(0, event.numberOfPublishers - shiftAssignments.length); // LEVEL 3: order by !availabilityIndex availablePublishers = allAvailablePublishers.sort((a: { availabilityIndex: number; }, b: { availabilityIndex: number; }) => a.availabilityIndex - b.availabilityIndex); console.warn("PASS 3: availablePublishers for this shift including already assigned this week: " + availablePublishers.length + " (" + publishersNeeded + " needed)"); availablePublishers.slice(0, publishersNeeded).forEach((p: { id: any; }) => { addAssignmentToPublisher(shiftAssignments, p); }); publishersNeeded = Math.max(0, event.numberOfPublishers - shiftAssignments.length); // LEVEL 4: include those without availability this month - based on old availabilities and assignments for this day of the week. // get candidates that are not assigned this week, and which have not been assigned this month as mutch as the last month. //query the api again for all publishers with assignments and availabilities for this day of the week including from old assignments (set filterPublishers to false) availablePublishers = await filterPublishers("id,firstName,lastName", null, shiftStart, false, true, true); console.warn("PASS 4: availablePublishers for this shift including weekly and old assignments: " + availablePublishers.length + " (" + publishersNeeded + " needed)"); function oldCode() { // ---------------------------------- old code ---------------------------------- // // console.warn("allAvailablePublishers: " + allAvailablePublishers.length); // // remove those who are already assigned this week (currentWeekAssignments > 0)//, # OLD: order by !availabilityIndex // let availablePublishers = allAvailablePublishers.filter((p: { currentWeekAssignments: number; }) => p.currentWeekAssignments === 0); // console.warn("availablePublishers for this shift after removing already assigned this week: " + availablePublishers.length + " (" + (event.numberOfPublishers - shiftAssignments.length) + " needed)"); // if (availablePublishers.length === 0) { // console.error(`------------------- no available publishers for ${dayName} ${dayOfM}!!! -------------------`); // // Skipping the rest of the code execution // //return; // } // let msg = `FOUND ${availablePublishers.length} publishers for ${dayName} ${dayOfM}, ${__shiftName} . ${event.numberOfPublishers - shiftAssignments.length} needed\r\n: `; // msg += availablePublishers.map((p: { firstName: any; lastName: any; asignmentsThisMonth: any; availabilityIndex: any; }) => `${p.firstName} ${p.lastName} (${p.asignmentsThisMonth}:${p.availabilityIndex})`).join(", "); // console.log(msg); // // ---------------------------------- old code ---------------------------------- // } // end of old code } } } //############################################################################################################### // create shift assignmens //############################################################################################################### // using prisma client: // https://stackoverflow.com/questions/65950407/prisma-many-to-many-relations-create-and-connect // connect publishers to shift const createdShift = await prisma.shift.create({ data: { startTime: shiftStart, endTime: shiftEnd, name: event.dayofweek + " " + shiftStart.toLocaleTimeString() + " - " + shiftEnd.toLocaleTimeString(), requiresTransport: isTransportRequired, cartEvent: { connect: { id: event.id, }, }, assignments: { create: shiftAssignments.map((a) => { return { publisher: { connect: { id: a.publisherId } }, isConfirmed: a.isConfirmed }; }), }, }, }); shiftStart = new Date(shiftEnd); shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration); } day.setDate(day.getDate() + 1); dayNr++; let weekDay = common.DaysOfWeekArray[day.getDayEuropean()] if (weekDay == DayOfWeek.Sunday) { weekNr++; publishersThisWeek = []; publishers.forEach((p: { currentWeekAssignments: number; }) => { p.currentWeekAssignments = 0; }); } //the whole day is done, go to next day. break if we are generating for a specific day if (forDay) { break; } } //###################################################GPT############################################################ if (!forDay) { const fs = require("fs"); //fs.writeFileSync("./content/publisherShiftStats.json", JSON.stringify(publishers, null, 2)); //fs.writeFileSync("./content/publishersWithChangedPref.json", JSON.stringify(publishersWithChangedPref, null, 2)); //fs.writeFileSync("./content/missingPublishers.json", JSON.stringify(missingPublishers, null, 2)); console.log("###############################################"); console.log(" DONE CREATING SCHEDULE FOR " + monthInfo.monthName + " " + monthInfo.year); console.log("###############################################"); } //create shifts using API // const { data: createdShifts } = await axios.post(`${process.env.PUBLIC_URL}/api/data/shifts`, shiftsToCreate); //const { data: allshifts } = await axios.get(`/api/data/shifts`); return {}; //allshifts; } catch (error) { console.log(error); return { error: error }; } } function addAssignmentToPublisher(shiftAssignments: any[], publisher: Publisher) { shiftAssignments.push({ publisherId: publisher.id }); publisher.currentWeekAssignments++ || 1; publisher.currentDayAssignments++ || 1; publisher.currentMonthAssignments++ || 1; //console.log(`manual assignment: ${dayName} ${dayOfM} ${shiftStart}:${shiftEnd} ${p.firstName} ${p.lastName} ${p.availabilityIndex} ${p.currentMonthAssignments}`); console.log(`manual assignment: ${publisher.firstName} ${publisher.lastName} ${publisher.currentMonthAssignments}`); return publisher; } async function DeleteShiftsForMonth(monthInfo: any) { try { const prisma = common.getPrismaClient(); await prisma.shift.deleteMany({ where: { startTime: { gte: monthInfo.firstMonday, lt: monthInfo.lastSunday, }, }, }); } catch (e) { console.log(e); } } async function DeleteShiftsForDay(date: Date) { const prisma = common.getPrismaClient(); try { // Assuming shifts do not span multiple days, so equality comparison is used await prisma.shift.deleteMany({ where: { startTime: { gte: date, lt: new Date(date.getTime() + 86400000), // +1 day in milliseconds }, }, }); } catch (e) { console.log(e); } } async function getShiftsFromLastMonth(monthInfo) { const prisma = common.getPrismaClient(); // Fetch shifts for the month const rawShifts = await prisma.shift.findMany({ where: { startTime: { gte: monthInfo.firstMonday, lte: monthInfo.lastSunday, }, }, include: { assignments: { include: { publisher: true, }, }, }, }); // Process shifts to add weekNr and shiftNr return rawShifts.map(shift => ({ ...shift, weekNr: common.getWeekNumber(new Date(shift.startTime)), shiftNr: rawShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(shift.startTime)).indexOf(shift) + 1, weekDay: common.DaysOfWeekArray[new Date(shift.startTime).getDayEuropean()], })); } function getShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr) { let weekDay = common.DaysOfWeekArray[day.getDayEuropean()]; return shiftsLastMonth.find(s => { return s.weekNr === weekNr && s.shiftNr === shiftNr && s.weekDay === weekDay; }); } /** * Dangerous function that deletes all shifts and publishers. * @param date * @returns */ async function DeleteSchedule(axios: Axios, date: Date, forDay: Boolean | undefined) { try { let monthInfo = common.getMonthDatesInfo(new Date(date)); if (forDay) { // Delete shifts only for the specific day await DeleteShiftsForDay(monthInfo.date); } else { // Delete all shifts for the entire month await DeleteShiftsForMonth(monthInfo); } } catch (error) { console.log(error); return { error: error }; } } async function CreateCalendarForUser(eventId: string | string[] | undefined) { try { //CAL.authorizeNew(); CAL.createEvent(eventId); } catch (error) { console.log(error); return { error: error }; } } /* obsolete? */ async function ImportShiftsFromDocx(axios: Axios) { try { const { data: shifts } = await axios.get(`/api/data/shifts`); shifts.forEach(async (shift: { id: any; }) => { await axios.delete(`/api/data/shifts/${shift.id}`); }); const { data: shiftsToCreate } = await axios.get(`/api/data/shiftsToCreate`); shiftsToCreate.forEach(async (shift: any) => { await axios.post(`/api/data/shifts`, shift); }); } catch (error) { console.log(error); return { error: error }; } } /** * Retrieves shifts and publishers for the previous months based on the given month information. * @deprecated This function is deprecated and will be removed in future versions. Use `filterPublishers` from `/pages/api/data/index.ts` instead. * @param monthInfo - An object containing information about the last month, including its first day and last Sunday. * @returns A Promise that resolves to an array containing the publishers for the previous months. */ // async function getShiftsAndPublishersForPreviousMonths(monthInfo: { firstDay: any; lastSunday: any; firstMonday: any; nrOfWeeks: number; }) { // const prisma = common.getPrismaClient(); //old: (global as any).prisma; // const [shiftsLastMonth, initialPublishers] = await Promise.all([ // prisma.shift.findMany({ // where: { // startTime: { // gte: monthInfo.firstDay, // lte: monthInfo.lastSunday, // }, // }, // include: { // assignments: { // include: { // publisher: true, // }, // }, // }, // }), // prisma.publisher.findMany({ // where: { // isActive: true, // }, // include: { // availabilities: { // where: { // isActive: true, // }, // }, // assignments: { // include: { // shift: true, // }, // }, // }, // }), // ]); // // Group shifts by day // function getDayFromDate(date: Date) { // return date.toISO String().split('T')[0]; // } // const groupedShifts = shiftsLastMonth.reduce((acc: { [x: string]: any[]; }, shift: { startTime: string | number | Date; }) => { // const day = getDayFromDate(new Date(shift.startTime)); // if (!acc[day]) { // acc[day] = []; // } // acc[day].push(shift); // return acc; // }, {}); // //temp fix - calculate shift.weekNr // const updatedShiftsLastMonth = []; // for (const day in groupedShifts) { // const shifts = groupedShifts[day]; // for (let i = 0; i < shifts.length; i++) { // const shift = shifts[i]; // updatedShiftsLastMonth.push({ // ...shift, // weekNr: common.getWeekNumber(shift.startTime) + 1, // shiftNr: i + 1 // The shift number for the day starts from 1 // }); // } // } // const publishers = initialPublishers.map((publisher: { assignments: any[]; desiredShiftsPerMonth: number; }) => { // // const lastMonthStartDate = new Date(date.getFullYear(), date.getMonth() - 1, 1); // // const last2MonthsStartDate = new Date(date.getFullYear(), date.getMonth() - 2, 1); // const filterAssignmentsByDate = (startDate: any, endDate: any) => // publisher.assignments.filter((assignment: { shift: { startTime: string | number | Date; }; }) => isDateBetween(new Date(assignment.shift.startTime), startDate, endDate)); // const lastMonthAssignments = filterAssignmentsByDate(monthInfo.firstMonday, monthInfo.lastSunday); // //const last2MonthsAssignments = filterAssignmentsByDate(last2MonthsStartDate, monthInfo.firstMonday); // const desiredShifts = publisher.desiredShiftsPerMonth * (monthInfo.nrOfWeeks / 4); // const availabilityIndex = Math.round((lastMonthAssignments.length / desiredShifts) * 100) / 100; // return { // ...publisher, // availabilityIndex, // currentWeekAssignments: 0, // currentMonthAssignments: 0, // assignmentsLastMonth: lastMonthAssignments.length, // //assignmentsLast2Months: last2MonthsAssignments.length, // }; // }); // return [updatedShiftsLastMonth, publishers]; // } // ********************************************************************************************************************* //region helpers // *********************************************************************************************************************