Merge remote-tracking branch 'origin/main' into production
This commit is contained in:
7
.env
7
.env
@ -46,7 +46,8 @@ GITHUB_SECRET=
|
||||
TWITTER_ID=
|
||||
TWITTER_SECRET=
|
||||
|
||||
|
||||
EMAIL_BYPASS_TO=mwitnessing@gmail.com
|
||||
EMAIL_SENDER='"Специално Свидетелстване София " <mwitnessing@gmail.com>'
|
||||
# EMAIL_SERVER=smtp://8ec69527ff2104:c7bc05f171c96c@smtp.mailtrap.io:2525
|
||||
EMAIL_FROM=noreply@mwitnessing.com
|
||||
|
||||
@ -61,8 +62,8 @@ MAILERSEND_PORT=587
|
||||
MAILERSEND_USER=MS_bL93ka@mwitnessing.com
|
||||
MAILERSEND_PASS=v23Z2XrDSNjHJxgo
|
||||
|
||||
GMAIL_EMAIL_USERNAME=
|
||||
GMAIL_EMAIL_APP_PASS=
|
||||
EMAIL_GMAIL_USERNAME=mwitnessing
|
||||
EMAIL_GMAIL_APP_PASS="acys uzsp eere qzyh"
|
||||
|
||||
TELEGRAM_BOT=false
|
||||
TELEGRAM_BOT_TOKEN=7050075088:AAH6VRpNCyQd9x9sW6CLm6q0q4ibUgYBfnM
|
||||
|
@ -9,6 +9,7 @@ NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638
|
||||
DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia
|
||||
|
||||
|
||||
EMAIL_BYPASS_TO=
|
||||
MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io
|
||||
MAILTRAP_HOST=live.smtp.mailtrap.io
|
||||
MAILTRAP_USER=api
|
||||
|
13
.env.test
13
.env.test
@ -10,26 +10,13 @@ NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638
|
||||
# ? do we need to duplicate this? already defined in the deoployment yml file
|
||||
DATABASE=mysql://jwpwsofia_demo:dwxhns9p9vp248@mariadb:3306/jwpwsofia_demo
|
||||
|
||||
APPLE_ID=
|
||||
APPLE_TEAM_ID=
|
||||
APPLE_PRIVATE_KEY=
|
||||
APPLE_KEY_ID=
|
||||
|
||||
AUTH0_ID=Aa9f3HJowauUrmBVY4iQzQJ7fYsaZDbK
|
||||
AUTH0_SECRET=_c0O9GkyRXkoWMQW7jNExnl6UoXN6O4oD3mg7NZ_uHVeAinCUtcTAkeQmcKXpZ4x
|
||||
AUTH0_ISSUER=https://dev-wkzi658ckibr1amv.us.auth0.com
|
||||
|
||||
FACEBOOK_ID=
|
||||
FACEBOOK_SECRET=
|
||||
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
# GOOGLE_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com
|
||||
# GOOGLE_SECRET=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
|
||||
|
||||
TWITTER_ID=
|
||||
TWITTER_SECRET=
|
||||
|
||||
MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io
|
||||
MAILTRAP_HOST=live.smtp.mailtrap.io
|
||||
MAILTRAP_USER=api
|
||||
|
@ -19,7 +19,7 @@ services:
|
||||
- GIT_USERNAME=deploy
|
||||
- GIT_PASSWORD=L3Kr2R438u4F7
|
||||
command: sh -c " cd /app && npm install && npm run prod; tail -f /dev/null"
|
||||
#command: sh -c " cd /app && n
|
||||
#command: sh -c " cd /app && tail -f /dev/null"
|
||||
tty: true
|
||||
stdin_open: true
|
||||
restart: always
|
||||
|
@ -10,6 +10,8 @@ import { bgBG } from '../x-date-pickers/locales/bgBG';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
const common = require('src/helpers/common');
|
||||
//todo import Availability type from prisma schema
|
||||
import { isBefore, addMinutes, isAfter, isEqual, set, getHours, getMinutes, getSeconds } from 'date-fns';
|
||||
|
||||
|
||||
|
||||
const fetchConfig = async () => {
|
||||
@ -183,59 +185,6 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
||||
return groupedIntervals;
|
||||
}
|
||||
|
||||
// // const firstSlotWithTransport = timeSlots[0].checked && timeSlots[0]?.isWithTransport;
|
||||
// // const lastSlotWithTransport = timeSlots[timeSlots.length - 1].checked && timeSlots[timeSlots.length - 1]?.isWithTransport;
|
||||
// function createAvailabilityFromGroup(group) {
|
||||
// let startTime = new Date(day);
|
||||
// startTime.setHours(group[0].startTime.getHours(), group[0].startTime.getMinutes(), group[0].startTime.getSeconds(), 0);
|
||||
|
||||
// let endTime = new Date(day);
|
||||
// endTime.setHours(group[group.length - 1].endTime.getHours(), group[group.length - 1].endTime.getMinutes(), group[group.length - 1].endTime.getSeconds(), 0);
|
||||
|
||||
|
||||
// return {
|
||||
// name: common.getTimeFomatted(startTime) + "-" + common.getTimeFomatted(endTime),
|
||||
// publisherId: publisher.id,
|
||||
// startTime: startTime,
|
||||
// endTime: endTime,
|
||||
// isWithTransportIn: group[0].isFirst && timeSlots[0].isWithTransport,
|
||||
// isWithTransportOut: group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport,
|
||||
// dayofweek: common.getDayOfWeekNameEnEnumForDate(day.getDay()),
|
||||
// repeatWeekly: doRepeat,
|
||||
// dayOfMonth: doRepeat ? null : startTime.getDate(),
|
||||
// endDate: doRepeat ? repeatUntil : null,
|
||||
// dateOfEntry: new Date(),
|
||||
// };
|
||||
// }
|
||||
|
||||
// function updateAvailabilityFromGroup(availability, group) {
|
||||
// availability.startTime.setTime(group[0].startTime);
|
||||
// availability.endTime.setTime(group[group.length - 1].endTime);
|
||||
// availability.name = common.getTimeFomatted(availability.startTime) + "-" + common.getTimeFomatted(availability.endTime);
|
||||
|
||||
// availability.isWithTransportIn = group[0].isFirst && timeSlots[0].isWithTransport;
|
||||
// availability.isWithTransportOut = group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport;
|
||||
|
||||
// delete availability.weekOfMonth;
|
||||
// if (doRepeat) {
|
||||
// availability.repeatWeekly = true;
|
||||
// availability.dayOfMonth = null;
|
||||
// availability.weekOfMonth = 0;
|
||||
// availability.endDate = repeatUntil;
|
||||
// } else {
|
||||
// availability.repeatWeekly = false;
|
||||
// availability.dayOfMonth = availability.startTime.getDate();
|
||||
// availability.endDate = null;
|
||||
// }
|
||||
|
||||
// availability.dateOfEntry = new Date();
|
||||
// if (availability.parentAvailabilityId) {
|
||||
// availability.parentAvailability = { connect: { id: parentAvailabilityId } };
|
||||
// }
|
||||
// delete availability.parentAvailabilityId;
|
||||
|
||||
// return availability;
|
||||
// }
|
||||
// Common function to set shared properties
|
||||
function setSharedAvailabilityProperties(availability, group, timeSlots) {
|
||||
let startTime = new Date(availability.startTime || day);
|
||||
@ -332,22 +281,25 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
||||
}
|
||||
|
||||
// console.log("AvailabilityForm: publisherId: " + publisher.id + ", id: " + availabilit .id, ", inline: " + isInline);
|
||||
|
||||
//ToDo: this is examplary function to be used in the future. replace all date/time related functions with this one
|
||||
const generateTimeSlots = (start, end, increment, items) => {
|
||||
const slots = [];
|
||||
let currentTime = start.getTime();
|
||||
let currentTime = start;
|
||||
|
||||
const endTime = end.getTime();
|
||||
const baseDate = new Date(2000, 0, 1); // Use a constant date for all time comparisons
|
||||
|
||||
while (currentTime < endTime) {
|
||||
let slotStart = new Date(currentTime);
|
||||
let slotEnd = new Date(currentTime + increment * 60000); // increment is in minutes
|
||||
while (isBefore(currentTime, end)) {
|
||||
let slotStart = normalizeTime(currentTime, baseDate);
|
||||
let slotEnd = normalizeTime(addMinutes(currentTime, increment), baseDate);
|
||||
|
||||
const isChecked = items.some(item =>
|
||||
item.startTime && item.endTime &&
|
||||
(slotStart.getTime() < item.endTime.getTime()) &&
|
||||
(slotEnd.getTime() > item.startTime.getTime())
|
||||
);
|
||||
const isChecked = items.some(item => {
|
||||
let itemStart = item.startTime ? normalizeTime(new Date(item.startTime), baseDate) : null;
|
||||
let itemEnd = item.endTime ? normalizeTime(new Date(item.endTime), baseDate) : null;
|
||||
|
||||
return itemStart && itemEnd &&
|
||||
(slotStart.getTime() < itemEnd.getTime()) &&
|
||||
(slotEnd.getTime() > itemStart.getTime());
|
||||
});
|
||||
|
||||
slots.push({
|
||||
startTime: slotStart,
|
||||
@ -355,10 +307,9 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
||||
isChecked: isChecked,
|
||||
});
|
||||
|
||||
currentTime += increment * 60000; // Increment in milliseconds (minutes to milliseconds)
|
||||
currentTime = addMinutes(currentTime, increment);
|
||||
}
|
||||
|
||||
// Optional: Add isFirst, isLast, and isWithTransport properties
|
||||
if (slots.length > 0 && items?.length > 0) {
|
||||
slots[0].isFirst = true;
|
||||
slots[slots.length - 1].isLast = true;
|
||||
@ -369,6 +320,16 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
||||
return slots;
|
||||
};
|
||||
|
||||
// Normalize the time part of a date by using a base date
|
||||
function normalizeTime(date, baseDate) {
|
||||
return set(baseDate, {
|
||||
hours: getHours(date),
|
||||
minutes: getMinutes(date),
|
||||
seconds: getSeconds(date),
|
||||
milliseconds: 0
|
||||
});
|
||||
}
|
||||
|
||||
const TimeSlotCheckboxes = ({ slots, setSlots, items: [] }) => {
|
||||
const [allDay, setAllDay] = useState(slots.every(slot => slot.isChecked));
|
||||
const handleAllDayChange = (e) => {
|
||||
|
@ -16,7 +16,13 @@ import { MdToday } from 'react-icons/md';
|
||||
|
||||
import { useSwipeable } from 'react-swipeable';
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
import { set } from 'date-fns';
|
||||
// import { set, format, addDays } from 'date-fns';
|
||||
import { isEqual, isSameDay, getHours, getMinutes } from 'date-fns';
|
||||
import { filter } from 'jszip';
|
||||
import e from 'express';
|
||||
|
||||
|
||||
|
||||
|
||||
// Set moment to use the Bulgarian locale
|
||||
moment.locale('bg');
|
||||
@ -162,6 +168,31 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
occurrences.push(occurrence);
|
||||
}
|
||||
};
|
||||
const filterEvents = (evts, publisherId, startdate) => {
|
||||
setDate(startdate); // Assuming setDate is a function that sets some state or context
|
||||
|
||||
// Filter events based on the publisher ID and the start date/time
|
||||
const existingEvents = evts?.filter(event => {
|
||||
// Ensure the event belongs to the specified publisher
|
||||
const isPublisherMatch = (event.publisher?.id || event.publisherId) === publisherId;
|
||||
|
||||
let isDateMatch;
|
||||
if (event.repeatWeekly && event.date) {
|
||||
// Compare only the time part
|
||||
const eventDate = new Date(event.startTime);
|
||||
const isSameHour = getHours(eventDate) === getHours(startdate);
|
||||
const isSameMinute = getMinutes(eventDate) === getMinutes(startdate);
|
||||
isDateMatch = isSameHour && isSameMinute;
|
||||
} else if (event.date) {
|
||||
// Compare the full date
|
||||
isDateMatch = isSameDay(new Date(event.date), startdate);
|
||||
}
|
||||
|
||||
return isPublisherMatch && isDateMatch;
|
||||
});
|
||||
|
||||
return existingEvents;
|
||||
};
|
||||
|
||||
// Define min and max times
|
||||
const minHour = 8; // 8:00 AM
|
||||
@ -177,7 +208,8 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
const enddate = typeof end === 'string' ? new Date(end) : end;
|
||||
|
||||
if (!start || !end) return;
|
||||
if (startdate < new Date() || end < new Date() || startdate > end) return;
|
||||
//readonly for past dates (ToDo: if not admin)
|
||||
//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()) {
|
||||
@ -198,52 +230,19 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
setDate(start);
|
||||
|
||||
// get exising events for the selected date
|
||||
const existingEvents = evts?.filter(event => (event.publisher?.id || event.publisherId) === publisherId && new Date(event.date).toDateString() === startdate.toDateString());
|
||||
// const existingEvents = evts?.filter(event => {
|
||||
// return event.publisherId === publisherId &&
|
||||
// new Date(event.startTime).getFullYear() === start.getFullYear() &&
|
||||
// new Date(event.startTime).getMonth() === start.getMonth() &&
|
||||
// new Date(event.startTime).getDate() === start.getDate();
|
||||
// });
|
||||
let existingEvents = filterEvents(evts, publisherId, startdate);
|
||||
// if existingEvents is empty - create new with the selected range
|
||||
if (existingEvents.length === 0) {
|
||||
existingEvents = [{ startTime: start, endTime: end }];
|
||||
}
|
||||
console.log("handleSelect: " + existingEvents);
|
||||
setSelectedEvents(existingEvents);
|
||||
|
||||
// setSelectedEvent({
|
||||
// date: start,
|
||||
// startTime: start,
|
||||
// endTime: end,
|
||||
// dayOfMonth: start.getDate(),
|
||||
// isActive: true,
|
||||
// publisherId: publisherId,
|
||||
// // Add any other initial values needed
|
||||
// //set dayOfMonth to null, so that we repeat the availability every week
|
||||
// dayOfMonth: null,
|
||||
|
||||
// });
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleEventClick = (event) => {
|
||||
if (event.type === "assignment") return;
|
||||
handleSelect({ start: event.startTime, end: event.endTime });
|
||||
// Handle event click
|
||||
// const eventForEditing = {
|
||||
// ...event,
|
||||
// startTime: new Date(event.startTime),
|
||||
// endTime: new Date(event.endTime),
|
||||
// publisherId: event.publisherId || event.publisher?.connect?.id,
|
||||
// repeatWeekly: event.repeatWeekly || false,
|
||||
// };
|
||||
// //strip title, start, end and allDay properties
|
||||
// delete eventForEditing.title;
|
||||
// delete eventForEditing.start;
|
||||
// delete eventForEditing.end;
|
||||
// delete eventForEditing.type;
|
||||
// delete eventForEditing.publisher
|
||||
// console.log("handleEventClick: " + eventForEditing);
|
||||
// setSelectedEvents([eventForEditing]);
|
||||
// setIsModalOpen(true);
|
||||
|
||||
};
|
||||
|
||||
const handleDialogClose = async (dialogEvent) => {
|
||||
@ -256,10 +255,8 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => {
|
||||
newEvents.forEach(event => {
|
||||
event.startTime = new Date(event.startTime);
|
||||
event.endTime = new Date(event.endTime);
|
||||
|
||||
});
|
||||
setEvents(newEvents);
|
||||
|
||||
}
|
||||
|
||||
console.log("handleSave: ", dialogEvent);
|
||||
|
@ -7,7 +7,7 @@ import DayOfWeek from "../DayOfWeek";
|
||||
import TextEditor from "../TextEditor";
|
||||
import FileUploadWithPreview from 'components/FileUploadWithPreview ';
|
||||
|
||||
import ProtectedRoute, { serverSideAuth } from "../..//components/protectedRoute";
|
||||
import ProtectedRoute, { serverSideAuth } from "../../components/protectedRoute";
|
||||
import { UserRole } from "@prisma/client";
|
||||
|
||||
const common = require('src/helpers/common');
|
||||
|
@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
import ProtectedRoute, { serverSideAuth } from "../../components/protectedRoute";
|
||||
|
||||
//add months to date. works with negative numbers and numbers > 12
|
||||
export function addMonths(numOfMonths, date) {
|
||||
@ -53,6 +54,23 @@ export default function PublisherCard({ publisher }) {
|
||||
console.log(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
const handleLoginAs = async (userId) => {
|
||||
const response = await fetch('/api/auth/login-as', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ userId }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
// Assuming you have some context or state management to update the session
|
||||
updateSession(data.session);
|
||||
} else {
|
||||
alert("Failed to impersonate user.");
|
||||
}
|
||||
};
|
||||
|
||||
return isCardVisible ? (
|
||||
// className="block p-6 max-w-sm bg-white rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3"
|
||||
@ -89,6 +107,10 @@ export default function PublisherCard({ publisher }) {
|
||||
<path fillRule="evenodd" d="M4.293 4.293A1 1 0 015.707 3.707L10 8l4.293-4.293a1 1 0 111.414 1.414L11.414 9l4.293 4.293a1 1 0 01-1.414 1.414L10 10.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 9 4.293 4.707a1 1 0 010-1.414z" clipRule="evenodd" /> */}
|
||||
</svg>
|
||||
</button>
|
||||
<ProtectedRoute>
|
||||
|
||||
<button onClick={() => handleLoginAs(publisher.id)}>Login as</button>
|
||||
</ProtectedRoute>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.cardFadeOut {
|
||||
|
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;
|
BIN
mwitnessing_totp_setup.png
Normal file
BIN
mwitnessing_totp_setup.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -5,7 +5,7 @@ const withPWA = require('next-pwa')({
|
||||
register: true, // ?
|
||||
publicExcludes: ["!_error*.js"], //?
|
||||
|
||||
disable: process.env.NODE_ENV === 'development',
|
||||
//disable: process.env.NODE_ENV === 'development',
|
||||
})
|
||||
|
||||
module.exports = withPWA({
|
||||
|
@ -18,7 +18,7 @@ const common = require("../../../src/helpers/common");
|
||||
import { isLoggedIn, setAuthTokens, clearAuthTokens, getAccessToken, getRefreshToken } from 'axios-jwt'
|
||||
|
||||
|
||||
console.log("appleID:", process.env.APPLE_ID);
|
||||
console.log("appleID:", process.env.APPLE_APP_ID);
|
||||
// console.log(process.env.EMAIL_SERVER)
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
@ -43,7 +43,7 @@ export const authOptions: NextAuthOptions = {
|
||||
}
|
||||
}),
|
||||
AppleProvider({
|
||||
clientId: process.env.APPLE_ID,
|
||||
clientId: process.env.APPLE_APP_ID,
|
||||
clientSecret: process.env.APPLE_SECRET
|
||||
}),
|
||||
// AzureADProvider({
|
||||
|
40
pages/api/auth/login-as.js
Normal file
40
pages/api/auth/login-as.js
Normal file
@ -0,0 +1,40 @@
|
||||
// pages/api/auth/login-as.js
|
||||
|
||||
import { getSession } from "next-auth/react";
|
||||
import prisma from '../../../lib/prisma'; // Adjust the path as per your setup
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const session = await getSession({ req });
|
||||
if (session && session.user.role === 'admin') {
|
||||
const { userId } = req.body;
|
||||
|
||||
const userToImpersonate = await prisma.publisher.findUnique({
|
||||
where: { id: userId }
|
||||
});
|
||||
|
||||
if (userToImpersonate) {
|
||||
// Create a custom session object for the impersonated user
|
||||
const impersonatedSession = {
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
id: userToImpersonate.id,
|
||||
email: userToImpersonate.email,
|
||||
name: userToImpersonate.name,
|
||||
role: userToImpersonate.role,
|
||||
// add other necessary fields
|
||||
},
|
||||
impersonating: true, // flag to indicate impersonation
|
||||
originalUser: session.user // save the original user for later
|
||||
};
|
||||
|
||||
// Here you would typically use some method to create a session server-side
|
||||
// For this example, we'll just send the impersonated session as a response
|
||||
res.status(200).json({ session: impersonatedSession });
|
||||
} else {
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
} else {
|
||||
res.status(403).json({ error: 'Unauthorized' });
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { getToken } from "next-auth/jwt";
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { createRouter, expressWrapper } from "next-connect";
|
||||
const common = require('../../src/helpers/common');
|
||||
const data = require('../../src/helpers/data');
|
||||
const emailHelper = require('../../src/helpers/email');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const CON = require("../../src/helpers/const");
|
||||
@ -213,9 +214,10 @@ export default async function handler(req, res) {
|
||||
//get from POST data: shiftId, assignmentId, date
|
||||
//let shiftId = req.body.shiftId;
|
||||
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({
|
||||
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
|
||||
let newPublicGuid = uuidv4();
|
||||
@ -246,29 +250,44 @@ export default async function handler(req, res) {
|
||||
}
|
||||
});
|
||||
|
||||
//get all subscribed publisers
|
||||
const subscribedPublishers = await prisma.publisher.findMany({
|
||||
where: {
|
||||
isSubscribedToCoverMe: true
|
||||
}
|
||||
});
|
||||
let 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);
|
||||
|
||||
}
|
||||
//concat and remove duplicate emails
|
||||
let pubsToSend = subscribedPublishers.concat(availablePublishers).
|
||||
filter((item, index, self) =>
|
||||
index === self.findIndex((t) => (
|
||||
t.email === item.email //and exclude the user himself
|
||||
)) //&& item.email !== user.email
|
||||
);
|
||||
console.log("Sending CoverMe request to " + pubsToSend.length + " publishers");
|
||||
|
||||
//send email to all subscribed publishers
|
||||
for (let i = 0; i < subscribedPublishers.length; i++) {
|
||||
if (subscribedPublishers[i].id == user.id) {
|
||||
continue;
|
||||
}
|
||||
for (let i = 0; i < pubsToSend.length; i++) {
|
||||
|
||||
//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 = {
|
||||
user: user,
|
||||
shiftId: assignment.shiftId,
|
||||
acceptUrl: acceptUrl,
|
||||
prefix: user.isMale ? "Брат" : "Сестра",
|
||||
firstName: subscribedPublishers[i].firstName,
|
||||
lastName: subscribedPublishers[i].lastName,
|
||||
email: subscribedPublishers[i].email,
|
||||
firstName: pubsToSend[i].firstName,
|
||||
lastName: pubsToSend[i].lastName,
|
||||
email: pubsToSend[i].email,
|
||||
placeName: assignment.shift.cartEvent.location.name,
|
||||
dateStr: common.getDateFormated(assignment.shift.startTime),
|
||||
time: common.formatTimeHHmm(assignment.shift.startTime),
|
||||
@ -276,8 +295,8 @@ export default async function handler(req, res) {
|
||||
};
|
||||
let results = emailHelper.SendEmailHandlebars(
|
||||
{
|
||||
name: subscribedPublishers[i].firstName + " " + subscribedPublishers[i].lastName,
|
||||
email: subscribedPublishers[i].email
|
||||
name: pubsToSend[i].firstName + " " + pubsToSend[i].lastName,
|
||||
email: pubsToSend[i].email
|
||||
}, "coverMe", model);
|
||||
// if (results) {
|
||||
// console.log("Error sending email: " + error);
|
||||
@ -285,10 +304,12 @@ export default async function handler(req, res) {
|
||||
//}
|
||||
|
||||
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;
|
||||
default:
|
||||
return res.status(400).json({ message: "Invalid action" });
|
||||
|
@ -123,10 +123,10 @@ export default async function handler(req, res) {
|
||||
const availabilities = req.body;
|
||||
//! console.log("createAvailabilities: " + JSON.stringify(availabilities));
|
||||
try {
|
||||
await prisma.availability.createMany({
|
||||
let createResults = await prisma.availability.createMany({
|
||||
data: availabilities
|
||||
});
|
||||
res.status(200).json({ "message": "ok" });
|
||||
res.status(200).json({ "message": "ok", "results": createResults });
|
||||
} catch (error) {
|
||||
console.error("Error creating availabilities: " + error);
|
||||
res.status(500).json({ error });
|
||||
@ -347,6 +347,43 @@ export default async function handler(req, res) {
|
||||
res.status(200).json({ "message": "ok" });
|
||||
|
||||
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:
|
||||
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) {
|
||||
|
||||
// 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;
|
||||
return data.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, isWithStats);
|
||||
}
|
||||
|
||||
// availabilites filter:
|
||||
|
@ -7,6 +7,7 @@ import common from '../../../src/helpers/common';
|
||||
import Modal from 'components/Modal';
|
||||
import ConfirmationModal from 'components/ConfirmationModal';
|
||||
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 { 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 (
|
||||
<Layout>
|
||||
@ -121,12 +107,8 @@ export default function MySchedulePage({ assignments }) {
|
||||
>
|
||||
Избери Заместник
|
||||
</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"
|
||||
onClick={() => searchReplacement(assignment.id)}
|
||||
>
|
||||
Търси заместник
|
||||
</button>
|
||||
|
||||
<SearchReplacement shiftId={assignment.shift.id} assignmentId={assignment.id} />
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
@ -1,15 +1,18 @@
|
||||
import { useState } from 'react';
|
||||
import Layout from "../../../components/layout";
|
||||
import ProtectedRoute from '../../../components/protectedRoute';
|
||||
import { UserRole } from '@prisma/client';
|
||||
import { Prisma, UserRole } from '@prisma/client';
|
||||
import axiosServer from '../../../src/axiosServer';
|
||||
import common from '../../../src/helpers/common';
|
||||
import { filterPublishers, /* other functions */ } from '../../api/index';
|
||||
// import { filterPublishers, /* other functions */ } from '../../api/index';
|
||||
import data from '../../../src/helpers/data';
|
||||
|
||||
function ContactsPage({ publishers }) {
|
||||
// const data = require('../../src/helpers/data');
|
||||
|
||||
function ContactsPage({ publishers, allPublishers }) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const filteredPublishers = publishers.filter((publisher) =>
|
||||
const filteredPublishers = allPublishers.filter((publisher) =>
|
||||
publisher.firstName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
publisher.lastName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
publisher.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
@ -21,7 +24,7 @@ function ContactsPage({ publishers }) {
|
||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER, UserRole.USER]}>
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-xl font-semibold mb-4">Статистика </h1>
|
||||
<h5 className="text-lg font-semibold mb-4">{publishers.length} участника с предпочитания</h5>
|
||||
<h5 className="text-lg font-semibold mb-4">{publishers.length} участника с предпочитания за месеца (от {allPublishers.length} )</h5>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Търси по име, имейл или телефон..."
|
||||
@ -39,26 +42,55 @@ function ContactsPage({ publishers }) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredPublishers.map((pub) => (
|
||||
<tr key={pub.id}>
|
||||
<td className="border-b p-4 pl-8" title={pub.lastUpdate}>{pub.firstName} {pub.lastName}</td>
|
||||
<td className="border-b p-4">
|
||||
<span title="Възможност: часове | дни" className={`badge py-1 px-2 rounded-md text-xs ${pub.currentMonthAvailabilityHoursCount || pub.currentMonthAvailabilityDaysCount ? 'bg-teal-500 text-white' : 'bg-teal-200 text-gray-300'} hover:underline`} >
|
||||
{pub.currentMonthAvailabilityDaysCount || 0} | {pub.currentMonthAvailabilityHoursCount || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td className="border-b p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<span title="участия тази седмица" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentWeekAssignments ? 'bg-yellow-500 text-white' : 'bg-yellow-200 text-gray-400'}`}>{pub.currentWeekAssignments || 0}</span>
|
||||
<span title="участия този месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentMonthAssignments ? 'bg-green-500 text-white' : 'bg-green-200 text-gray-400'}`}>{pub.currentMonthAssignments || 0}</span>
|
||||
<span tooltip="участия миналия месец" title="участия миналия месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.previousMonthAssignments ? 'bg-blue-500 text-white' : 'bg-blue-200 text-gray-400'}`}>{pub.previousMonthAssignments || 0}</span>
|
||||
<button tooltip="желани участия този месец" title="желани участия" className={`badge py-1 px-2 rounded-md text-xs ${pub.desiredShiftsPerMonth ? 'bg-purple-500 text-white' : 'bg-purple-200 text-gray-400'}`}>{pub.desiredShiftsPerMonth || 0}</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{filteredPublishers.map((allPub) => {
|
||||
// Find the publisher in the publishers collection to access statistics
|
||||
const pub = publishers.find(publisher => publisher.id === allPub.id);
|
||||
|
||||
return (
|
||||
<tr key={allPub.id}>
|
||||
<td className="border-b p-4 pl-8" title={allPub.lastUpdate}>{allPub.firstName} {allPub.lastName}</td>
|
||||
{/* Display statistics if publisher is found */}
|
||||
{pub ? (
|
||||
<>
|
||||
<td className="border-b p-4">
|
||||
<span title="Възможност: часове | дни" className={`badge py-1 px-2 rounded-md text-xs ${pub.currentMonthAvailabilityHoursCount || pub.currentMonthAvailabilityDaysCount ? 'bg-teal-500 text-white' : 'bg-teal-200 text-gray-300'} hover:underline`}>
|
||||
{pub.currentMonthAvailabilityDaysCount || 0} | {pub.currentMonthAvailabilityHoursCount || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td className="border-b p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<span title="участия тази седмица" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentWeekAssignments ? 'bg-yellow-500 text-white' : 'bg-yellow-200 text-gray-400'}`}>{pub.currentWeekAssignments || 0}</span>
|
||||
<span title="участия този месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.currentMonthAssignments ? 'bg-green-500 text-white' : 'bg-green-200 text-gray-400'}`}>{pub.currentMonthAssignments || 0}</span>
|
||||
<span title="участия миналия месец" className={`badge py-1 px-2 rounded-full text-xs ${pub.previousMonthAssignments ? 'bg-blue-500 text-white' : 'bg-blue-200 text-gray-400'}`}>{pub.previousMonthAssignments || 0}</span>
|
||||
<button title="желани участия" className={`badge py-1 px-2 rounded-md text-xs ${pub.desiredShiftsPerMonth ? 'bg-purple-500 text-white' : 'bg-purple-200 text-gray-400'}`}>{pub.desiredShiftsPerMonth || 0}</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<td className="border-b p-4">
|
||||
|
||||
</td>
|
||||
<td className="border-b p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<span className="badge py-1 px-2 rounded-md text-xs bg-gray-300 text-gray-500" title="участия този месец">
|
||||
{allPub.currentMonthAssignments || 0}
|
||||
</span>
|
||||
<span className="badge py-1 px-2 rounded-md text-xs bg-gray-300 text-gray-500" title="участия миналия месец">
|
||||
{allPub.previousMonthAssignments || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="border-b p-4"></td> {/* Empty cell for alignment */}
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -71,10 +103,29 @@ function ContactsPage({ publishers }) {
|
||||
|
||||
export default ContactsPage;
|
||||
|
||||
|
||||
// Helper functions ToDo: move them to common and replace all implementations with the common ones
|
||||
function countAssignments(assignments, startTime, endTime) {
|
||||
return assignments.filter(assignment =>
|
||||
assignment.shift.startTime >= startTime && assignment.shift.startTime <= endTime
|
||||
).length;
|
||||
}
|
||||
|
||||
function convertShiftDates(assignments) {
|
||||
assignments.forEach(assignment => {
|
||||
if (assignment.shift && assignment.shift.startTime) {
|
||||
assignment.shift.startTime = new Date(assignment.shift.startTime).toISOString();
|
||||
assignment.shift.endTime = new Date(assignment.shift.endTime).toISOString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
|
||||
const prisma = common.getPrismaClient();
|
||||
const dateStr = new Date().toISOString().split('T')[0];
|
||||
|
||||
let publishers = await filterPublishers('id,firstName,lastName,email,isActive,desiredShiftsPerMonth', "", new Date(), true, true, false);
|
||||
let publishers = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth', dateStr, false, true, true);
|
||||
|
||||
// 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`);
|
||||
@ -115,9 +166,65 @@ export const getServerSideProps = async (context) => {
|
||||
//remove publishers without availabilities
|
||||
publishers = publishers.filter(publisher => publisher.availabilities.length > 0);
|
||||
|
||||
let allPublishers = await prisma.publisher.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
email: true,
|
||||
phone: true,
|
||||
isActive: true,
|
||||
desiredShiftsPerMonth: true,
|
||||
assignments: {
|
||||
select: {
|
||||
id: true,
|
||||
shift: {
|
||||
select: {
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
let monthInfo,
|
||||
currentMonthStart, currentMonthEnd,
|
||||
previousMonthStart, previousMonthEnd;
|
||||
|
||||
monthInfo = common.getMonthDatesInfo(new Date());
|
||||
currentMonthStart = monthInfo.firstMonday;
|
||||
currentMonthEnd = monthInfo.lastSunday;
|
||||
let prevMnt = new Date();
|
||||
prevMnt.setMonth(prevMnt.getMonth() - 1);
|
||||
monthInfo = common.getMonthDatesInfo(prevMnt);
|
||||
previousMonthStart = monthInfo.firstMonday;
|
||||
previousMonthEnd = monthInfo.lastSunday;
|
||||
|
||||
|
||||
allPublishers.forEach(publisher => {
|
||||
// Use helper functions to calculate and assign assignment counts
|
||||
publisher.currentMonthAssignments = countAssignments(publisher.assignments, currentMonthStart, currentMonthEnd);
|
||||
publisher.previousMonthAssignments = countAssignments(publisher.assignments, previousMonthStart, previousMonthEnd);
|
||||
|
||||
// Convert date formats within the same iteration
|
||||
convertShiftDates(publisher.assignments);
|
||||
});
|
||||
|
||||
// Optionally, if you need a transformed list or additional properties, map the publishers
|
||||
allPublishers = allPublishers.map(publisher => ({
|
||||
...publisher,
|
||||
// Potentially add more computed properties or transformations here if needed
|
||||
}));
|
||||
|
||||
return {
|
||||
props: {
|
||||
publishers,
|
||||
allPublishers,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -329,7 +329,11 @@ exports.getWeekNumber = function (date) {
|
||||
return Math.ceil((date.getDate() - info.firstMonday.getDate() + 1) / 7);
|
||||
};
|
||||
|
||||
|
||||
exports.compareTimes = function (time1, time2) {
|
||||
const time1String = `${getHours(time1)}:${getMinutes(time1)}`;
|
||||
const time2String = `${getHours(time2)}:${getMinutes(time2)}`;
|
||||
return time1String.localeCompare(time2String);
|
||||
};
|
||||
|
||||
exports.getTimeRange = function (start, end) {
|
||||
start = new Date(start);
|
||||
|
@ -226,6 +226,273 @@ async function getAvailabilities(userId) {
|
||||
return serializableItems;
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true) {
|
||||
|
||||
filterDate = new Date(filterDate); // Convert to date object if not already
|
||||
|
||||
// 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 monthInfo.firstMonday (Assignments on specific days AND time)
|
||||
{
|
||||
dayOfMonth: { not: null },
|
||||
startTime: { gte: monthInfo.firstMonday },
|
||||
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: 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 path = require('path');
|
||||
|
||||
@ -255,5 +522,6 @@ module.exports = {
|
||||
findPublisher,
|
||||
findPublisherAvailability,
|
||||
runSqlFile,
|
||||
getAvailabilities
|
||||
getAvailabilities,
|
||||
filterPublishersNew
|
||||
};
|
@ -25,22 +25,50 @@ let mailtrapTestClient = null;
|
||||
// password: 'c7bc05f171c96c'
|
||||
// });
|
||||
|
||||
//test
|
||||
var transporter = nodemailer.createTransport({
|
||||
host: process.env.MAILERSEND_SERVER,
|
||||
port: process.env.MAILERSEND_PORT,
|
||||
//MAILTRAP
|
||||
var transporterMT = nodemailer.createTransport({
|
||||
host: process.env.MAILTRAP_HOST || "sandbox.smtp.mailtrap.io",
|
||||
port: 2525,
|
||||
auth: {
|
||||
user: process.env.MAILERSEND_USER,
|
||||
pass: process.env.MAILERSEND_PASS
|
||||
user: process.env.MAILTRAP_USER,
|
||||
pass: process.env.MAILTRAP_PASS
|
||||
}
|
||||
});
|
||||
// production
|
||||
// var transporter = nodemailer.createTransport({
|
||||
// host: "live.smtp.mailtrap.io",
|
||||
// port: 587,
|
||||
|
||||
//PROD GMAIL
|
||||
// const oauth2Client = new OAuth2(
|
||||
// process.env.CLIENT_ID,
|
||||
// process.env.CLIENT_SECRET,
|
||||
// "https://developers.google.com/oauthplayground"
|
||||
// );
|
||||
// var transporterGmail = nodemailer.createTransport({
|
||||
// service: "gmail",
|
||||
// auth: {
|
||||
// user: "api",
|
||||
// pass: "1cfe82e747b8dc3390ed08bb16e0f48d"
|
||||
// type: "OAuth2",
|
||||
// user: process.env.GMAIL_USER,
|
||||
// clientId: process.env.CLIENT_ID,
|
||||
// clientSecret: process.env.CLIENT_SECRET,
|
||||
// refreshToken: process.env.REFRESH_TOKEN,
|
||||
// accessToken: process.env.ACCESS_TOKEN
|
||||
// }
|
||||
// });
|
||||
//--------------
|
||||
var transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: process.env.EMAIL_GMAIL_USERNAME,
|
||||
pass: process.env.EMAIL_GMAIL_APP_PASS
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//PROD MAILERSEND
|
||||
// var transporter = nodemailer.createTransport({
|
||||
// host: process.env.MAILERSEND_SERVER,
|
||||
// port: process.env.MAILERSEND_PORT,
|
||||
// auth: {
|
||||
// user: process.env.MAILERSEND_USER,
|
||||
// pass: process.env.MAILERSEND_PASS
|
||||
// }
|
||||
// });
|
||||
|
||||
@ -52,6 +80,10 @@ var transporterBulk = nodemailer.createTransport({
|
||||
pass: "1cfe82e747b8dc3390ed08bb16e0f48d"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// ------------------ Email sending ------------------
|
||||
var lastResult = null;
|
||||
function setResult(result) {
|
||||
@ -87,8 +119,14 @@ function normalizeEmailAddresses(to) {
|
||||
|
||||
|
||||
exports.SendEmail = async function (to, subject, text, html, attachments = []) {
|
||||
let sender = '"Специално Свидетелстване София - тест" <demo@mwitnessing.com>';
|
||||
const emailAddresses = normalizeEmailAddresses(to)
|
||||
let sender = process.env.EMAIL_SENDER || '"Специално Свидетелстване София " <sofia@mwitnessing.com>';
|
||||
let emailAddresses = normalizeEmailAddresses(to)
|
||||
|
||||
const bypassEmailReccipients = process.env.EMAIL_BYPASS_TO || null;
|
||||
if (bypassEmailReccipients !== null && bypassEmailReccipients.length > 0) {
|
||||
emailAddresses = bypassEmailReccipients;
|
||||
console.log("Emails bypassed. All mails sent to: " + emailAddresses);
|
||||
}
|
||||
|
||||
const message = {
|
||||
from: sender,
|
||||
|
@ -31,7 +31,8 @@
|
||||
"pages/cart/locations/[id].tsx.typed",
|
||||
"components/location/LocationForm.js",
|
||||
"pages/cart/locations/[id].tsx.old",
|
||||
"components/publisher/ShiftsList.js"
|
||||
"components/publisher/ShiftsList.js",
|
||||
"src/helpers/data.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
@ -2,6 +2,19 @@
|
||||
|
||||
console.log('Service Worker Loaded...')
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
try {
|
||||
if (event.request.url.includes('/api/auth/callback/')) {
|
||||
// Use network only strategy for auth routes, or bypass SW completely
|
||||
event.respondWith(fetch(event.request));
|
||||
return;
|
||||
}
|
||||
// other caching strategies...
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('push', function (event) {
|
||||
console.log('Push message', event)
|
||||
if (!(self.Notification && self.Notification.permission === 'granted')) {
|
||||
|
Reference in New Issue
Block a user