diff --git a/.env.test b/.env.test index 94abc74..38c9d2f 100644 --- a/.env.test +++ b/.env.test @@ -17,8 +17,8 @@ AUTH0_ISSUER=https://dev-wkzi658ckibr1amv.us.auth0.com # GOOGLE_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com # GOOGLE_SECRET=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 -EMAIL_SERVICE=mailtrap -MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io -MAILTRAP_HOST=live.smtp.mailtrap.io -MAILTRAP_USER=api -MAILTRAP_PASS=1cfe82e747b8dc3390ed08bb16e0f48d \ No newline at end of file +# EMAIL_SERVICE=mailtrap +# MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io +# MAILTRAP_HOST=live.smtp.mailtrap.io +# MAILTRAP_USER=api +# MAILTRAP_PASS=1cfe82e747b8dc3390ed08bb16e0f48d \ No newline at end of file diff --git a/_doc/ToDo.md b/_doc/ToDo.md index 123edcb..b5f5b1f 100644 --- a/_doc/ToDo.md +++ b/_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. -otchet - za denq -делете цонфирм -статистика - фкс (янка) + posledno vlizane +[x] OK last login. pubs. +[x] OK otchet - za denq +[x] OK - делете цонфирм +[x] статистика - фкс (янка) + posledno vlizane -заместник, предпочитание в миналото - да не може. -Да иска потвърждение преди да заместиш някой -да не се виждат непубликуваните смени в Моите смени -да не се виждат старите предпочитания ако не си админ в публишерс \ No newline at end of file +[x] OK заместник, предпочитание в миналото - да не може. +[] Да иска потвърждение преди да заместиш някой +[] да не се виждат непубликуваните смени в Моите смени +[] да не се виждат старите предпочитания ако не си админ в публишерс \ No newline at end of file diff --git a/components/calendar/avcalendar.tsx b/components/calendar/avcalendar.tsx index 258463c..13acedd 100644 --- a/components/calendar/avcalendar.tsx +++ b/components/calendar/avcalendar.tsx @@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react'; import { Calendar, momentLocalizer, dateFnsLocalizer } from 'react-big-calendar'; import 'react-big-calendar/lib/css/react-big-calendar.css'; import AvailabilityForm from '../availability/AvailabilityForm'; +import ProtectedRoute from '../protectedRoute'; +import { UserRole } from "@prisma/client"; import common from '../../src/helpers/common'; import { toast } from 'react-toastify'; @@ -16,6 +18,7 @@ import { MdToday } from 'react-icons/md'; import { useSwipeable } from 'react-swipeable'; import axiosInstance from '../../src/axiosSecure'; + // import { set, format, addDays } from 'date-fns'; // import { isEqual, isSameDay, getHours, getMinutes } from 'date-fns'; import { filter } from 'jszip'; @@ -45,6 +48,8 @@ const messages = { const AvCalendar = ({ publisherId, events, selectedDate }) => { + const isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN); + const [date, setDate] = useState(new Date()); //ToDo: see if we can optimize this const [evts, setEvents] = useState(events); // Existing events @@ -211,8 +216,9 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { if (!start || !end) return; //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 if (startdate.toDateString() !== enddate.toDateString()) { end = common.setTimeHHmm(startdate, "23:59"); @@ -299,22 +305,22 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { } - if (event.isActive) { - switch (event.type) { - case 'assignment': - // 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 - break; - case 'recurring': - backgroundColor = '#63b3ed'; // blue-300 from Tailwind CSS - break; - default: // availability - //backgroundColor = '#a0aec0'; // gray-400 from Tailwind CSS - break; - } - } else { - backgroundColor = '#a0aec0'; // Default color for inactive events + // if (event.isActive) { + switch (event.type) { + case 'assignment': + 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 + break; + case 'recurring': + backgroundColor = '#63b3ed'; // blue-300 from Tailwind CSS + break; + default: // availability + //backgroundColor = '#a0aec0'; // gray-400 from Tailwind CSS + break; } + // } else { + // backgroundColor = '#a0aec0'; // Default color for inactive events + // } return { style: { @@ -337,9 +343,7 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { const handleMouseLeave = () => setIsHovered(false); if (currentView !== 'agenda') { //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 - let bgColorClass = 'bg-gray-500'; // Default color for inactive events - var bgColor = event.isActive ? "" : "bg-gray-500"; + var bgColor = ""; //ToDo: fix this. maybe we're missing some properties // if (event.isFromPreviousMonth) { // // 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) { try { if (event.type === "recurring") { - //bgColor = "bg-blue-300"; event.title = common.getTimeFomatted(event.startTime) + " - " + common.getTimeFomatted(event.endTime); } else { diff --git a/components/protectedRoute.tsx b/components/protectedRoute.tsx index 9a9b88a..4c2adc9 100644 --- a/components/protectedRoute.tsx +++ b/components/protectedRoute.tsx @@ -68,3 +68,8 @@ export async function serverSideAuth({ req, allowedRoles }) { // Return the session if the user is authenticated and has the required role 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; +}; \ No newline at end of file diff --git a/components/publisher/SearchReplacement.js b/components/publisher/SearchReplacement.js index 527cbc0..5d69bf1 100644 --- a/components/publisher/SearchReplacement.js +++ b/components/publisher/SearchReplacement.js @@ -114,7 +114,7 @@ function ConfirmationModal({ isOpen, onClose, onConfirm, subscribedPublishers, a checked={selectedGroups.includes('availablePublishers')} onChange={() => handleToggleGroup('availablePublishers')} /> - На разположение: + На разположение :
{availablePublishers.map(pub => ( diff --git a/pages/api/email.ts b/pages/api/email.ts index facba07..08f6547 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -9,6 +9,7 @@ const emailHelper = require('../../src/helpers/email'); const { v4: uuidv4 } = require('uuid'); const CON = require("../../src/helpers/const"); import { EventLogType } from "@prisma/client"; +const logger = require('../../src/logger'); import fs from 'fs'; import path from 'path'; @@ -46,6 +47,7 @@ export default async function handler(req, res) { }); // Update the user status to accepted 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; 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 @@ -260,23 +261,35 @@ export default async function handler(req, res) { } }); - let subscribedPublishers = [], availablePublishers = []; - if (toSubscribed) { - //get all subscribed publisers - subscribedPublishers = await prisma.publisher.findMany({ - where: { - isSubscribedToCoverMe: true - } - }); + let targetEmails = await data.getCoverMePublisherEmails(assignment.shift.id); + if (!toSubscribed) { + targetEmails.subscribedPublishers = []; + } + if (!toAvailable) { + 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 - let pubsToSend = subscribedPublishers.concat(availablePublishers). + let pubsToSend = targetEmails.subscribedPublishers.concat(targetEmails.availablePublishers). filter((item, index, self) => index === self.findIndex((t) => ( 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"); - await prisma.eventLog.create({ + let eventLog = await prisma.eventLog.create({ data: { date: new Date(), 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(", "), } }); + 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 for (let i = 0; i < pubsToSend.length; i++) { diff --git a/pages/api/index.ts b/pages/api/index.ts index bcd17ac..adc1129 100644 --- a/pages/api/index.ts +++ b/pages/api/index.ts @@ -2,8 +2,9 @@ import { getToken } from "next-auth/jwt"; import { NextApiRequest, NextApiResponse } from 'next' import { DayOfWeek, AvailabilityType } from '@prisma/client'; const common = require('../../src/helpers/common'); -const data = require('../../src/helpers/data'); +const dataHelper = require('../../src/helpers/data'); const subq = require('../../prisma/bl/subqueries'); +import { addMinutes } from 'date-fns'; import fs from 'fs'; import path from 'path'; @@ -171,7 +172,7 @@ export default async function handler(req, res) { // find publisher by full name or email case "findPublisher": 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); break; @@ -350,41 +351,8 @@ export default async function handler(req, res) { 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 }); + let data = await dataHelper.getCoverMePublisherEmails(parseInt(req.query.shiftId)); + res.status(200).json(data); break; 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) { - return data.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, isWithStats, includeOldAvailabilities); + return dataHelper.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, false, isWithStats, includeOldAvailabilities); } // availabilites filter: @@ -837,8 +805,48 @@ function matchesAvailability(avail, filterDate) { async function getCalendarEvents(publisherId, date, availabilities = true, assignments = true) { const result = []; - let pubs = await filterPublishers("id,firstName,lastName,email".split(","), "", date, assignments, availabilities, date ? true : false, publisherId); - let publisher = pubs[0]; + // let pubs = await filterPublishers("id,firstName,lastName,email".split(","), "", date, assignments, availabilities, date ? true : false, publisherId); + + 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 (availabilities) { publisher.availabilities?.forEach(item => { diff --git a/pages/cart/calendar/index.tsx b/pages/cart/calendar/index.tsx index 5bbb9ae..0ab474b 100644 --- a/pages/cart/calendar/index.tsx +++ b/pages/cart/calendar/index.tsx @@ -603,7 +603,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) { message="Това ще изпрати имейли до всички участници за смените им през избрания месец. Сигурни ли сте?" />