Merge branch 'main' into production
This commit is contained in:
10
.env.test
10
.env.test
@ -17,8 +17,8 @@ AUTH0_ISSUER=https://dev-wkzi658ckibr1amv.us.auth0.com
|
|||||||
# GOOGLE_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com
|
# GOOGLE_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com
|
||||||
# GOOGLE_SECRET=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
|
# GOOGLE_SECRET=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
|
||||||
|
|
||||||
EMAIL_SERVICE=mailtrap
|
# EMAIL_SERVICE=mailtrap
|
||||||
MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io
|
# MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io
|
||||||
MAILTRAP_HOST=live.smtp.mailtrap.io
|
# MAILTRAP_HOST=live.smtp.mailtrap.io
|
||||||
MAILTRAP_USER=api
|
# MAILTRAP_USER=api
|
||||||
MAILTRAP_PASS=1cfe82e747b8dc3390ed08bb16e0f48d
|
# MAILTRAP_PASS=1cfe82e747b8dc3390ed08bb16e0f48d
|
16
_doc/ToDo.md
16
_doc/ToDo.md
@ -224,12 +224,12 @@ in schedule admin - if a publisher is always pair & family is not in the shift -
|
|||||||
лог ако е изрит потребител.
|
лог ако е изрит потребител.
|
||||||
|
|
||||||
|
|
||||||
last login. pubs.
|
[x] OK last login. pubs.
|
||||||
otchet - za denq
|
[x] OK otchet - za denq
|
||||||
делете цонфирм
|
[x] OK - делете цонфирм
|
||||||
статистика - фкс (янка) + posledno vlizane
|
[x] статистика - фкс (янка) + posledno vlizane
|
||||||
|
|
||||||
заместник, предпочитание в миналото - да не може.
|
[x] OK заместник, предпочитание в миналото - да не може.
|
||||||
Да иска потвърждение преди да заместиш някой
|
[] Да иска потвърждение преди да заместиш някой
|
||||||
да не се виждат непубликуваните смени в Моите смени
|
[] да не се виждат непубликуваните смени в Моите смени
|
||||||
да не се виждат старите предпочитания ако не си админ в публишерс
|
[] да не се виждат старите предпочитания ако не си админ в публишерс
|
@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Calendar, momentLocalizer, dateFnsLocalizer } from 'react-big-calendar';
|
import { Calendar, momentLocalizer, dateFnsLocalizer } from 'react-big-calendar';
|
||||||
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
||||||
import AvailabilityForm from '../availability/AvailabilityForm';
|
import AvailabilityForm from '../availability/AvailabilityForm';
|
||||||
|
import ProtectedRoute from '../protectedRoute';
|
||||||
|
import { UserRole } from "@prisma/client";
|
||||||
import common from '../../src/helpers/common';
|
import common from '../../src/helpers/common';
|
||||||
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@ -16,6 +18,7 @@ import { MdToday } from 'react-icons/md';
|
|||||||
|
|
||||||
import { useSwipeable } from 'react-swipeable';
|
import { useSwipeable } from 'react-swipeable';
|
||||||
import axiosInstance from '../../src/axiosSecure';
|
import axiosInstance from '../../src/axiosSecure';
|
||||||
|
|
||||||
// import { set, format, addDays } from 'date-fns';
|
// import { set, format, addDays } from 'date-fns';
|
||||||
// import { isEqual, isSameDay, getHours, getMinutes } from 'date-fns';
|
// import { isEqual, isSameDay, getHours, getMinutes } from 'date-fns';
|
||||||
import { filter } from 'jszip';
|
import { filter } from 'jszip';
|
||||||
@ -45,6 +48,8 @@ const messages = {
|
|||||||
|
|
||||||
const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||||
|
|
||||||
|
const isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN);
|
||||||
|
|
||||||
const [date, setDate] = useState(new Date());
|
const [date, setDate] = useState(new Date());
|
||||||
//ToDo: see if we can optimize this
|
//ToDo: see if we can optimize this
|
||||||
const [evts, setEvents] = useState(events); // Existing events
|
const [evts, setEvents] = useState(events); // Existing events
|
||||||
@ -211,8 +216,9 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
|
|
||||||
if (!start || !end) return;
|
if (!start || !end) return;
|
||||||
//readonly for past dates (ToDo: if not admin)
|
//readonly for past dates (ToDo: if not admin)
|
||||||
if (startdate < new Date() || end < new Date() || startdate > end) return;
|
if (!isAdmin) {
|
||||||
|
if (startdate < new Date() || end < new Date() || startdate > end) return;
|
||||||
|
}
|
||||||
// Check if start and end are on the same day
|
// Check if start and end are on the same day
|
||||||
if (startdate.toDateString() !== enddate.toDateString()) {
|
if (startdate.toDateString() !== enddate.toDateString()) {
|
||||||
end = common.setTimeHHmm(startdate, "23:59");
|
end = common.setTimeHHmm(startdate, "23:59");
|
||||||
@ -299,22 +305,22 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (event.isActive) {
|
// if (event.isActive) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'assignment':
|
case 'assignment':
|
||||||
// backgroundColor = '#48bb78' // always green-500 as we don't pass isConfirmed correctly
|
backgroundColor = '#48bb78' // always green-500 as we don't pass isConfirmed correctly
|
||||||
//backgroundColor = event.isConfirmed ? '#48bb78' : '#f6e05e'; // green-500 and yellow-300 from Tailwind CSS
|
//backgroundColor = event.isConfirmed ? '#48bb78' : '#f6e05e'; // green-500 and yellow-300 from Tailwind CSS
|
||||||
break;
|
break;
|
||||||
case 'recurring':
|
case 'recurring':
|
||||||
backgroundColor = '#63b3ed'; // blue-300 from Tailwind CSS
|
backgroundColor = '#63b3ed'; // blue-300 from Tailwind CSS
|
||||||
break;
|
break;
|
||||||
default: // availability
|
default: // availability
|
||||||
//backgroundColor = '#a0aec0'; // gray-400 from Tailwind CSS
|
//backgroundColor = '#a0aec0'; // gray-400 from Tailwind CSS
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
backgroundColor = '#a0aec0'; // Default color for inactive events
|
|
||||||
}
|
}
|
||||||
|
// } else {
|
||||||
|
// backgroundColor = '#a0aec0'; // Default color for inactive events
|
||||||
|
// }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
style: {
|
style: {
|
||||||
@ -337,9 +343,7 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
const handleMouseLeave = () => setIsHovered(false);
|
const handleMouseLeave = () => setIsHovered(false);
|
||||||
if (currentView !== 'agenda') {
|
if (currentView !== 'agenda') {
|
||||||
//if event.type is availability show in blue. if it is schedule - green if confirmed, yellow if not confirmed
|
//if event.type is availability show in blue. if it is schedule - green if confirmed, yellow if not confirmed
|
||||||
//if event is not active - show in gray
|
var bgColor = "";
|
||||||
let bgColorClass = 'bg-gray-500'; // Default color for inactive events
|
|
||||||
var bgColor = event.isActive ? "" : "bg-gray-500";
|
|
||||||
//ToDo: fix this. maybe we're missing some properties
|
//ToDo: fix this. maybe we're missing some properties
|
||||||
// if (event.isFromPreviousMonth) {
|
// if (event.isFromPreviousMonth) {
|
||||||
// // set opacity to 0.5
|
// // set opacity to 0.5
|
||||||
@ -354,7 +358,6 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
|||||||
if (event.start !== undefined && event.end !== undefined && event.startTime !== null && event.endTime !== null) {
|
if (event.start !== undefined && event.end !== undefined && event.startTime !== null && event.endTime !== null) {
|
||||||
try {
|
try {
|
||||||
if (event.type === "recurring") {
|
if (event.type === "recurring") {
|
||||||
//bgColor = "bg-blue-300";
|
|
||||||
event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime);
|
event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -68,3 +68,8 @@ export async function serverSideAuth({ req, allowedRoles }) {
|
|||||||
// Return the session if the user is authenticated and has the required role
|
// Return the session if the user is authenticated and has the required role
|
||||||
return { session };
|
return { session };
|
||||||
}
|
}
|
||||||
|
// Static method to check if the user has a specific role
|
||||||
|
ProtectedRoute.IsInRole = async (roleName) => {
|
||||||
|
const session = await getSession();
|
||||||
|
return session && session.user && session.user.role === roleName;
|
||||||
|
};
|
@ -114,7 +114,7 @@ function ConfirmationModal({ isOpen, onClose, onConfirm, subscribedPublishers, a
|
|||||||
checked={selectedGroups.includes('availablePublishers')}
|
checked={selectedGroups.includes('availablePublishers')}
|
||||||
onChange={() => handleToggleGroup('availablePublishers')}
|
onChange={() => handleToggleGroup('availablePublishers')}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm font-medium">На разположение:</span>
|
<span className="text-sm font-medium">На разположение :</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap">
|
<div className="flex flex-wrap">
|
||||||
{availablePublishers.map(pub => (
|
{availablePublishers.map(pub => (
|
||||||
|
@ -9,6 +9,7 @@ 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");
|
||||||
import { EventLogType } from "@prisma/client";
|
import { EventLogType } from "@prisma/client";
|
||||||
|
const logger = require('../../src/logger');
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@ -46,6 +47,7 @@ export default async function handler(req, res) {
|
|||||||
});
|
});
|
||||||
// Update the user status to accepted
|
// Update the user status to accepted
|
||||||
console.log("User: " + publisher.firstName + " " + publisher.lastName + " accepted the CoverMe request");
|
console.log("User: " + publisher.firstName + " " + publisher.lastName + " accepted the CoverMe request");
|
||||||
|
logger.info("" + publisher.firstName + " " + publisher.lastName + " accepted the CoverMe request for shift " + shiftId + " PID: " + req.query.assignmentPID + "");
|
||||||
|
|
||||||
let assignmentPID = req.query.assignmentPID;
|
let assignmentPID = req.query.assignmentPID;
|
||||||
if (!shiftId) {
|
if (!shiftId) {
|
||||||
@ -245,7 +247,6 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log("User: " + publisher.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
|
||||||
@ -260,23 +261,35 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let subscribedPublishers = [], availablePublishers = [];
|
let targetEmails = await data.getCoverMePublisherEmails(assignment.shift.id);
|
||||||
if (toSubscribed) {
|
if (!toSubscribed) {
|
||||||
//get all subscribed publisers
|
targetEmails.subscribedPublishers = [];
|
||||||
subscribedPublishers = await prisma.publisher.findMany({
|
}
|
||||||
where: {
|
if (!toAvailable) {
|
||||||
isSubscribedToCoverMe: true
|
targetEmails.availablePublishers = [];
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let subscribedPublishers = targetEmails.subscribedPublishers, availablePublishers = [];
|
||||||
|
// if (toSubscribed) {
|
||||||
|
// //get all subscribed publisers
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// }
|
||||||
|
// use
|
||||||
|
|
||||||
if (toAvailable) {
|
|
||||||
availablePublishers = await data.filterPublishersNew("id,firstName,lastName,email", new Date(assignment.shift.startTime), true, false);
|
|
||||||
|
|
||||||
}
|
|
||||||
//concat and remove duplicate emails
|
//concat and remove duplicate emails
|
||||||
let pubsToSend = subscribedPublishers.concat(availablePublishers).
|
let pubsToSend = targetEmails.subscribedPublishers.concat(targetEmails.availablePublishers).
|
||||||
filter((item, index, self) =>
|
filter((item, index, self) =>
|
||||||
index === self.findIndex((t) => (
|
index === self.findIndex((t) => (
|
||||||
t.email === item.email && item.email !== publisher.email//and exclude the user himself
|
t.email === item.email && item.email !== publisher.email//and exclude the user himself
|
||||||
@ -284,7 +297,7 @@ export default async function handler(req, res) {
|
|||||||
);
|
);
|
||||||
console.log("Sending CoverMe request to " + pubsToSend.length + " publishers");
|
console.log("Sending CoverMe request to " + pubsToSend.length + " publishers");
|
||||||
|
|
||||||
await prisma.eventLog.create({
|
let eventLog = await prisma.eventLog.create({
|
||||||
data: {
|
data: {
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
publisher: { connect: { id: publisher.id } },
|
publisher: { connect: { id: publisher.id } },
|
||||||
@ -294,6 +307,7 @@ export default async function handler(req, res) {
|
|||||||
+ "до: " + pubsToSend.map(p => p.firstName + " " + p.lastName + "<" + p.email + ">").join(", "),
|
+ "до: " + pubsToSend.map(p => p.firstName + " " + p.lastName + "<" + p.email + ">").join(", "),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
logger.info("User: " + publisher.email + " sent a 'CoverMe' request for his assignment " + assignmentId + " - " + assignment.shift.cartEvent.location.name + " " + assignment.shift.startTime.toISOString() + " to " + pubsToSend.map(p => p.firstName + " " + p.lastName + "<" + p.email + ">").join(", ") + ". EventLogId: " + eventLog.id + "");
|
||||||
|
|
||||||
//send email to all subscribed publishers
|
//send email to all subscribed publishers
|
||||||
for (let i = 0; i < pubsToSend.length; i++) {
|
for (let i = 0; i < pubsToSend.length; i++) {
|
||||||
|
@ -2,8 +2,9 @@ import { getToken } from "next-auth/jwt";
|
|||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { DayOfWeek, AvailabilityType } from '@prisma/client';
|
import { DayOfWeek, AvailabilityType } from '@prisma/client';
|
||||||
const common = require('../../src/helpers/common');
|
const common = require('../../src/helpers/common');
|
||||||
const data = require('../../src/helpers/data');
|
const dataHelper = require('../../src/helpers/data');
|
||||||
const subq = require('../../prisma/bl/subqueries');
|
const subq = require('../../prisma/bl/subqueries');
|
||||||
|
import { addMinutes } from 'date-fns';
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@ -171,7 +172,7 @@ export default async function handler(req, res) {
|
|||||||
// find publisher by full name or email
|
// find publisher by full name or email
|
||||||
case "findPublisher":
|
case "findPublisher":
|
||||||
const getAll = common.parseBool(req.query.all) || false;
|
const getAll = common.parseBool(req.query.all) || false;
|
||||||
let publisher = await data.findPublisher(filter, req.query.email, req.query.select, getAll);
|
let publisher = await dataHelper.findPublisher(filter, req.query.email, req.query.select, getAll);
|
||||||
res.status(200).json(publisher);
|
res.status(200).json(publisher);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -350,41 +351,8 @@ export default async function handler(req, res) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case "getPossibleShiftPublisherEmails":
|
case "getPossibleShiftPublisherEmails":
|
||||||
const subscribedPublishers = await prisma.publisher.findMany({
|
let data = await dataHelper.getCoverMePublisherEmails(parseInt(req.query.shiftId));
|
||||||
where: {
|
res.status(200).json(data);
|
||||||
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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -464,7 +432,7 @@ export async function getMonthlyStatistics(selectFields, filterDate) {
|
|||||||
|
|
||||||
|
|
||||||
export async function filterPublishersNew_Available(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true, includeOldAvailabilities = false) {
|
export async function filterPublishersNew_Available(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true, includeOldAvailabilities = false) {
|
||||||
return data.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, isWithStats, includeOldAvailabilities);
|
return dataHelper.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, false, isWithStats, includeOldAvailabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
// availabilites filter:
|
// availabilites filter:
|
||||||
@ -837,8 +805,48 @@ function matchesAvailability(avail, filterDate) {
|
|||||||
|
|
||||||
async function getCalendarEvents(publisherId, date, availabilities = true, assignments = true) {
|
async function getCalendarEvents(publisherId, date, availabilities = true, assignments = true) {
|
||||||
const result = [];
|
const result = [];
|
||||||
let pubs = await filterPublishers("id,firstName,lastName,email".split(","), "", date, assignments, availabilities, date ? true : false, publisherId);
|
// let pubs = await filterPublishers("id,firstName,lastName,email".split(","), "", date, assignments, availabilities, date ? true : false, publisherId);
|
||||||
let publisher = pubs[0];
|
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
|
let publisher = await prisma.publisher.findUnique({
|
||||||
|
where: {
|
||||||
|
id: publisherId
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true,
|
||||||
|
availabilities: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
dayOfMonth: true,
|
||||||
|
dayofweek: true,
|
||||||
|
weekOfMonth: true,
|
||||||
|
startTime: true,
|
||||||
|
endTime: true,
|
||||||
|
name: true,
|
||||||
|
isFromPreviousAssignment: true,
|
||||||
|
isFromPreviousMonth: true,
|
||||||
|
repeatWeekly: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assignments: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
shift: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
startTime: true,
|
||||||
|
endTime: true,
|
||||||
|
isPublished: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (publisher) {
|
if (publisher) {
|
||||||
if (availabilities) {
|
if (availabilities) {
|
||||||
publisher.availabilities?.forEach(item => {
|
publisher.availabilities?.forEach(item => {
|
||||||
|
@ -603,7 +603,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
|||||||
message="Това ще изпрати имейли до всички участници за смените им през избрания месец. Сигурни ли сте?"
|
message="Това ще изпрати имейли до всички участници за смените им през избрания месец. Сигурни ли сте?"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className={`button btn m-2 ${isPublished ? 'hover:bg-gray-100 bg-yellow-500' : 'hover:bg-red-300 bg-blue-400'}`}
|
className={`button btn m-2 ${isPublished ? 'hover:bg-gray-500 bg-yellow-500' : 'hover:bg-red-300 bg-blue-400'}`}
|
||||||
onClick={togglePublished}>
|
onClick={togglePublished}>
|
||||||
<i className={`fas ${isPublished ? 'fa-check' : 'fa-close'} mr-2`}></i>
|
<i className={`fas ${isPublished ? 'fa-check' : 'fa-close'} mr-2`}></i>
|
||||||
{isPublished ? "Скрий" : "Публикувай"} графика (м.)
|
{isPublished ? "Скрий" : "Публикувай"} графика (м.)
|
||||||
|
@ -3,6 +3,8 @@ import NewPubPage from "../new";
|
|||||||
export default NewPubPage;
|
export default NewPubPage;
|
||||||
|
|
||||||
import { Assignment, Shift, UserRole, AvailabilityType } from "prisma/prisma-client";
|
import { Assignment, Shift, UserRole, AvailabilityType } from "prisma/prisma-client";
|
||||||
|
//import ProtectedRoute from '../../../../components/protectedRoute';
|
||||||
|
|
||||||
// import { monthNamesBG } from "~/src/helpers/const"
|
// import { monthNamesBG } from "~/src/helpers/const"
|
||||||
import { monthNamesBG } from "src/helpers/const";
|
import { monthNamesBG } from "src/helpers/const";
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ function getShiftGroups(shifts: [Shift]) {
|
|||||||
export const getServerSideProps = async (context) => {
|
export const getServerSideProps = async (context) => {
|
||||||
const axios = await axiosServer(context);
|
const axios = await axiosServer(context);
|
||||||
|
|
||||||
|
// const isAdmin = await ProtectedRoute.IsInRole(UserRole.ADMIN); does not work on server side
|
||||||
context.res.setHeader("Cache-Control", "s-maxage=1, stale-while-revalidate");
|
context.res.setHeader("Cache-Control", "s-maxage=1, stale-while-revalidate");
|
||||||
if (!context.query || !context.query.id) {
|
if (!context.query || !context.query.id) {
|
||||||
return {
|
return {
|
||||||
@ -50,11 +53,9 @@ export const getServerSideProps = async (context) => {
|
|||||||
|
|
||||||
// item.allShifts = item.assignments.map((a: Assignment[]) => a.shift);
|
// item.allShifts = item.assignments.map((a: Assignment[]) => a.shift);
|
||||||
|
|
||||||
//group shifts by month, remove duplicates
|
// ============================================================
|
||||||
//sort availabilities by start time
|
// don't show past availabilities (if user not admin) ToDo: add admin check
|
||||||
// item.availabilities = item.availabilities
|
// ============================================================
|
||||||
// .sort((a, b) => b.startTime - a.startTime);
|
|
||||||
|
|
||||||
item.availabilities = item.availabilities.filter((a) => new Date(a.startTime) >= new Date() || a.type == AvailabilityType.Weekly);
|
item.availabilities = item.availabilities.filter((a) => new Date(a.startTime) >= new Date() || a.type == AvailabilityType.Weekly);
|
||||||
item.assignments = item.assignments
|
item.assignments = item.assignments
|
||||||
.sort((a, b) => b.startTime - a.startTime)
|
.sort((a, b) => b.startTime - a.startTime)
|
||||||
|
@ -127,7 +127,7 @@ export const getServerSideProps = async (context) => {
|
|||||||
const prisma = common.getPrismaClient();
|
const prisma = common.getPrismaClient();
|
||||||
const dateStr = new Date().toISOString().split('T')[0];
|
const dateStr = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
let publishers = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin', dateStr, false, true, true, true);
|
let publishers = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin', dateStr, false, true, true, true, true);
|
||||||
|
|
||||||
// const axios = await axiosServer(context);
|
// const axios = await axiosServer(context);
|
||||||
// const { data: publishers } = await axios.get(`api/?action=filterPublishers&assignments=true&availabilities=true&date=${dateStr}&select=id,firstName,lastName,isActive,desiredShiftsPerMonth`);
|
// const { data: publishers } = await axios.get(`api/?action=filterPublishers&assignments=true&availabilities=true&date=${dateStr}&select=id,firstName,lastName,isActive,desiredShiftsPerMonth`);
|
||||||
|
@ -3,9 +3,8 @@ import Layout from "../components/layout"
|
|||||||
import AvCalendar from '../components/calendar/avcalendar';
|
import AvCalendar from '../components/calendar/avcalendar';
|
||||||
import { getSession } from "next-auth/react";
|
import { getSession } from "next-auth/react";
|
||||||
import common from '../src/helpers/common';
|
import common from '../src/helpers/common';
|
||||||
import { Availability } from "@prisma/client";
|
import { Availability, UserRole } from "@prisma/client";
|
||||||
import ProtectedRoute, { serverSideAuth } from "../components/protectedRoute";
|
import ProtectedRoute, { serverSideAuth } from "../components/protectedRoute";
|
||||||
import { UserRole } from "@prisma/client";
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import axiosInstance from '../src/axiosSecure';
|
import axiosInstance from '../src/axiosSecure';
|
||||||
|
|
||||||
@ -56,6 +55,15 @@ export default function IndexPage({ initialItems, initialUserId }: IProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// EXAMPLE USAGE OF ProtectedRoute
|
||||||
|
ProtectedRoute.IsInRole(UserRole.ADMIN).then(isAdmin => {
|
||||||
|
if (isAdmin) {
|
||||||
|
console.log("User is an admin.");
|
||||||
|
} else {
|
||||||
|
console.log("User is not an admin.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<ProtectedRoute deniedMessage="">
|
<ProtectedRoute deniedMessage="">
|
||||||
|
@ -8,6 +8,8 @@ const data = require("./src/helpers/data");
|
|||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
|
||||||
|
const logger = require('./src/logger');
|
||||||
|
|
||||||
//const getPort = require("get-port");
|
//const getPort = require("get-port");
|
||||||
|
|
||||||
let getPort;
|
let getPort;
|
||||||
@ -16,6 +18,7 @@ import('get-port').then(module => {
|
|||||||
// Initialize your server or other logic that relies on getPort here
|
// Initialize your server or other logic that relies on getPort here
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
process.env.TZ = 'Europe/Sofia';
|
process.env.TZ = 'Europe/Sofia';
|
||||||
// Global variable to store the base URL
|
// Global variable to store the base URL
|
||||||
// let baseUrlGlobal;
|
// let baseUrlGlobal;
|
||||||
@ -42,6 +45,12 @@ console.log("process.env.DATABASE_URL = ", process.env.DATABASE_URL);
|
|||||||
console.log("process.env.DATABASE = ", process.env.DATABASE);
|
console.log("process.env.DATABASE = ", process.env.DATABASE);
|
||||||
console.log("process.env.APPLE_APP_ID = ", process.env.APPLE_APP_ID);
|
console.log("process.env.APPLE_APP_ID = ", process.env.APPLE_APP_ID);
|
||||||
|
|
||||||
|
logger.info("App started on " + process.env.PROTOCOL + "://" + process.env.HOST + ":" + process.env.PORT + "");
|
||||||
|
logger.info("process.env.GIT_COMMIT_ID = " + process.env.GIT_COMMIT_ID);
|
||||||
|
logger.info("process.env.APP_ENV = " + process.env.APP_ENV);
|
||||||
|
logger.info("process.env.NODE_ENV = " + process.env.NODE_ENV);
|
||||||
|
logger.info("process.env.APPLE_APP_ID = " + process.env.APPLE_APP_ID);
|
||||||
|
logger.info("process.env.EMAIL_SERVICE = " + process.env.EMAIL_SERVICE);
|
||||||
|
|
||||||
// update GIT_COMMIT_ID
|
// update GIT_COMMIT_ID
|
||||||
const { exec } = require("child_process");
|
const { exec } = require("child_process");
|
||||||
|
@ -78,7 +78,7 @@ exports.getPrismaClient = function getPrismaClient() {
|
|||||||
if (!prisma) {
|
if (!prisma) {
|
||||||
logger.debug("getPrismaClient: process.env.DATABASE = ", process.env.DATABASE);
|
logger.debug("getPrismaClient: process.env.DATABASE = ", process.env.DATABASE);
|
||||||
prisma = new PrismaClient({
|
prisma = new PrismaClient({
|
||||||
// Optional: Enable logging
|
// Optional: Enable Prisma logging
|
||||||
//log: ['query', 'info', 'warn', 'error'],
|
//log: ['query', 'info', 'warn', 'error'],
|
||||||
datasources: { db: { url: process.env.DATABASE } },
|
datasources: { db: { url: process.env.DATABASE } },
|
||||||
});
|
});
|
||||||
@ -757,6 +757,11 @@ exports.getInitials = function (names) {
|
|||||||
const parts = names.split(' ');
|
const parts = names.split(' ');
|
||||||
return parts.map(part => part[0] + ".").join('');
|
return parts.map(part => part[0] + ".").join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ? import { addMinutes } from 'date-fns';
|
||||||
|
exports.addMinutes = function (date, minutes) {
|
||||||
|
return new Date(date.getTime() + minutes * 60000); // 60000 milliseconds in a minute
|
||||||
|
}
|
||||||
// exports.getInitials = function (names) {
|
// exports.getInitials = function (names) {
|
||||||
// const parts = names.split(' '); // Split the full name into parts
|
// const parts = names.split(' '); // Split the full name into parts
|
||||||
// if (parts.length === 0) {
|
// if (parts.length === 0) {
|
||||||
|
@ -230,8 +230,14 @@ async function getAvailabilities(userId) {
|
|||||||
|
|
||||||
async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, noEndDateFilter = false, isWithStats = true, includeOldAvailabilities = false) {
|
async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, noEndDateFilter = false, isWithStats = true, includeOldAvailabilities = false) {
|
||||||
|
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
filterDate = new Date(filterDate); // Convert to date object if not already
|
filterDate = new Date(filterDate); // Convert to date object if not already
|
||||||
|
|
||||||
|
const monthInfo = common.getMonthDatesInfo(filterDate);
|
||||||
|
let prevMnt = new Date(filterDate)
|
||||||
|
prevMnt.setMonth(prevMnt.getMonth() - 1);
|
||||||
|
const prevMonthInfo = common.getMonthDatesInfo(prevMnt);
|
||||||
|
|
||||||
// Only attempt to split if selectFields is a string; otherwise, use it as it is.
|
// Only attempt to split if selectFields is a string; otherwise, use it as it is.
|
||||||
selectFields = typeof selectFields === 'string' ? selectFields.split(",") : selectFields;
|
selectFields = typeof selectFields === 'string' ? selectFields.split(",") : selectFields;
|
||||||
|
|
||||||
@ -240,6 +246,7 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|
||||||
selectBase.assignments = {
|
selectBase.assignments = {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@ -254,119 +261,91 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
|||||||
where: {
|
where: {
|
||||||
shift: {
|
shift: {
|
||||||
startTime: {
|
startTime: {
|
||||||
gte: filterDate,
|
gte: prevMnt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var monthInfo = common.getMonthDatesInfo(filterDate);
|
let filterTimeFrom = new Date(filterDate)
|
||||||
var weekNr = common.getWeekOfMonth(filterDate); //getWeekNumber
|
let filterTimeTo = new Date(filterDate);
|
||||||
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(filterDate);
|
let isDayFilter = true;
|
||||||
if (!isExactTime) {
|
|
||||||
filterDate.setHours(0, 0, 0, 0); // Set to midnight
|
|
||||||
}
|
|
||||||
const filterDateEnd = new Date(filterDate);
|
|
||||||
filterDateEnd.setHours(23, 59, 59, 999);
|
|
||||||
|
|
||||||
|
|
||||||
let whereClause = {};
|
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 (isForTheMonth) {
|
||||||
// If no filter date, return all publishers's availabilities for currentMonthStart
|
var weekNr = common.getWeekOfMonth(filterDate); //getWeekNumber
|
||||||
|
|
||||||
whereClause["availabilities"] = {
|
filterTimeFrom = monthInfo.firstMonday;
|
||||||
some: {
|
filterTimeTo = monthInfo.lastSunday;
|
||||||
OR: [
|
isDayFilter = false;
|
||||||
// Check if dayOfMonth is not null and startTime is after monthInfo.firstMonday (Assignments on specific days AND time)
|
}
|
||||||
{
|
if (isExactTime) {
|
||||||
dayOfMonth: { not: null },
|
//add +- 90 minutes to the filterDate ToDo: should be "shift duration"
|
||||||
startTime: { gte: monthInfo.firstMonday },
|
// filterTimeFrom.setMinutes(filterTimeFrom.getMinutes() - 90);
|
||||||
// endTime: { lte: monthInfo.lastSunday }
|
filterTimeTo.setMinutes(filterTimeTo.getMinutes() + 90);
|
||||||
},
|
}
|
||||||
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
|
else {
|
||||||
{
|
filterTimeFrom.setHours(0, 0, 0, 0);
|
||||||
dayOfMonth: null,
|
filterTimeTo.setHours(23, 59, 59, 999);
|
||||||
AND: [
|
}
|
||||||
{
|
|
||||||
OR: [ // OR condition for repeatUntil to handle events that either end after filterDate or repeat forever
|
whereClause["availabilities"] = {
|
||||||
{ endDate: { gte: filterDate } },
|
some: {
|
||||||
{ endDate: null }
|
OR: [
|
||||||
]
|
// Check if dayOfMonth is not null and startTime is after monthInfo.firstMonday (Assignments on specific days AND time)
|
||||||
}
|
{
|
||||||
]
|
//dayOfMonth: { not: null },
|
||||||
}
|
startTime: { gte: filterTimeFrom },
|
||||||
]
|
// endTime: { lte: monthInfo.lastSunday }
|
||||||
|
},
|
||||||
|
// 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: filterTimeFrom } }, // endDate included
|
||||||
|
{ endDate: null }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* FILTERS
|
||||||
|
1. exact time
|
||||||
|
2. exact date
|
||||||
|
3. the month
|
||||||
|
4. from start date only
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (noEndDateFilter) {
|
||||||
|
isDayFilter = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
whereClause["availabilities"].some.OR[0].endTime = { lte: filterTimeTo };
|
||||||
|
if (isForTheMonth) {
|
||||||
|
// no dayofweek or time filters here
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(filterDate);
|
||||||
|
whereClause["availabilities"].some.OR[1].dayofweek = dayOfWeekEnum;
|
||||||
|
//NOTE: we filter by date after we calculate the correct dates post query
|
||||||
|
if (isExactTime) {
|
||||||
|
//if exact time we need the availability to be starting on or before start of the shift and ending on or after the end of the shift
|
||||||
|
whereClause["availabilities"].some.OR[0].startTime = { lte: filterTimeFrom };
|
||||||
|
whereClause["availabilities"].some.OR[0].endTime = { gte: filterTimeTo };
|
||||||
|
}
|
||||||
|
else {
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (!noEndDateFilter) { // Check if we need to apply the endTime filter
|
|
||||||
whereClause["availabilities"].some.OR[0].endTime = { lte: monthInfo.lastSunday };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
console.log(`getting publishers for date: ${filterDate}, isExactTime: ${isExactTime}, isForTheMonth: ${isForTheMonth}`);
|
console.log(`getting publishers for date: ${filterDate}, isExactTime: ${isExactTime}, isForTheMonth: ${isForTheMonth}`);
|
||||||
//include availabilities if flag is true
|
//include availabilities if flag is true
|
||||||
const prisma = common.getPrismaClient(); //why we need to get it again?
|
|
||||||
let publishers = await prisma.publisher.findMany({
|
let publishers = await prisma.publisher.findMany({
|
||||||
where: whereClause,
|
where: whereClause,
|
||||||
select: {
|
select: {
|
||||||
@ -377,7 +356,7 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
|||||||
|
|
||||||
console.log(`publishers: ${publishers.length}, WhereClause: ${JSON.stringify(whereClause)}`);
|
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.
|
// convert matching weekly availabilities to availabilities for the day to make further processing easier on the client.
|
||||||
// we trust that the filtering was OK, so we use the dateFilter as date.
|
// we trust that the filtering was OK, so we use the dateFilter as date.
|
||||||
publishers.forEach(pub => {
|
publishers.forEach(pub => {
|
||||||
pub.availabilities = pub.availabilities.map(avail => {
|
pub.availabilities = pub.availabilities.map(avail => {
|
||||||
@ -397,28 +376,20 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let currentWeekStart, currentWeekEnd,
|
let currentWeekStart, currentWeekEnd;
|
||||||
currentMonthStart, currentMonthEnd,
|
|
||||||
previousMonthStart, previousMonthEnd;
|
|
||||||
|
|
||||||
if (isWithStats) {
|
if (isWithStats) {
|
||||||
currentWeekStart = common.getStartOfWeek(filterDate);
|
currentWeekStart = common.getStartOfWeek(filterDate);
|
||||||
currentWeekEnd = common.getEndOfWeek(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
|
//get if publisher has assignments for current weekday, week, current month, previous month
|
||||||
publishers.forEach(pub => {
|
publishers.forEach(pub => {
|
||||||
// Filter assignments for current day
|
// Filter assignments for current day
|
||||||
pub.currentDayAssignments = pub.assignments?.filter(assignment => {
|
if (isDayFilter) {
|
||||||
return assignment.shift.startTime >= filterDate && assignment.shift.startTime <= filterDateEnd;
|
pub.currentDayAssignments = pub.assignments?.filter(assignment => {
|
||||||
}).length;
|
return assignment.shift.startTime >= filterDate && assignment.shift.startTime <= filterTimeTo;
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
// Filter assignments for current week
|
// Filter assignments for current week
|
||||||
pub.currentWeekAssignments = pub.assignments?.filter(assignment => {
|
pub.currentWeekAssignments = pub.assignments?.filter(assignment => {
|
||||||
return assignment.shift.startTime >= currentWeekStart && assignment.shift.startTime <= currentWeekEnd;
|
return assignment.shift.startTime >= currentWeekStart && assignment.shift.startTime <= currentWeekEnd;
|
||||||
@ -426,26 +397,26 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
|||||||
|
|
||||||
// Filter assignments for current month
|
// Filter assignments for current month
|
||||||
pub.currentMonthAssignments = pub.assignments?.filter(assignment => {
|
pub.currentMonthAssignments = pub.assignments?.filter(assignment => {
|
||||||
return assignment.shift.startTime >= currentMonthStart && assignment.shift.startTime <= currentMonthEnd;
|
return assignment.shift.startTime >= monthInfo.firstMonday && (noEndDateFilter || assignment.shift.startTime <= monthInfo.lastSunday);
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
// Filter assignments for previous month
|
// Filter assignments for previous month
|
||||||
pub.previousMonthAssignments = pub.assignments?.filter(assignment => {
|
pub.previousMonthAssignments = pub.assignments?.filter(assignment => {
|
||||||
return assignment.shift.startTime >= previousMonthStart && assignment.shift.startTime <= previousMonthEnd;
|
return assignment.shift.startTime >= prevMonthInfo.firstMonday && assignment.shift.startTime <= prevMonthInfo.lastSunday;
|
||||||
}).length;
|
}).length;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//get the availabilities for the day. Calcullate:
|
//get the availabilities for the day. Calculate:
|
||||||
//1. how many days the publisher is available for the current month - only with dayOfMonth
|
//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)
|
//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
|
//3. how many hours in total the publisher is available for the current month
|
||||||
publishers.forEach(pub => {
|
publishers.forEach(pub => {
|
||||||
if (isWithStats) {
|
if (isWithStats) {
|
||||||
pub.currentMonthAvailability = pub.availabilities?.filter(avail => {
|
pub.currentMonthAvailability = pub.availabilities?.filter(avail => {
|
||||||
// return avail.dayOfMonth != null && avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
// return avail.dayOfMonth != null && avail.startTime >= currentMonthStart && avail.startTime <= monthInfo.lastSunday;
|
||||||
return avail.startTime >= currentMonthStart && avail.startTime <= currentMonthEnd;
|
return avail.startTime >= monthInfo.firstMonday && (noEndDateFilter || avail.startTime <= monthInfo.lastSunday);
|
||||||
})
|
})
|
||||||
pub.currentMonthAvailabilityDaysCount = pub.currentMonthAvailability.length || 0;
|
pub.currentMonthAvailabilityDaysCount = pub.currentMonthAvailability.length || 0;
|
||||||
// pub.currentMonthAvailabilityDaysCount += pub.availabilities.filter(avail => {
|
// pub.currentMonthAvailabilityDaysCount += pub.availabilities.filter(avail => {
|
||||||
@ -456,7 +427,7 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
|||||||
}, 0);
|
}, 0);
|
||||||
//if pub has up-to-date availabilities (with dayOfMonth) for the current month
|
//if pub has up-to-date availabilities (with dayOfMonth) for the current month
|
||||||
pub.hasUpToDateAvailabilities = pub.availabilities?.some(avail => {
|
pub.hasUpToDateAvailabilities = pub.availabilities?.some(avail => {
|
||||||
return avail.dayOfMonth != null && avail.startTime >= currentMonthStart; // && avail.startTime <= currentMonthEnd;
|
return avail.dayOfMonth != null && avail.startTime >= monthInfo.firstMonday; // && avail.startTime <= monthInfo.lastSunday;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -465,38 +436,118 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
|||||||
pub.hasEverFilledForm = pub.availabilities?.some(avail => {
|
pub.hasEverFilledForm = pub.availabilities?.some(avail => {
|
||||||
return avail.isFromPreviousAssignments == false;
|
return avail.isFromPreviousAssignments == false;
|
||||||
});
|
});
|
||||||
|
if (isDayFilter) {
|
||||||
//if pub has availabilities for the current day
|
//if pub has availabilities for the current day
|
||||||
pub.hasAvailabilityForCurrentDay = pub.availabilities?.some(avail => {
|
pub.hasAvailabilityForCurrentDay = pub.availabilities?.some(avail => {
|
||||||
return avail.startTime >= filterDate && avail.startTime <= filterDateEnd;
|
return avail.startTime >= filterDate && avail.startTime <= filterTimeTo;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (isExactTime) {
|
if (isExactTime) {
|
||||||
// Post filter for time if dayOfMonth is null as we can't only by time for multiple dates in SQL
|
//HERE WE FILTER by time for repeating availabilities. We can't do that if we don't have
|
||||||
// Modify the availabilities array of the filtered publishers
|
// whereClause["availabilities"].some.OR[1].startTime = { gte: filterTimeFrom };
|
||||||
|
// whereClause["availabilities"].some.OR[1].endTime = { gte: filterTimeTo }
|
||||||
publishers.forEach(pub => {
|
publishers.forEach(pub => {
|
||||||
pub.availabilities = pub.availabilities?.filter(avail => matchesAvailability(avail, filterDate));
|
pub.availabilities.filter(a => a.startTime > filterTimeFrom && a.endTime < filterTimeTo)
|
||||||
});
|
});
|
||||||
|
publishers.filter(pub => pub.availabilities.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
return publishers;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// exports.getCoverMePublisherEmails = async function (shiftId) {
|
||||||
|
async function getCoverMePublisherEmails(shiftId) {
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
|
let 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,
|
||||||
|
firstName: pub.firstName,
|
||||||
|
lastName: pub.lastName,
|
||||||
|
name: pub.firstName + " " + pub.lastName,
|
||||||
|
email: pub.email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function matchesAvailability(avail, filterDate) {
|
let shift = await prisma.shift.findUnique({
|
||||||
// Setting the start and end time of the filterDate
|
where: {
|
||||||
filterDate.setHours(0, 0, 0, 0);
|
id: shiftId
|
||||||
const filterDateEnd = new Date(filterDate);
|
},
|
||||||
filterDateEnd.setHours(23, 59, 59, 999);
|
include: {
|
||||||
|
assignments: {
|
||||||
|
include: {
|
||||||
|
publisher: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let availableIn = new Date(shift.startTime)
|
||||||
|
let availablePublishers = await filterPublishersNew("id,firstName,lastName,email", availableIn,
|
||||||
|
true, false, false, false, false);
|
||||||
|
|
||||||
// Return true if avail.startTime is between filterDate and filterDateEnd
|
//filter out publishers that are already assigned to the shift
|
||||||
return avail.startTime >= filterDate && avail.startTime <= filterDateEnd;
|
availablePublishers = availablePublishers.filter(pub => {
|
||||||
|
return shift.assignments.findIndex(assignment => assignment.publisher.id == pub.id) == -1;
|
||||||
|
});
|
||||||
|
//subscribed list includes only publishers that are not already assigned to the shift
|
||||||
|
subscribedPublishers = subscribedPublishers.filter(pub => {
|
||||||
|
return availablePublishers.findIndex(availablePub => availablePub.id == pub.id) == -1
|
||||||
|
&& shift.assignments.findIndex(assignment => assignment.publisher.id == pub.id) == -1;
|
||||||
|
});
|
||||||
|
//return names and email info only
|
||||||
|
|
||||||
|
availablePublishers = availablePublishers.map(pub => {
|
||||||
|
return {
|
||||||
|
id: pub.id,
|
||||||
|
firstName: pub.firstName,
|
||||||
|
lastName: pub.lastName,
|
||||||
|
name: pub.firstName + " " + pub.lastName,
|
||||||
|
email: pub.email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { shift, availablePublishers: availablePublishers, subscribedPublishers };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
@ -527,5 +578,6 @@ module.exports = {
|
|||||||
findPublisherAvailability,
|
findPublisherAvailability,
|
||||||
runSqlFile,
|
runSqlFile,
|
||||||
getAvailabilities,
|
getAvailabilities,
|
||||||
filterPublishersNew
|
filterPublishersNew,
|
||||||
|
getCoverMePublisherEmails
|
||||||
};
|
};
|
@ -27,8 +27,7 @@ if (process.env.EMAIL_SERVICE.toLowerCase() === "mailtrap") {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else if (process.env.EMAIL_SERVICE.toLowerCase() === "gmail") {
|
||||||
if (process.env.EMAIL_SERVICE.toLowerCase() === "gmail") {
|
|
||||||
transporter = nodemailer.createTransport({
|
transporter = nodemailer.createTransport({
|
||||||
service: "gmail",
|
service: "gmail",
|
||||||
auth: {
|
auth: {
|
||||||
@ -71,7 +70,15 @@ function normalizeEmailAddresses(to) {
|
|||||||
|
|
||||||
return emails; // Always returns an array
|
return emails; // Always returns an array
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Final email sending function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="to">Email address or array of email addresses</param>
|
||||||
|
/// <param name="subject">Email subject</param>
|
||||||
|
/// <param name="text">Plain text version of the email</param>
|
||||||
|
/// <param name="html">HTML version of the email</param>
|
||||||
|
/// <param name="attachments">Array of attachment objects</param>
|
||||||
|
/// <returns>Promise</returns>
|
||||||
|
|
||||||
exports.SendEmail = async function (to, subject, text, html, attachments = []) {
|
exports.SendEmail = async function (to, subject, text, html, attachments = []) {
|
||||||
let sender = process.env.EMAIL_SENDER || '"Специално Свидетелстване София" <sofia@mwitnessing.com>';
|
let sender = process.env.EMAIL_SENDER || '"Специално Свидетелстване София" <sofia@mwitnessing.com>';
|
||||||
|
@ -5,7 +5,7 @@ const logConfiguration = {
|
|||||||
'transports': [
|
'transports': [
|
||||||
new winston.transports.DailyRotateFile({
|
new winston.transports.DailyRotateFile({
|
||||||
filename: './logs/application-%DATE%.log',
|
filename: './logs/application-%DATE%.log',
|
||||||
datePattern: 'YYYY-MM-DD-HH',
|
datePattern: 'YYYY-MM-DD', // new file is created every hour: 'YYYY-MM-DD-HH'
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: '20m',
|
maxSize: '20m',
|
||||||
maxFiles: '90d',
|
maxFiles: '90d',
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{{!--Subject: ССОМ: Нужен е заместник --}}
|
{{!--Subject: ССОМ: Нужен е заместник --}}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3>Търси се зместник:
|
{{!-- <h3>Търси се зместник: --}}
|
||||||
{{!-- за смяна на {{placeName}} за {{dateStr}}! --}}
|
{{!-- за смяна на {{placeName}} за {{dateStr}}! --}}
|
||||||
</h3>
|
{{!-- </h3> --}}
|
||||||
<p>Здравей {{firstName}},</p>
|
<p>Здравей {{firstName}},</p>
|
||||||
<p>{{user.prefix}} {{user.firstName}} {{user.lastName}} търси заместник.</p>
|
<p>{{user.prefix}} {{user.firstName}} {{user.lastName}} търси заместник.</p>
|
||||||
{{!-- <p><strong>Shift Details:</strong></p> --}}
|
{{!-- <p><strong>Shift Details:</strong></p> --}}
|
||||||
|
Reference in New Issue
Block a user