new coverMe routine; refactoring
This commit is contained in:
147
components/publisher/SearchReplacement.js
Normal file
147
components/publisher/SearchReplacement.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import axiosInstance from '../../src/axiosSecure';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { set } from 'date-fns';
|
||||||
|
|
||||||
|
function SearchReplacement({ shiftId, assignmentId }) {
|
||||||
|
const [users, setUsers] = useState([]);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
// Dummy endpoint and shiftId, replace with actual
|
||||||
|
const response = await axiosInstance.get('/api/?action=getPossibleShiftPublisherEmails&shiftId=' + shiftId);
|
||||||
|
setUsers(response.data);
|
||||||
|
setShowModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendCoverMeRequestByEmail = (selectedGroups) => {
|
||||||
|
// You can map 'selectedGroups' to determine which API calls to make
|
||||||
|
console.log("Selected Groups:", selectedGroups);
|
||||||
|
axiosInstance.post('/api/email?action=sendCoverMeRequestByEmail', {
|
||||||
|
assignmentId: assignmentId,
|
||||||
|
toSubscribed: selectedGroups.includes('subscribedPublishers'),
|
||||||
|
toAvailable: selectedGroups.includes('availablePublishers'),
|
||||||
|
}).then(response => {
|
||||||
|
console.log("response", response);
|
||||||
|
setShowModal(false);
|
||||||
|
//toast success and confirm the change
|
||||||
|
toast.success("Заявката за заместник е изпратена!", {
|
||||||
|
onClose: () => {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
console.log("error", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="mr-2 mb-2 inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
onClick={() => fetchUsers(shiftId)}
|
||||||
|
>
|
||||||
|
Търси заместник
|
||||||
|
</button>
|
||||||
|
{
|
||||||
|
showModal && (
|
||||||
|
<ConfirmationModal
|
||||||
|
isOpen={showModal}
|
||||||
|
onClose={() => setShowModal(false)}
|
||||||
|
onConfirm={sendCoverMeRequestByEmail}
|
||||||
|
subscribedPublishers={users.subscribedPublishers}
|
||||||
|
availablePublishers={users.availablePublishers}
|
||||||
|
/>
|
||||||
|
// <ConfirmationModal
|
||||||
|
// isOpen={showModal}
|
||||||
|
// users={users}
|
||||||
|
// onClose={() => setShowModal(false)}
|
||||||
|
// onConfirm={(selectedUsers) => {
|
||||||
|
// console.log(selectedUsers); // Here you would call the email API
|
||||||
|
// setShowModal(false);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConfirmationModal({ isOpen, onClose, onConfirm, subscribedPublishers, availablePublishers }) {
|
||||||
|
const [selectedGroups, setSelectedGroups] = useState([]);
|
||||||
|
|
||||||
|
const handleToggleGroup = (groupName) => {
|
||||||
|
setSelectedGroups(prev => {
|
||||||
|
if (prev.includes(groupName)) {
|
||||||
|
return prev.filter(name => name !== groupName);
|
||||||
|
} else {
|
||||||
|
return [...prev, groupName];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||||
|
<div className="absolute inset-0 bg-black opacity-50" onClick={onClose}></div>
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow-lg z-10">
|
||||||
|
<h2 className="text-lg font-semibold mb-4">Можете да изпратите заявка за заместник до следните групи:</h2>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block mb-2">
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mr-2 leading-tight"
|
||||||
|
checked={selectedGroups.includes('subscribedPublishers')}
|
||||||
|
onChange={() => handleToggleGroup('subscribedPublishers')}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">Абонирани:</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap">
|
||||||
|
{subscribedPublishers.map(pub => (
|
||||||
|
<span key={pub.id} className="bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">{pub.name}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block mb-2">
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mr-2 leading-tight"
|
||||||
|
checked={selectedGroups.includes('availablePublishers')}
|
||||||
|
onChange={() => handleToggleGroup('availablePublishers')}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">На разположение:</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap">
|
||||||
|
{availablePublishers.map(pub => (
|
||||||
|
<span key={pub.id} className="bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">{pub.name}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<button
|
||||||
|
className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 mr-2"
|
||||||
|
onClick={() => onConfirm(selectedGroups)}
|
||||||
|
>
|
||||||
|
Потвърждавам
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="bg-gray-300 px-4 py-2 rounded hover:bg-gray-400"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Отказ
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default SearchReplacement;
|
@ -5,7 +5,7 @@ const withPWA = require('next-pwa')({
|
|||||||
register: true, // ?
|
register: true, // ?
|
||||||
publicExcludes: ["!_error*.js"], //?
|
publicExcludes: ["!_error*.js"], //?
|
||||||
|
|
||||||
disable: process.env.NODE_ENV === 'development',
|
//disable: process.env.NODE_ENV === 'development',
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = withPWA({
|
module.exports = withPWA({
|
||||||
|
@ -4,6 +4,7 @@ import { getToken } from "next-auth/jwt";
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { createRouter, expressWrapper } from "next-connect";
|
import { createRouter, expressWrapper } from "next-connect";
|
||||||
const common = require('../../src/helpers/common');
|
const common = require('../../src/helpers/common');
|
||||||
|
const data = require('../../src/helpers/data');
|
||||||
const emailHelper = require('../../src/helpers/email');
|
const emailHelper = require('../../src/helpers/email');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const CON = require("../../src/helpers/const");
|
const CON = require("../../src/helpers/const");
|
||||||
@ -213,9 +214,10 @@ export default async function handler(req, res) {
|
|||||||
//get from POST data: shiftId, assignmentId, date
|
//get from POST data: shiftId, assignmentId, date
|
||||||
//let shiftId = req.body.shiftId;
|
//let shiftId = req.body.shiftId;
|
||||||
let assignmentId = req.body.assignmentId;
|
let assignmentId = req.body.assignmentId;
|
||||||
let date = req.body.date;
|
|
||||||
|
|
||||||
console.log("User: " + user.email + " sent a 'CoverMe' request for his assignment " + assignmentId + " " + date);
|
let toSubscribed = req.body.toSubscribed;
|
||||||
|
let toAvailable = req.body.toAvailable;
|
||||||
|
|
||||||
|
|
||||||
let assignment = await prisma.assignment.findUnique({
|
let assignment = await prisma.assignment.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@ -233,6 +235,8 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log("User: " + user.email + " sent a 'CoverMe' request for his assignment " + assignmentId + " - " + assignment.shift.cartEvent.location.name + " " + assignment.shift.startTime.toISOString());
|
||||||
|
|
||||||
|
|
||||||
// update the assignment. generate new publicGuid, isConfirmed to false
|
// update the assignment. generate new publicGuid, isConfirmed to false
|
||||||
let newPublicGuid = uuidv4();
|
let newPublicGuid = uuidv4();
|
||||||
@ -246,29 +250,43 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//get all subscribed publisers
|
let subscribedPublishers = [], availablePublishers = [];
|
||||||
const subscribedPublishers = await prisma.publisher.findMany({
|
if (toSubscribed) {
|
||||||
where: {
|
//get all subscribed publisers
|
||||||
isSubscribedToCoverMe: true
|
subscribedPublishers = await prisma.publisher.findMany({
|
||||||
}
|
where: {
|
||||||
});
|
isSubscribedToCoverMe: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (toAvailable) {
|
||||||
|
availablePublishers = await data.filterPublishersNew("id,firstName,lastName,email", new Date(assignment.shift.startTime), true, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
//concat and remove duplicate emails
|
||||||
|
let pubsToSend = subscribedPublishers.concat(availablePublishers).
|
||||||
|
filter((item, index, self) =>
|
||||||
|
index === self.findIndex((t) => (
|
||||||
|
t.email === item.email
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
//send email to all subscribed publishers
|
//send email to all subscribed publishers
|
||||||
for (let i = 0; i < subscribedPublishers.length; i++) {
|
for (let i = 0; i < pubsToSend.length; i++) {
|
||||||
if (subscribedPublishers[i].id == user.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//send email to subscribed publisher
|
//send email to subscribed publisher
|
||||||
let acceptUrl = process.env.NEXTAUTH_URL + "/api/email?action=email_response&emailaction=coverMeAccept&userId=" + subscribedPublishers[i].id + "&shiftId=" + assignment.shiftId + "&assignmentPID=" + newPublicGuid;
|
let acceptUrl = process.env.NEXTAUTH_URL + "/api/email?action=email_response&emailaction=coverMeAccept&userId=" + pubsToSend[i].id + "&shiftId=" + assignment.shiftId + "&assignmentPID=" + newPublicGuid;
|
||||||
|
|
||||||
let model = {
|
let model = {
|
||||||
user: user,
|
user: user,
|
||||||
shiftId: assignment.shiftId,
|
shiftId: assignment.shiftId,
|
||||||
acceptUrl: acceptUrl,
|
acceptUrl: acceptUrl,
|
||||||
prefix: user.isMale ? "Брат" : "Сестра",
|
prefix: user.isMale ? "Брат" : "Сестра",
|
||||||
firstName: subscribedPublishers[i].firstName,
|
firstName: pubsToSend[i].firstName,
|
||||||
lastName: subscribedPublishers[i].lastName,
|
lastName: pubsToSend[i].lastName,
|
||||||
email: subscribedPublishers[i].email,
|
email: pubsToSend[i].email,
|
||||||
placeName: assignment.shift.cartEvent.location.name,
|
placeName: assignment.shift.cartEvent.location.name,
|
||||||
dateStr: common.getDateFormated(assignment.shift.startTime),
|
dateStr: common.getDateFormated(assignment.shift.startTime),
|
||||||
time: common.formatTimeHHmm(assignment.shift.startTime),
|
time: common.formatTimeHHmm(assignment.shift.startTime),
|
||||||
@ -276,8 +294,8 @@ export default async function handler(req, res) {
|
|||||||
};
|
};
|
||||||
let results = emailHelper.SendEmailHandlebars(
|
let results = emailHelper.SendEmailHandlebars(
|
||||||
{
|
{
|
||||||
name: subscribedPublishers[i].firstName + " " + subscribedPublishers[i].lastName,
|
name: pubsToSend[i].firstName + " " + pubsToSend[i].lastName,
|
||||||
email: subscribedPublishers[i].email
|
email: pubsToSend[i].email
|
||||||
}, "coverMe", model);
|
}, "coverMe", model);
|
||||||
// if (results) {
|
// if (results) {
|
||||||
// console.log("Error sending email: " + error);
|
// console.log("Error sending email: " + error);
|
||||||
@ -285,10 +303,12 @@ export default async function handler(req, res) {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
if (results) {
|
if (results) {
|
||||||
console.log("Email sent to: " + subscribedPublishers[i].email);
|
console.log("Email sent to: " + pubsToSend[i].email);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
res.status(200).json({ message: "CoverMe request sent" });
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return res.status(400).json({ message: "Invalid action" });
|
return res.status(400).json({ message: "Invalid action" });
|
||||||
|
@ -347,6 +347,43 @@ export default async function handler(req, res) {
|
|||||||
res.status(200).json({ "message": "ok" });
|
res.status(200).json({ "message": "ok" });
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case "getPossibleShiftPublisherEmails":
|
||||||
|
const subscribedPublishers = await prisma.publisher.findMany({
|
||||||
|
where: {
|
||||||
|
isSubscribedToCoverMe: true
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true
|
||||||
|
}
|
||||||
|
}).then(pubs => {
|
||||||
|
return pubs.map(pub => {
|
||||||
|
return {
|
||||||
|
id: pub.id,
|
||||||
|
name: pub.firstName + " " + pub.lastName,
|
||||||
|
email: pub.email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let shift = await prisma.shift.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parseInt(req.query.shiftId)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let availablePublishers = await filterPublishersNew_Available("id,firstName,lastName,email", new Date(shift.startTime), true, false);
|
||||||
|
//return names and email info only
|
||||||
|
availablePublishers = availablePublishers.map(pub => {
|
||||||
|
return {
|
||||||
|
id: pub.id,
|
||||||
|
name: pub.firstName + " " + pub.lastName,
|
||||||
|
email: pub.email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.status(200).json({ shift, availablePublishers: availablePublishers, subscribedPublishers });
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
@ -425,254 +462,7 @@ export async function getMonthlyStatistics(selectFields, filterDate) {
|
|||||||
|
|
||||||
|
|
||||||
export async function filterPublishersNew_Available(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true) {
|
export async function filterPublishersNew_Available(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true) {
|
||||||
|
return data.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, isWithStats);
|
||||||
// Only attempt to split if selectFields is a string; otherwise, use it as it is.
|
|
||||||
selectFields = typeof selectFields === 'string' ? selectFields.split(",") : selectFields;
|
|
||||||
|
|
||||||
let selectBase = selectFields.reduce((acc, curr) => {
|
|
||||||
acc[curr] = true;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
selectBase.assignments = {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
shift: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
shift: {
|
|
||||||
startTime: {
|
|
||||||
gte: filterDate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var monthInfo = common.getMonthDatesInfo(filterDate);
|
|
||||||
var weekNr = common.getWeekOfMonth(filterDate); //getWeekNumber
|
|
||||||
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(filterDate);
|
|
||||||
if (!isExactTime) {
|
|
||||||
filterDate.setHours(0, 0, 0, 0); // Set to midnight
|
|
||||||
}
|
|
||||||
const filterDateEnd = new Date(filterDate);
|
|
||||||
filterDateEnd.setHours(23, 59, 59, 999);
|
|
||||||
|
|
||||||
|
|
||||||
let whereClause = {};
|
|
||||||
//if full day, match by date only
|
|
||||||
if (!isExactTime) { // Check only by date without considering time ( Assignments on specific days without time)
|
|
||||||
whereClause["availabilities"] = {
|
|
||||||
some: {
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
startTime: { gte: filterDate },
|
|
||||||
endTime: { lte: filterDateEnd },
|
|
||||||
}
|
|
||||||
,
|
|
||||||
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
|
||||||
// This includes availabilities from previous assignments but not with preference
|
|
||||||
{
|
|
||||||
dayOfMonth: null, // includes monthly and weekly repeats
|
|
||||||
dayofweek: dayOfWeekEnum,
|
|
||||||
// ToDo: and weekOfMonth
|
|
||||||
startTime: { lte: filterDate },
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
|
||||||
{ endDate: { gte: filterDate } },
|
|
||||||
{ endDate: null }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//if not full day, match by date and time
|
|
||||||
else {
|
|
||||||
//match exact time (should be same as data.findPublisherAvailability())
|
|
||||||
whereClause["availabilities"] = {
|
|
||||||
some: {
|
|
||||||
OR: [
|
|
||||||
// Check if dayOfMonth is set and filterDate is between start and end dates (Assignments on specific days AND time)
|
|
||||||
{
|
|
||||||
// dayOfMonth: filterDate.getDate(),
|
|
||||||
startTime: { lte: filterDate },
|
|
||||||
endTime: { gte: filterDate }
|
|
||||||
},
|
|
||||||
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
|
||||||
{
|
|
||||||
dayOfMonth: null,
|
|
||||||
dayofweek: dayOfWeekEnum,
|
|
||||||
startTime: { gte: filterDate },
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
|
||||||
{ endDate: { gte: filterDate } },
|
|
||||||
{ endDate: null }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (isForTheMonth) {
|
|
||||||
// If no filter date, return all publishers's availabilities for currentMonthStart
|
|
||||||
whereClause["availabilities"] = {
|
|
||||||
some: {
|
|
||||||
OR: [
|
|
||||||
// Check if dayOfMonth is not null and startTime is after currentMonthStart (Assignments on specific days AND time)
|
|
||||||
{
|
|
||||||
dayOfMonth: { not: null },
|
|
||||||
startTime: { gte: currentMonthStart },
|
|
||||||
endTime: { lte: currentMonthEnd }
|
|
||||||
},
|
|
||||||
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
|
||||||
{
|
|
||||||
dayOfMonth: null,
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
|
||||||
{ endDate: { gte: filterDate } },
|
|
||||||
{ endDate: null }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`getting publishers for date: ${filterDate}, isExactTime: ${isExactTime}, isForTheMonth: ${isForTheMonth}`);
|
|
||||||
//include availabilities if flag is true
|
|
||||||
const prisma = common.getPrismaClient(); //why we need to get it again?
|
|
||||||
let publishers = await prisma.publisher.findMany({
|
|
||||||
where: whereClause,
|
|
||||||
select: {
|
|
||||||
...selectBase,
|
|
||||||
availabilities: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`publishers: ${publishers.length}, WhereClause: ${JSON.stringify(whereClause)}`);
|
|
||||||
|
|
||||||
// convert matching weekly availabilities to availabilities for the day to make furter processing easier on the client.
|
|
||||||
// we trust that the filtering was OK, so we use the dateFilter as date.
|
|
||||||
publishers.forEach(pub => {
|
|
||||||
pub.availabilities = pub.availabilities.map(avail => {
|
|
||||||
if (avail.dayOfMonth == null) {
|
|
||||||
let newStart = new Date(filterDate);
|
|
||||||
newStart.setHours(avail.startTime.getHours(), avail.startTime.getMinutes(), 0, 0);
|
|
||||||
let newEnd = new Date(filterDate);
|
|
||||||
newEnd.setHours(avail.endTime.getHours(), avail.endTime.getMinutes(), 0, 0);
|
|
||||||
return {
|
|
||||||
...avail,
|
|
||||||
startTime: newStart,
|
|
||||||
endTime: newEnd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return avail;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
let currentWeekStart: Date, currentWeekEnd: Date,
|
|
||||||
currentMonthStart: Date, currentMonthEnd: Date,
|
|
||||||
previousMonthStart: Date, previousMonthEnd: Date;
|
|
||||||
|
|
||||||
if (isWithStats) {
|
|
||||||
currentWeekStart = common.getStartOfWeek(filterDate);
|
|
||||||
currentWeekEnd = common.getEndOfWeek(filterDate);
|
|
||||||
currentMonthStart = monthInfo.firstMonday;
|
|
||||||
currentMonthEnd = monthInfo.lastSunday;
|
|
||||||
let prevMnt = new Date(filterDate)
|
|
||||||
prevMnt.setMonth(prevMnt.getMonth() - 1);
|
|
||||||
monthInfo = common.getMonthDatesInfo(prevMnt);
|
|
||||||
previousMonthStart = monthInfo.firstMonday;
|
|
||||||
previousMonthEnd = monthInfo.lastSunday;
|
|
||||||
|
|
||||||
//get if publisher has assignments for current weekday, week, current month, previous month
|
|
||||||
publishers.forEach(pub => {
|
|
||||||
// Filter assignments for current day
|
|
||||||
pub.currentDayAssignments = pub.assignments?.filter(assignment => {
|
|
||||||
return assignment.shift.startTime >= filterDate && assignment.shift.startTime <= filterDateEnd;
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
// Filter assignments for current week
|
|
||||||
pub.currentWeekAssignments = pub.assignments?.filter(assignment => {
|
|
||||||
return assignment.shift.startTime >= currentWeekStart && assignment.shift.startTime <= currentWeekEnd;
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
// Filter assignments for current month
|
|
||||||
pub.currentMonthAssignments = pub.assignments?.filter(assignment => {
|
|
||||||
return assignment.shift.startTime >= currentMonthStart && assignment.shift.startTime <= currentMonthEnd;
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
// Filter assignments for previous month
|
|
||||||
pub.previousMonthAssignments = pub.assignments?.filter(assignment => {
|
|
||||||
return assignment.shift.startTime >= previousMonthStart && assignment.shift.startTime <= previousMonthEnd;
|
|
||||||
}).length;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//get the availabilities for the day. Calcullate:
|
|
||||||
//1. how many days the publisher is available for the current month - only with dayOfMonth
|
|
||||||
//2. how many days the publisher is available without dayOfMonth (previous months count)
|
|
||||||
//3. how many hours in total the publisher is available for the current month
|
|
||||||
publishers.forEach(pub => {
|
|
||||||
if (isWithStats) {
|
|
||||||
pub.currentMonthAvailability = pub.availabilities?.filter(avail => {
|
|
||||||
// return avail.dayOfMonth != null && avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
|
||||||
return avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
|
||||||
})
|
|
||||||
pub.currentMonthAvailabilityDaysCount = pub.currentMonthAvailability.length || 0;
|
|
||||||
// pub.currentMonthAvailabilityDaysCount += pub.availabilities.filter(avail => {
|
|
||||||
// return avail.dayOfMonth == null;
|
|
||||||
// }).length;
|
|
||||||
pub.currentMonthAvailabilityHoursCount = pub.currentMonthAvailability.reduce((acc, curr) => {
|
|
||||||
return acc + (curr.endTime.getTime() - curr.startTime.getTime()) / (1000 * 60 * 60);
|
|
||||||
}, 0);
|
|
||||||
//if pub has up-to-date availabilities (with dayOfMonth) for the current month
|
|
||||||
pub.hasUpToDateAvailabilities = pub.availabilities?.some(avail => {
|
|
||||||
return avail.dayOfMonth != null && avail.startTime >= currentMonthStart; // && avail.startTime <= currentMonthEnd;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//if pub has ever filled the form - if has availabilities which are not from previous assignments
|
|
||||||
pub.hasEverFilledForm = pub.availabilities?.some(avail => {
|
|
||||||
return avail.isFromPreviousAssignments == false;
|
|
||||||
});
|
|
||||||
|
|
||||||
//if pub has availabilities for the current day
|
|
||||||
pub.hasAvailabilityForCurrentDay = pub.availabilities?.some(avail => {
|
|
||||||
return avail.startTime >= filterDate && avail.startTime <= filterDateEnd;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (isExactTime) {
|
|
||||||
// Post filter for time if dayOfMonth is null as we can't only by time for multiple dates in SQL
|
|
||||||
// Modify the availabilities array of the filtered publishers
|
|
||||||
publishers.forEach(pub => {
|
|
||||||
pub.availabilities = pub.availabilities?.filter(avail => matchesAvailability(avail, filterDate));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return publishers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// availabilites filter:
|
// availabilites filter:
|
||||||
|
@ -7,6 +7,7 @@ import common from '../../../src/helpers/common';
|
|||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import ConfirmationModal from 'components/ConfirmationModal';
|
import ConfirmationModal from 'components/ConfirmationModal';
|
||||||
import PublisherSearchBox from '../../../components/publisher/PublisherSearchBox'; // Update the path
|
import PublisherSearchBox from '../../../components/publisher/PublisherSearchBox'; // Update the path
|
||||||
|
import SearchReplacement from '../../../components/publisher/SearchReplacement'; // Update the path
|
||||||
|
|
||||||
import { monthNamesBG, GetTimeFormat, GetDateFormat } from "../../../src/helpers/const"
|
import { monthNamesBG, GetTimeFormat, GetDateFormat } from "../../../src/helpers/const"
|
||||||
import { useSession, getSession } from 'next-auth/react';
|
import { useSession, getSession } from 'next-auth/react';
|
||||||
@ -52,21 +53,6 @@ export default function MySchedulePage({ assignments }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchReplacement = (assignmentId) => {
|
|
||||||
axiosInstance.post('/api/email?action=sendCoverMeRequestByEmail', {
|
|
||||||
assignmentId: assignmentId,
|
|
||||||
}).then(response => {
|
|
||||||
console.log("response", response);
|
|
||||||
//toast success and confirm the change
|
|
||||||
toast.success("Заявката за заместник е изпратена!", {
|
|
||||||
onClose: () => {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
console.log("error", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
@ -121,12 +107,8 @@ export default function MySchedulePage({ assignments }) {
|
|||||||
>
|
>
|
||||||
Избери Заместник
|
Избери Заместник
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
className="mr-2 mb-2 inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
<SearchReplacement shiftId={assignment.shift.id} assignmentId={assignment.id} />
|
||||||
onClick={() => searchReplacement(assignment.id)}
|
|
||||||
>
|
|
||||||
Търси заместник
|
|
||||||
</button>
|
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
@ -226,6 +226,268 @@ async function getAvailabilities(userId) {
|
|||||||
return serializableItems;
|
return serializableItems;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true) {
|
||||||
|
|
||||||
|
// Only attempt to split if selectFields is a string; otherwise, use it as it is.
|
||||||
|
selectFields = typeof selectFields === 'string' ? selectFields.split(",") : selectFields;
|
||||||
|
|
||||||
|
let selectBase = selectFields.reduce((acc, curr) => {
|
||||||
|
acc[curr] = true;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
selectBase.assignments = {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
shift: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
startTime: true,
|
||||||
|
endTime: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
shift: {
|
||||||
|
startTime: {
|
||||||
|
gte: filterDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var monthInfo = common.getMonthDatesInfo(filterDate);
|
||||||
|
var weekNr = common.getWeekOfMonth(filterDate); //getWeekNumber
|
||||||
|
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(filterDate);
|
||||||
|
if (!isExactTime) {
|
||||||
|
filterDate.setHours(0, 0, 0, 0); // Set to midnight
|
||||||
|
}
|
||||||
|
const filterDateEnd = new Date(filterDate);
|
||||||
|
filterDateEnd.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
|
||||||
|
let whereClause = {};
|
||||||
|
//if full day, match by date only
|
||||||
|
if (!isExactTime) { // Check only by date without considering time ( Assignments on specific days without time)
|
||||||
|
whereClause["availabilities"] = {
|
||||||
|
some: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
startTime: { gte: filterDate },
|
||||||
|
endTime: { lte: filterDateEnd },
|
||||||
|
}
|
||||||
|
,
|
||||||
|
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
||||||
|
// This includes availabilities from previous assignments but not with preference
|
||||||
|
{
|
||||||
|
dayOfMonth: null, // includes monthly and weekly repeats
|
||||||
|
dayofweek: dayOfWeekEnum,
|
||||||
|
// ToDo: and weekOfMonth
|
||||||
|
startTime: { lte: filterDate },
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
||||||
|
{ endDate: { gte: filterDate } },
|
||||||
|
{ endDate: null }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//if not full day, match by date and time
|
||||||
|
else {
|
||||||
|
//match exact time (should be same as data.findPublisherAvailability())
|
||||||
|
whereClause["availabilities"] = {
|
||||||
|
some: {
|
||||||
|
OR: [
|
||||||
|
// Check if dayOfMonth is set and filterDate is between start and end dates (Assignments on specific days AND time)
|
||||||
|
{
|
||||||
|
// dayOfMonth: filterDate.getDate(),
|
||||||
|
startTime: { lte: filterDate },
|
||||||
|
endTime: { gte: filterDate }
|
||||||
|
},
|
||||||
|
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
||||||
|
{
|
||||||
|
dayOfMonth: null,
|
||||||
|
dayofweek: dayOfWeekEnum,
|
||||||
|
startTime: { gte: filterDate },
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
||||||
|
{ endDate: { gte: filterDate } },
|
||||||
|
{ endDate: null }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isForTheMonth) {
|
||||||
|
// If no filter date, return all publishers's availabilities for currentMonthStart
|
||||||
|
whereClause["availabilities"] = {
|
||||||
|
some: {
|
||||||
|
OR: [
|
||||||
|
// Check if dayOfMonth is not null and startTime is after currentMonthStart (Assignments on specific days AND time)
|
||||||
|
{
|
||||||
|
dayOfMonth: { not: null },
|
||||||
|
startTime: { gte: currentMonthStart },
|
||||||
|
endTime: { lte: currentMonthEnd }
|
||||||
|
},
|
||||||
|
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
||||||
|
{
|
||||||
|
dayOfMonth: null,
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
||||||
|
{ endDate: { gte: filterDate } },
|
||||||
|
{ endDate: null }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`getting publishers for date: ${filterDate}, isExactTime: ${isExactTime}, isForTheMonth: ${isForTheMonth}`);
|
||||||
|
//include availabilities if flag is true
|
||||||
|
const prisma = common.getPrismaClient(); //why we need to get it again?
|
||||||
|
let publishers = await prisma.publisher.findMany({
|
||||||
|
where: whereClause,
|
||||||
|
select: {
|
||||||
|
...selectBase,
|
||||||
|
availabilities: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`publishers: ${publishers.length}, WhereClause: ${JSON.stringify(whereClause)}`);
|
||||||
|
|
||||||
|
// convert matching weekly availabilities to availabilities for the day to make furter processing easier on the client.
|
||||||
|
// we trust that the filtering was OK, so we use the dateFilter as date.
|
||||||
|
publishers.forEach(pub => {
|
||||||
|
pub.availabilities = pub.availabilities.map(avail => {
|
||||||
|
if (avail.dayOfMonth == null) {
|
||||||
|
let newStart = new Date(filterDate);
|
||||||
|
newStart.setHours(avail.startTime.getHours(), avail.startTime.getMinutes(), 0, 0);
|
||||||
|
let newEnd = new Date(filterDate);
|
||||||
|
newEnd.setHours(avail.endTime.getHours(), avail.endTime.getMinutes(), 0, 0);
|
||||||
|
return {
|
||||||
|
...avail,
|
||||||
|
startTime: newStart,
|
||||||
|
endTime: newEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return avail;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let currentWeekStart, currentWeekEnd,
|
||||||
|
currentMonthStart, currentMonthEnd,
|
||||||
|
previousMonthStart, previousMonthEnd;
|
||||||
|
|
||||||
|
if (isWithStats) {
|
||||||
|
currentWeekStart = common.getStartOfWeek(filterDate);
|
||||||
|
currentWeekEnd = common.getEndOfWeek(filterDate);
|
||||||
|
currentMonthStart = monthInfo.firstMonday;
|
||||||
|
currentMonthEnd = monthInfo.lastSunday;
|
||||||
|
let prevMnt = new Date(filterDate)
|
||||||
|
prevMnt.setMonth(prevMnt.getMonth() - 1);
|
||||||
|
monthInfo = common.getMonthDatesInfo(prevMnt);
|
||||||
|
previousMonthStart = monthInfo.firstMonday;
|
||||||
|
previousMonthEnd = monthInfo.lastSunday;
|
||||||
|
|
||||||
|
//get if publisher has assignments for current weekday, week, current month, previous month
|
||||||
|
publishers.forEach(pub => {
|
||||||
|
// Filter assignments for current day
|
||||||
|
pub.currentDayAssignments = pub.assignments?.filter(assignment => {
|
||||||
|
return assignment.shift.startTime >= filterDate && assignment.shift.startTime <= filterDateEnd;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
// Filter assignments for current week
|
||||||
|
pub.currentWeekAssignments = pub.assignments?.filter(assignment => {
|
||||||
|
return assignment.shift.startTime >= currentWeekStart && assignment.shift.startTime <= currentWeekEnd;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
// Filter assignments for current month
|
||||||
|
pub.currentMonthAssignments = pub.assignments?.filter(assignment => {
|
||||||
|
return assignment.shift.startTime >= currentMonthStart && assignment.shift.startTime <= currentMonthEnd;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
// Filter assignments for previous month
|
||||||
|
pub.previousMonthAssignments = pub.assignments?.filter(assignment => {
|
||||||
|
return assignment.shift.startTime >= previousMonthStart && assignment.shift.startTime <= previousMonthEnd;
|
||||||
|
}).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the availabilities for the day. Calcullate:
|
||||||
|
//1. how many days the publisher is available for the current month - only with dayOfMonth
|
||||||
|
//2. how many days the publisher is available without dayOfMonth (previous months count)
|
||||||
|
//3. how many hours in total the publisher is available for the current month
|
||||||
|
publishers.forEach(pub => {
|
||||||
|
if (isWithStats) {
|
||||||
|
pub.currentMonthAvailability = pub.availabilities?.filter(avail => {
|
||||||
|
// return avail.dayOfMonth != null && avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
||||||
|
return avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
||||||
|
})
|
||||||
|
pub.currentMonthAvailabilityDaysCount = pub.currentMonthAvailability.length || 0;
|
||||||
|
// pub.currentMonthAvailabilityDaysCount += pub.availabilities.filter(avail => {
|
||||||
|
// return avail.dayOfMonth == null;
|
||||||
|
// }).length;
|
||||||
|
pub.currentMonthAvailabilityHoursCount = pub.currentMonthAvailability.reduce((acc, curr) => {
|
||||||
|
return acc + (curr.endTime.getTime() - curr.startTime.getTime()) / (1000 * 60 * 60);
|
||||||
|
}, 0);
|
||||||
|
//if pub has up-to-date availabilities (with dayOfMonth) for the current month
|
||||||
|
pub.hasUpToDateAvailabilities = pub.availabilities?.some(avail => {
|
||||||
|
return avail.dayOfMonth != null && avail.startTime >= currentMonthStart; // && avail.startTime <= currentMonthEnd;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//if pub has ever filled the form - if has availabilities which are not from previous assignments
|
||||||
|
pub.hasEverFilledForm = pub.availabilities?.some(avail => {
|
||||||
|
return avail.isFromPreviousAssignments == false;
|
||||||
|
});
|
||||||
|
|
||||||
|
//if pub has availabilities for the current day
|
||||||
|
pub.hasAvailabilityForCurrentDay = pub.availabilities?.some(avail => {
|
||||||
|
return avail.startTime >= filterDate && avail.startTime <= filterDateEnd;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (isExactTime) {
|
||||||
|
// Post filter for time if dayOfMonth is null as we can't only by time for multiple dates in SQL
|
||||||
|
// Modify the availabilities array of the filtered publishers
|
||||||
|
publishers.forEach(pub => {
|
||||||
|
pub.availabilities = pub.availabilities?.filter(avail => matchesAvailability(avail, filterDate));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return publishers;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesAvailability(avail, filterDate) {
|
||||||
|
// Setting the start and end time of the filterDate
|
||||||
|
filterDate.setHours(0, 0, 0, 0);
|
||||||
|
const filterDateEnd = new Date(filterDate);
|
||||||
|
filterDateEnd.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
// Return true if avail.startTime is between filterDate and filterDateEnd
|
||||||
|
return avail.startTime >= filterDate && avail.startTime <= filterDateEnd;
|
||||||
|
}
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
@ -255,5 +517,6 @@ module.exports = {
|
|||||||
findPublisher,
|
findPublisher,
|
||||||
findPublisherAvailability,
|
findPublisherAvailability,
|
||||||
runSqlFile,
|
runSqlFile,
|
||||||
getAvailabilities
|
getAvailabilities,
|
||||||
|
filterPublishersNew
|
||||||
};
|
};
|
@ -25,22 +25,23 @@ let mailtrapTestClient = null;
|
|||||||
// password: 'c7bc05f171c96c'
|
// password: 'c7bc05f171c96c'
|
||||||
// });
|
// });
|
||||||
|
|
||||||
//test
|
//PROD MAILTRAP
|
||||||
var transporter = nodemailer.createTransport({
|
var transporter = nodemailer.createTransport({
|
||||||
host: process.env.MAILERSEND_SERVER,
|
host: process.env.MAILTRAP_HOST || "sandbox.smtp.mailtrap.io",
|
||||||
port: process.env.MAILERSEND_PORT,
|
port: 2525,
|
||||||
auth: {
|
auth: {
|
||||||
user: process.env.MAILERSEND_USER,
|
user: process.env.MAILTRAP_USER,
|
||||||
pass: process.env.MAILERSEND_PASS
|
pass: process.env.MAILTRAP_PASS
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// production
|
|
||||||
|
//PROD MAILERSEND
|
||||||
// var transporter = nodemailer.createTransport({
|
// var transporter = nodemailer.createTransport({
|
||||||
// host: "live.smtp.mailtrap.io",
|
// host: process.env.MAILERSEND_SERVER,
|
||||||
// port: 587,
|
// port: process.env.MAILERSEND_PORT,
|
||||||
// auth: {
|
// auth: {
|
||||||
// user: "api",
|
// user: process.env.MAILERSEND_USER,
|
||||||
// pass: "1cfe82e747b8dc3390ed08bb16e0f48d"
|
// pass: process.env.MAILERSEND_PASS
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user