Merge branch 'main' of https://git.d-popov.com/popov/mwhitnessing
This commit is contained in:
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@ -60,6 +60,17 @@
|
||||
"APP_ENV": "development.devserver"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "!Run npm DEV (UI REDESIGN)",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"command": "npm run start-env",
|
||||
"env": {
|
||||
// "NODE_ENV": "test",
|
||||
"APP_ENV": "development.devserver"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Run conda npm TEST",
|
||||
"request": "launch",
|
||||
|
@ -259,7 +259,7 @@ in schedule admin - if a publisher is always pair & family is not in the shift -
|
||||
[] fix transport UI
|
||||
[] revise import/export to word
|
||||
[] allow keyman/scheduler role
|
||||
[] allow blocking of inputs (different from publishing)
|
||||
[] allow blocking of inputs (different from publishing) TODO: fix to keep previous occurances when repeating evert week
|
||||
[] user - add createdAt field
|
||||
|
||||
[] FIX insecure logins
|
37
components/ErrorBoundary.tsx
Normal file
37
components/ErrorBoundary.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
this.logger = null;
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
this.logger = require('../src/logger');
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
// Log the error to an error reporting service
|
||||
console.error(error, info);
|
||||
if (this.logger) {
|
||||
this.logger.error(`${error}: ${info.componentStack}`);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Render any custom fallback UI
|
||||
return <h1>Нещо се обърка при изтриването. Моля, опитай отново и се свържете с нас ако проблема продължи. </h1>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
@ -35,6 +35,7 @@ function PwaManager({ subs }) {
|
||||
useEffect(() => {
|
||||
if (isSupported()) {
|
||||
setNotificationPermission(Notification.permission);
|
||||
getSubscriptionCount();
|
||||
}
|
||||
|
||||
// Handle Push Notification Subscription
|
||||
@ -77,6 +78,10 @@ function PwaManager({ subs }) {
|
||||
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
||||
window.removeEventListener('appinstalled', handleAppInstalled);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}, []);
|
||||
|
||||
|
||||
@ -127,6 +132,7 @@ function PwaManager({ subs }) {
|
||||
throw new Error("Failed to fetch VAPID public key from server.");
|
||||
}
|
||||
}
|
||||
|
||||
const sub = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: common.base64ToUint8Array(vapidPublicKey)
|
||||
@ -197,6 +203,19 @@ function PwaManager({ subs }) {
|
||||
}
|
||||
};
|
||||
|
||||
const getSubscriptionCount = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/notify?id=' + session.user.id, { method: 'GET' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch subscription data.');
|
||||
}
|
||||
const result = await response.json();
|
||||
setSubs(result.subs);
|
||||
} catch (error) {
|
||||
console.error('Error fetching subscription data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to request push notification permission
|
||||
const requestNotificationPermission = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -243,48 +262,56 @@ function PwaManager({ subs }) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
//sends test notification to the current subscription
|
||||
// body: JSON.stringify({ subscription })
|
||||
//sends test notification to all subscriptions of this user
|
||||
body: JSON.stringify({ id: session.user.id, title: "Тестово уведомление", message: "Това е тестово уведомление" })
|
||||
body: JSON.stringify(
|
||||
{
|
||||
id: session.user.id,
|
||||
title: "Тестово уведомление",
|
||||
message: "Това е тестово уведомление",
|
||||
actions: [{ action: 'test', title: 'Тест', icon: '✅' },
|
||||
{ action: 'close', title: 'Затвори', icon: '❌' }]
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
async function sendTestReminder(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
|
||||
event.preventDefault();
|
||||
if (!subscription) {
|
||||
console.error('Web push not subscribed');
|
||||
return;
|
||||
}
|
||||
// async function sendTestReminder(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
|
||||
// event.preventDefault();
|
||||
// if (!subscription) {
|
||||
// console.error('Web push not subscribed');
|
||||
// return;
|
||||
// }
|
||||
|
||||
await fetch('/api/notify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ broadcast: true, message: "Мили братя, искаме да ви напомним да ни изпратите вашите предпочитания за юни до 25-то число като използвате меню 'Възможности'. Ако имате проблем, моля пишете ни на 'specialnosvidetelstvanesofia@gmail.com'" })
|
||||
});
|
||||
}
|
||||
// await fetch('/api/notify', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// broadcast: true,
|
||||
// message: "Мили братя, искаме да ви напомним да ни изпратите вашите предпочитания за юни до 25-то число като използвате меню 'Възможности'. Ако имате проблем, моля пишете ни на 'specialnosvidetelstvanesofia@gmail.com'"
|
||||
// })
|
||||
// });
|
||||
// }
|
||||
|
||||
async function sendTestCoverMe(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
|
||||
event.preventDefault();
|
||||
if (!subscription) {
|
||||
console.error('Web push not subscribed');
|
||||
return;
|
||||
}
|
||||
// async function sendTestCoverMe(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
|
||||
// event.preventDefault();
|
||||
// if (!subscription) {
|
||||
// console.error('Web push not subscribed');
|
||||
// return;
|
||||
// }
|
||||
|
||||
await fetch('/api/notify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
broadcast: true, message: "Брат ТЕСТ търси заместник за 24-ти май от 10:00 ч. Можеш ли да го покриеш?",
|
||||
//use fontawesome icons for actions
|
||||
actions: [{ action: 'covermeaccepted', title: 'Да ', icon: '✅' }]
|
||||
})
|
||||
});
|
||||
}
|
||||
// await fetch('/api/notify', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// id: session.user.id,
|
||||
// message: "Брат ТЕСТ търси заместник за 24-ти май от 10:00 ч. Можеш ли да го покриеш?",
|
||||
// //use fontawesome icons for actions
|
||||
// actions: [{ action: 'covermeaccepted', title: 'Да ', icon: '✅' }]
|
||||
// })
|
||||
// });
|
||||
// }
|
||||
|
||||
async function deleteAllSubscriptions(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
|
||||
event.preventDefault();
|
||||
@ -358,22 +385,22 @@ function PwaManager({ subs }) {
|
||||
</div>
|
||||
{isAdmin &&
|
||||
<div>
|
||||
<button
|
||||
{/* <button
|
||||
onClick={sendTestReminder}
|
||||
disabled={!isSubscribed}
|
||||
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${!isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-yellow-500 hover:bg-yellow-600 text-white'
|
||||
}`}
|
||||
>
|
||||
Broadcast Reminder
|
||||
</button>
|
||||
<button
|
||||
</button> */}
|
||||
{/* <button
|
||||
onClick={sendTestCoverMe}
|
||||
disabled={!isSubscribed}
|
||||
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${!isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-yellow-500 hover:bg-yellow-600 text-white'
|
||||
}`}
|
||||
>
|
||||
Broadcast CoverMe
|
||||
</button>
|
||||
</button> */}
|
||||
</div>
|
||||
}
|
||||
{notificationPermission !== "granted" && (
|
||||
|
@ -10,7 +10,7 @@ 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'; //ToDo obsolete
|
||||
import { isBefore, addMinutes, isAfter, isEqual, set, getHours, getMinutes, getSeconds } from 'date-fns'; //ToDo obsolete?
|
||||
import { stat } from 'fs';
|
||||
|
||||
const { DateTime, FixedOffsetZone } = require('luxon');
|
||||
|
@ -17,11 +17,22 @@ export default function AvailabilityList({ publisher, showNew }) {
|
||||
const [showAv, setShowAv] = useState(showNew || false);
|
||||
const [selectedItem, setSelectedItem] = useState(null);
|
||||
const [items, setItems] = useState(publisher.availabilities); // Convert items prop to state
|
||||
const [blockedAvailabilityDate, setBlockedAvailabilityDate] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('items set to:', items?.map(item => item.id));
|
||||
}, [items])
|
||||
|
||||
useEffect(() => {
|
||||
axiosInstance.get(`/api/?action=settings&key=AvailabilityBlockDate`)
|
||||
.then(({ data }) => {
|
||||
setBlockedAvailabilityDate(new Date(data.value));
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error getting blocked date:", error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleAv = () => setShowAv(!showAv);
|
||||
const editAvailability = (item) => {
|
||||
setSelectedItem(item);
|
||||
@ -72,9 +83,16 @@ export default function AvailabilityList({ publisher, showNew }) {
|
||||
{/* <button className="bg-blue-200 hover:bg-blue-300 text-blue-600 py-1 px-2 rounded inline-flex items-center" onClick={() => editAvailability(item)}>
|
||||
<PencilSquareIcon className="h-6 w-6" />
|
||||
</button> */}
|
||||
<button className="bg-red-200 hover:bg-red-300 text-red-600 py-1 px-2 rounded ml-2 inline-flex items-center" onClick={() => deleteAvailability(item.id)}>
|
||||
<TrashIcon className="h-6 w-6" />
|
||||
</button>
|
||||
{(blockedAvailabilityDate && new Date(item.startTime) < blockedAvailabilityDate) ? (
|
||||
<button className="disabled bg-gray-200 hover:bg-gray-300 text-gray-600 py-1 px-2 rounded inline-flex items-center">
|
||||
<TrashIcon className="h-6 w-6" />
|
||||
</button>
|
||||
) : (
|
||||
<button className="bg-red-200 hover:bg-red-300 text-red-600 py-1 px-2 rounded ml-2 inline-flex items-center" onClick={() => deleteAvailability(item.id)}>
|
||||
<TrashIcon className="h-6 w-6" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
@ -511,7 +511,8 @@ const AvCalendar = ({ publisherId, events, selectedDate, cartEvents, lastPublish
|
||||
<> <div {...handlers} className="flex flex-col"
|
||||
>
|
||||
{/* достъпности на {publisherId} */}
|
||||
<ToastContainer position="top-center" style={{ zIndex: 9999 }} />
|
||||
{/* having multiple ToastContainers causes double rendering of toasts and all kind of problems */}
|
||||
{/* <ToastContainer position="top-center" style={{ zIndex: 9999 }} /> */}
|
||||
</div>
|
||||
<Calendar
|
||||
localizer={localizer}
|
||||
|
@ -10,6 +10,7 @@ import Body from 'next/document'
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { set } from "date-fns"
|
||||
import ErrorBoundary from "./ErrorBoundary";
|
||||
|
||||
export default function Layout({ children }) {
|
||||
const router = useRouter();
|
||||
@ -61,7 +62,9 @@ export default function Layout({ children }) {
|
||||
<Sidebar isSidebarOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
|
||||
<main className={`flex-1 transition-all duration-300 ${marginLeftClass}`}>
|
||||
<div className="">
|
||||
{children}
|
||||
<ErrorBoundary>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<div id="modal-root"></div> {/* Modal container */}
|
||||
</main>
|
||||
|
@ -46,7 +46,8 @@ const ProtectedRoute = ({ children, allowedRoles, deniedMessage, bypass = false,
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold mb-4 text-blue-500">{session?.user?.email},</h1>
|
||||
<p className="mb-6">{`Нямате достъп до тази страница. Ако мислите, че това е грешка, моля, свържете се с администраторите`}</p>
|
||||
<p className="mb-6">{`Нямате достъп до тази страница.`}</p>
|
||||
<p className="mb-6">{`Ако мислите, че това е грешка, моля, свържете се с администраторите`}</p>
|
||||
</div>
|
||||
</div>
|
||||
</>);
|
||||
|
@ -92,15 +92,15 @@ export default function PublisherForm({ item, me }) {
|
||||
|
||||
let { familyHeadId, userId, congregationId, ...rest } = publisher;
|
||||
// Set the familyHead relation based on the selected head
|
||||
const familyHeadRelation = familyHeadId ? { connect: { id: familyHeadId } } : { disconnect: true };
|
||||
const userRel = userId ? { connect: { id: userId } } : { disconnect: true };
|
||||
const congregationRel = congregationId ? { connect: { id: parseInt(congregationId) } } : { disconnect: true };
|
||||
const familyHeadRelation = familyHeadId ? { connect: { id: familyHeadId } } : null;
|
||||
const userRel = userId ? { connect: { id: userId } } : null;
|
||||
const congregationRel = congregationId ? { connect: { id: parseInt(congregationId) } } : null;
|
||||
// Return the new state without familyHeadId and with the correct familyHead relation
|
||||
rest = {
|
||||
...rest,
|
||||
familyHead: familyHeadRelation,
|
||||
user: userRel,
|
||||
congregation: congregationRel
|
||||
...(familyHeadRelation ? { familyHead: familyHeadRelation } : {}),
|
||||
...(userRel ? { user: userRel } : {}),
|
||||
...(congregationRel ? { congregation: congregationRel } : {}),
|
||||
};
|
||||
|
||||
try {
|
||||
@ -112,7 +112,7 @@ export default function PublisherForm({ item, me }) {
|
||||
position: "bottom-center",
|
||||
});
|
||||
} else {
|
||||
await axiosInstance.post(urls.apiUrl, publisher);
|
||||
await axiosInstance.post(urls.apiUrl, rest);
|
||||
toast.success("Task Saved", {
|
||||
position: "bottom-center",
|
||||
});
|
||||
|
@ -81,6 +81,7 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
|
||||
};
|
||||
fetchData();
|
||||
}, [item.date, existingItem]);
|
||||
|
||||
const handleChange = ({ target }) => {
|
||||
setItem({ ...item, [target.name]: target.value });
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
|
||||
import { UserRole } from "@prisma/client";
|
||||
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
// import { faStar } from '@fortawesome/free-solid-svg-icons'; // Star icon
|
||||
// import { faClipboardList } from '@fortawesome/free-solid-svg-icons'; // Clipboard icon
|
||||
import { FaStar } from 'react-icons/fa'; // Import FontAwesome icons
|
||||
|
||||
|
||||
const sidemenu = [
|
||||
@ -103,6 +107,19 @@ const sidemenu = [
|
||||
text: "Календар",
|
||||
url: "/cart/calendar",
|
||||
roles: [UserRole.ADMIN, UserRole.POWERUSER],
|
||||
},
|
||||
{
|
||||
id: "surveys",
|
||||
// text: "Анкети",
|
||||
// add new icon before text
|
||||
// text: "Анкети",
|
||||
text: (
|
||||
<span>
|
||||
<FaStar className="inline-block mr-2" />
|
||||
Анкети
|
||||
</span>
|
||||
),
|
||||
url: "/cart/surveys",
|
||||
}, {
|
||||
id: "cart-reports",
|
||||
text: "Отчети",
|
||||
|
354
components/survey/SurveyForm.tsx
Normal file
354
components/survey/SurveyForm.tsx
Normal file
@ -0,0 +1,354 @@
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
import { use, useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import { useSession } from "next-auth/react"
|
||||
import { MessageType, Message, Survey } from "@prisma/client";
|
||||
// import { content } from 'googleapis/build/src/apis/content';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
// import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
|
||||
// import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
||||
import dayjs from 'dayjs';
|
||||
import { set } from 'lodash';
|
||||
|
||||
const common = require('src/helpers/common');
|
||||
|
||||
|
||||
// ------------------ ------------------
|
||||
// This component is used to create and edit
|
||||
/* location model:
|
||||
|
||||
model Survey {
|
||||
id Int @id @default(autoincrement())
|
||||
content String
|
||||
answers Json?
|
||||
messages Message[]
|
||||
publicFrom DateTime?
|
||||
publicUntil DateTime?
|
||||
}
|
||||
|
||||
model Message {
|
||||
id Int @id @default(autoincrement())
|
||||
publisher Publisher @relation(fields: [publisherId], references: [id])
|
||||
publisherId String
|
||||
date DateTime
|
||||
content String
|
||||
isRead Boolean @default(false)
|
||||
isPublic Boolean @default(false)
|
||||
type MessageType @default(Email)
|
||||
publicUntil DateTime?
|
||||
shownDate DateTime?
|
||||
answer String?
|
||||
answerDate DateTime?
|
||||
|
||||
Survey Survey? @relation(fields: [surveyId], references: [id])
|
||||
surveyId Int?
|
||||
}
|
||||
*/
|
||||
|
||||
interface SurveyFormProps {
|
||||
existingItem: Survey | null;
|
||||
}
|
||||
|
||||
const SurveyForm: React.FC<SurveyFormProps> = ({ existingItem }) => {
|
||||
|
||||
const router = useRouter();
|
||||
const [editMode, setEditMode] = useState(existingItem ? true : false);
|
||||
const [pubs, setPubs] = useState([]);
|
||||
|
||||
|
||||
const [item, setItem] = useState(existingItem || {
|
||||
...existingItem,
|
||||
content: existingItem?.content || "Нова анкета",
|
||||
answers: existingItem?.answers.split(",") || [],
|
||||
publicFrom: existingItem?.publicFrom ? dayjs(existingItem.publicFrom).toISOString() : new Date().toISOString(),
|
||||
publicUntil: existingItem?.publicUntil ? dayjs(existingItem.publicUntil).toISOString() : new Date().toISOString(),
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let transformedItem = { ...existingItem };
|
||||
transformedItem.answersCount = existingItem?.answers.split(",") || [];
|
||||
setEditMode(existingItem ? true : false);
|
||||
setItem(transformedItem);
|
||||
}, [existingItem]);
|
||||
|
||||
useEffect(async () => {
|
||||
const pubs = await axiosInstance.get("/api/data/publishers?select=id,firstName,lastName,email");
|
||||
setPubs(pubs.data);
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
const handleChange = ({ target }) => {
|
||||
setItem({ ...item, [target.name]: target.value });
|
||||
};
|
||||
|
||||
const handleDateChange = (fieldName, newDate) => {
|
||||
setItem((prevItem) => ({
|
||||
...prevItem,
|
||||
[fieldName]: newDate
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
delete item.answersCount;
|
||||
try {
|
||||
|
||||
if (editMode) {
|
||||
delete item.messages;
|
||||
const { data } = await axiosInstance.put(`/api/data/surveys/${existingItem.id}`, item);
|
||||
toast.success("Анкетата е обновена успешно");
|
||||
}
|
||||
else {
|
||||
//get all publisherIds and create a message for each
|
||||
const messages = pubs.data.map(pub => {
|
||||
return {
|
||||
publisherId: pub.id,
|
||||
content: JSON.stringify({ message: item.content, options: item.answers }),
|
||||
date: new Date(),
|
||||
isPublic: false,
|
||||
type: MessageType.InApp,
|
||||
publicUntil: item.publicUntil,
|
||||
}
|
||||
});
|
||||
item.messages = { create: messages };
|
||||
const { data } = await axiosInstance.post("/api/data/surveys", item);
|
||||
toast.success("Анкетата е създадена успешно");
|
||||
}
|
||||
router.push("/cart/surveys");
|
||||
} catch (error) {
|
||||
toast.error("Възникна грешка при създаването на анкетата");
|
||||
console.error("Error creating survey:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!editMode) return;
|
||||
try {
|
||||
await axiosInstance.delete(`/api/data/surveys/${existingItem.id}`);
|
||||
|
||||
toast.success("Записът изтрит", {
|
||||
position: "bottom-center",
|
||||
});
|
||||
router.push("/cart/surveys");
|
||||
|
||||
} catch (error) {
|
||||
//alert("Нещо се обърка при изтриването. Моля, опитайте отново или се свържете с нас");
|
||||
console.log(JSON.stringify(error));
|
||||
toast.error(error.response?.data?.message || "Нещо се обърка при изтриването. Моля, опитай отново и се свържете с нас ако проблема продължи.");
|
||||
}
|
||||
};
|
||||
|
||||
function handleDeleteAnswers(e: MouseEvent<HTMLButtonElement, MouseEvent>): void {
|
||||
e.preventDefault();
|
||||
if (!editMode) return;
|
||||
|
||||
Promise.all(existingItem.messages.map(message =>
|
||||
axiosInstance.put(`/api/data/messages/${message.id}`, { answer: null })
|
||||
))
|
||||
.then(() => {
|
||||
toast.success("Отговорите изтрити", {
|
||||
position: "bottom-center",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(JSON.stringify(error));
|
||||
toast.error(error.response?.data?.message || "Нещо се обърка при изтриването. Моля, опитай отново и се свържете с нас ако проблема продължи.");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const getNamesByIds = (ids) => {
|
||||
return ids
|
||||
.map((id) => {
|
||||
const pub = pubs.find((p) => p.id === id);
|
||||
return pub ? `${pub.firstName} ${pub.lastName}` : null;
|
||||
})
|
||||
.filter((name) => name !== null)
|
||||
.join(", ");
|
||||
};
|
||||
|
||||
const getIdsForAnswer = (answer) => {
|
||||
return item.messages
|
||||
.filter((message) => message.answer === answer)
|
||||
.map((message) => message.publisherId);
|
||||
};
|
||||
|
||||
const getIdsForAnswered = () => {
|
||||
return item.messages
|
||||
.filter((message) => message.answer)
|
||||
.map((message) => message.publisherId);
|
||||
};
|
||||
|
||||
const getIdsForUnanswered = () => {
|
||||
return item.messages
|
||||
.filter((message) => !message.answer)
|
||||
.map((message) => message.publisherId);
|
||||
};
|
||||
|
||||
// const copyToClipboard = (text) => {
|
||||
// navigator.clipboard.writeText(text).then(
|
||||
// () => toast.success('Copied to clipboard!'),
|
||||
// (err) => toast.error('Failed to copy text: ', err)
|
||||
// );
|
||||
// };
|
||||
|
||||
const copyToClipboard = (text) => {
|
||||
navigator.clipboard.writeText(text).then(
|
||||
() => alert('Имената са копирани: ' + text),
|
||||
(err) => alert('Не успяхме да копираме имената: ', err)
|
||||
);
|
||||
};
|
||||
const sendIndividualNotification = async (id, message) => {
|
||||
const response = await fetch('/api/notify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id,
|
||||
title: 'Нямаме отговор',
|
||||
message: `${message}`,
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log(`Notification sent successfully to ${name}`);
|
||||
} else {
|
||||
console.error(`Failed to send notification to ${name}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendNotificationsToAllUnanswered = async (message) => {
|
||||
getIdsForUnanswered().forEach((id, index) => sendIndividualNotification(id, message));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md mx-auto" >
|
||||
< form className="bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4" onSubmit={handleSubmit} >
|
||||
|
||||
<h1 className="text-2xl font-bold mb-8">Анкета {existingItem?.id}</h1>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="date">
|
||||
Видима от
|
||||
</label>
|
||||
<DatePicker className="textbox form-input px-4 py-2 rounded" name="publicFrom" onChange={(newDate) => handleDateChange('publicFrom', newDate)} value={dayjs(item?.publicFrom)} />
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="date">
|
||||
Видима до
|
||||
</label>
|
||||
<DatePicker className="textbox form-input px-4 py-2 rounded" name="publicUntil" onChange={(newDate) => handleDateChange('publicUntil', newDate)} value={dayjs(item?.publicUntil)} />
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="content">
|
||||
Съдържание
|
||||
</label>
|
||||
<textarea className={`textbox form-input px-4 py-2 rounded ${editMode ? 'opacity-50 cursor-not-allowed' : ''}`} id="content" name="content" onChange={handleChange} value={item?.content} autoComplete="off" disabled={editMode} />
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="answers">
|
||||
Отговори
|
||||
</label>
|
||||
<input className={`textbox form-input px-4 py-2 rounded ${editMode ? 'opacity-50 cursor-not-allowed' : ''}`} id="answers" name="answers" type="text" onChange={handleChange} value={item?.answers} autoComplete="off" disabled={editMode}
|
||||
/>
|
||||
</div>
|
||||
{/* show count of each answer and the total answered/unanswered messages */}
|
||||
{item?.answersCount?.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold mb-2">Отговори:</h3>
|
||||
{item.answersCount.map((answer, index) => {
|
||||
const currentCount = item.messages ? item.messages.filter((message) => message.answer === answer).length : 0;
|
||||
const totalCount = item.messages ? item.messages.length : 0;
|
||||
const percentage = totalCount > 0 ? (currentCount / totalCount) * 100 : 0;
|
||||
const ids = getIdsForAnswer(answer);
|
||||
const names = getNamesByIds(ids);
|
||||
|
||||
return (
|
||||
<div key={index} className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor={`answer-${index}`}>
|
||||
{answer}
|
||||
</label>
|
||||
<div className="relative h-6 w-full bg-gray-200 rounded" title={names}
|
||||
onClick={() => copyToClipboard(names)} style={{ cursor: 'copy' }}>
|
||||
<div className="absolute h-full bg-blue-600 rounded" style={{ width: `${percentage}%` }}></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-white font-bold">
|
||||
{currentCount} ({percentage.toFixed(1)}%)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">Общо отговорили</label>
|
||||
<div className="relative h-6 w-full bg-gray-200 rounded" title={getNamesByIds(getIdsForAnswered())}
|
||||
onClick={() => copyToClipboard(getNamesByIds(getIdsForAnswered()))} style={{ cursor: 'copy' }}
|
||||
>
|
||||
<div className="absolute h-full bg-green-600 rounded" style={{ width: `${item.messages ? (item.messages.filter((message) => message.answer).length / item.messages.length) * 100 : 0}%` }}></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-white font-bold">
|
||||
{item.messages ? item.messages.filter((message) => message.answer).length : 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">Общо неотговорили</label>
|
||||
<div className="relative h-6 w-full bg-gray-200 rounded" title={getNamesByIds(getIdsForUnanswered())}
|
||||
style={{ cursor: 'copy' }}
|
||||
onClick={() => copyToClipboard(getNamesByIds(getIdsForUnanswered()))}>
|
||||
<div className="absolute h-full bg-red-600 rounded" style={{ width: `${item.messages ? (item.messages.filter((message) => !message.answer).length / item.messages.length) * 100 : 0}%` }}></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-white font-bold">
|
||||
{item.messages ? item.messages.filter((message) => !message.answer).length : 0}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => { handleSendNotificationsToAllUnanswered(item?.content) }}>
|
||||
Подсети ВСИЧКИ неотховорили с нотификация
|
||||
</button>
|
||||
<div className="mt-2">
|
||||
{getIdsForUnanswered().map((id) => {
|
||||
const pub = pubs.find((p) => p.id === id);
|
||||
const name = pub ? `${pub.firstName} ${pub.lastName}` : '???';
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
// className="block mt-1 px-1 py-0.5 bg-orange-500 text-white rounded"
|
||||
className="mt-1 px-2 py-1 bg-green-500 text-white rounded text-xs"
|
||||
onClick={() => sendIndividualNotification(id, item?.content)}>
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
{editMode && (<>
|
||||
<button className="button btn-outline bg-red-500 hover:bg-red-700 focus:outline-none focus:shadow-outline" type="button" onClick={handleDelete}>
|
||||
Изтрий
|
||||
</button>
|
||||
|
||||
<button className="button btn-outline bg-red-500 hover:bg-red-700 focus:outline-none focus:shadow-outline" type="button" onClick={handleDeleteAnswers}>
|
||||
Изтрий отговорите
|
||||
</button>
|
||||
|
||||
</>)}
|
||||
<button className="btn bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition duration-300" type="submit">
|
||||
Запази
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form >
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
export default SurveyForm;
|
73
package-lock.json
generated
73
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pwwa",
|
||||
"version": "1.2.2",
|
||||
"name": "smws",
|
||||
"version": "1.2.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pwwa",
|
||||
"version": "1.2.2",
|
||||
"name": "smws",
|
||||
"version": "1.2.4",
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^1.4.0",
|
||||
"@emotion/react": "^11.11.3",
|
||||
@ -16,7 +16,7 @@
|
||||
"@mui/material": "^5.15.10",
|
||||
"@mui/x-date-pickers": "^6.19.4",
|
||||
"@premieroctet/next-crud": "^3.0.0",
|
||||
"@prisma/client": "^5.13.0",
|
||||
"@prisma/client": "^5.15.0",
|
||||
"@react-pdf/renderer": "^3.3.8",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/multer": "^1.4.11",
|
||||
@ -49,6 +49,7 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"levenshtein-edit-distance": "^3.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.4",
|
||||
"mailtrap": "^3.3.0",
|
||||
"module-alias": "^2.2.3",
|
||||
@ -99,7 +100,7 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"depcheck": "^1.4.7",
|
||||
"prisma": "^5.13.0"
|
||||
"prisma": "^5.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@ -3914,9 +3915,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.13.0.tgz",
|
||||
"integrity": "sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.15.0.tgz",
|
||||
"integrity": "sha512-wPTeTjbd2Q0abOeffN7zCDCbkp9C9cF+e9HPiI64lmpehyq2TepgXE+sY7FXr7Rhbb21prLMnhXX27/E11V09w==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
@ -3931,39 +3932,39 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.13.0.tgz",
|
||||
"integrity": "sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.15.0.tgz",
|
||||
"integrity": "sha512-QpEAOjieLPc/4sMny/WrWqtpIAmBYsgqwWlWwIctqZO0AbhQ9QcT6x2Ut3ojbDo/pFRCCA1Z1+xm2MUy7fAkZA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.13.0.tgz",
|
||||
"integrity": "sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.15.0.tgz",
|
||||
"integrity": "sha512-hXL5Sn9hh/ZpRKWiyPA5GbvF3laqBHKt6Vo70hYqqOhh5e0ZXDzHcdmxNvOefEFeqxra2DMz2hNbFoPvqrVe1w==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.13.0",
|
||||
"@prisma/engines-version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b",
|
||||
"@prisma/fetch-engine": "5.13.0",
|
||||
"@prisma/get-platform": "5.13.0"
|
||||
"@prisma/debug": "5.15.0",
|
||||
"@prisma/engines-version": "5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022",
|
||||
"@prisma/fetch-engine": "5.15.0",
|
||||
"@prisma/get-platform": "5.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b.tgz",
|
||||
"integrity": "sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A==",
|
||||
"version": "5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022.tgz",
|
||||
"integrity": "sha512-3BEgZ41Qb4oWHz9kZNofToRvNeS4LZYaT9pienR1gWkjhky6t6K1NyeWNBkqSj2llgraUNbgMOCQPY4f7Qp5wA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.13.0.tgz",
|
||||
"integrity": "sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.15.0.tgz",
|
||||
"integrity": "sha512-z6AY5yyXxc20Klj7wwnfGP0iIUkVKzybqapT02zLYR/nf9ynaeN8bq73WRmi1TkLYn+DJ5Qy+JGu7hBf1pE78A==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.13.0",
|
||||
"@prisma/engines-version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b",
|
||||
"@prisma/get-platform": "5.13.0"
|
||||
"@prisma/debug": "5.15.0",
|
||||
"@prisma/engines-version": "5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022",
|
||||
"@prisma/get-platform": "5.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/generator-helper": {
|
||||
@ -3980,12 +3981,12 @@
|
||||
"integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA=="
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.13.0.tgz",
|
||||
"integrity": "sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.15.0.tgz",
|
||||
"integrity": "sha512-1GULDkW4+/VQb73vihxCBSc4Chc2x88MA+O40tcZFjmBzG4/fF44PaXFxUqKSFltxU9L9GIMLhh0Gfkk/pUbtg==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.13.0"
|
||||
"@prisma/debug": "5.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/internals": {
|
||||
@ -15118,13 +15119,13 @@
|
||||
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.13.0.tgz",
|
||||
"integrity": "sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.15.0.tgz",
|
||||
"integrity": "sha512-JA81ACQSCi3a7NUOgonOIkdx8PAVkO+HbUOxmd00Yb8DgIIEpr2V9+Qe/j6MLxIgWtE/OtVQ54rVjfYRbZsCfw==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines": "5.13.0"
|
||||
"@prisma/engines": "5.15.0"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "smws",
|
||||
"version": "1.2.4",
|
||||
"version": "1.3.0",
|
||||
"private": true,
|
||||
"description": "SMWS | ССОМ | Специално Свидетелстване София",
|
||||
"repository": "http://git.d-popov.com/popov/next-cart-app.git",
|
||||
@ -34,7 +34,7 @@
|
||||
"@mui/material": "^5.15.10",
|
||||
"@mui/x-date-pickers": "^6.19.4",
|
||||
"@premieroctet/next-crud": "^3.0.0",
|
||||
"@prisma/client": "^5.13.0",
|
||||
"@prisma/client": "^5.15.0",
|
||||
"@react-pdf/renderer": "^3.3.8",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/multer": "^1.4.11",
|
||||
@ -67,6 +67,7 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"levenshtein-edit-distance": "^3.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.4",
|
||||
"mailtrap": "^3.3.0",
|
||||
"module-alias": "^2.2.3",
|
||||
@ -117,6 +118,6 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"depcheck": "^1.4.7",
|
||||
"prisma": "^5.13.0"
|
||||
"prisma": "^5.15.0"
|
||||
}
|
||||
}
|
@ -86,10 +86,14 @@ function SmwsApp({ Component, pageProps, session, locale, messages }) {
|
||||
// }, [locale]);
|
||||
|
||||
useEffect(() => {
|
||||
const use = async () => {
|
||||
(await import('tw-elements')).default;
|
||||
};
|
||||
use();
|
||||
try {
|
||||
const use = async () => {
|
||||
(await import('tw-elements')).default;
|
||||
};
|
||||
use();
|
||||
} catch (e) {
|
||||
console.error('Error loading tw-elements:', e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
|
@ -64,6 +64,50 @@ export default async function handler(req, res) {
|
||||
res.status(200).json({ message: "SQL script executed successfully" });
|
||||
break;
|
||||
|
||||
case "settings":
|
||||
try {
|
||||
const key = req.query.key;
|
||||
switch (req.method) {
|
||||
case "LIST":
|
||||
let s1 = await prisma.settings.findMany();
|
||||
res.status(200).json(s1.map(setting => setting.key));
|
||||
break;
|
||||
case "GET":
|
||||
const s2 = await prisma.settings.findUnique({
|
||||
where: {
|
||||
key: key
|
||||
}
|
||||
});
|
||||
res.status(200).json(s2);
|
||||
|
||||
break;
|
||||
case "PUT": //create or update
|
||||
const value = req.query.value;
|
||||
const results = await prisma.settings.upsert({
|
||||
where: {
|
||||
key: key
|
||||
},
|
||||
update: {
|
||||
value: value
|
||||
},
|
||||
create: {
|
||||
key: key,
|
||||
value: value
|
||||
}
|
||||
});
|
||||
res.status(200).json(results);
|
||||
break;
|
||||
default:
|
||||
res.status(405).json({ message: "Method Not Allowed" });
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting settings: " + error);
|
||||
res.status(500).json({ error });
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case "deleteAllPublishers":
|
||||
//get filter and delete all publishers containing that in first name or last name
|
||||
await prisma.publisher.deleteMany({
|
||||
@ -467,6 +511,7 @@ export async function getMonthlyStatistics(selectFields, filterDate) {
|
||||
|
||||
export async function filterPublishersNew_Available(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true, includeOldAvailabilities = false) {
|
||||
return dataHelper.filterPublishersNew(selectFields, filterDate, isExactTime, isForTheMonth, false, isWithStats, includeOldAvailabilities);
|
||||
// async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, noEndDateFilter = false, isWithStats = true, includeOldAvailabilities = false, id = null, filterAvailabilitiesByDate = true)
|
||||
}
|
||||
|
||||
// availabilites filter:
|
||||
|
@ -33,19 +33,21 @@ const Notification = async (req, res) => {
|
||||
select: { pushSubscription: true }
|
||||
});
|
||||
subs = Array.isArray(publisher.pushSubscription) ? publisher.pushSubscription.length : (publisher.pushSubscription ? 1 : 0);
|
||||
res.send({ subs })
|
||||
res.end()
|
||||
return
|
||||
} else {
|
||||
// send the public key in the response headers
|
||||
//res.setHeader('Content-Type', 'text/plain')
|
||||
res.send({ pk: process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY, subs })
|
||||
res.end()
|
||||
}
|
||||
// send the public key in the response headers
|
||||
//res.setHeader('Content-Type', 'text/plain')
|
||||
res.send({ pk: process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY, subs })
|
||||
res.end()
|
||||
}
|
||||
if (req.method == 'PUT') {
|
||||
// store the subscription object in the database
|
||||
// publisher.pushSubscription = subscription
|
||||
const prisma = common.getPrismaClient();
|
||||
const { subscription, id } = req.body
|
||||
const { subscription, id, name } = req.body
|
||||
const publisher = await prisma.publisher.findUnique({
|
||||
where: { id },
|
||||
select: { pushSubscription: true }
|
||||
@ -105,16 +107,21 @@ const Notification = async (req, res) => {
|
||||
|
||||
|
||||
if (req.method == 'POST') {//title = "ССС", message = "Ще получите уведомление по този начин.")
|
||||
const { subscription, id, broadcast, title = 'ССОМ', message = 'Ще получавате уведомления така.', actions } = req.body
|
||||
const { subscription, id, ids, broadcast, title = 'ССОМ', message = 'Ще получавате уведомления така.', actions } = req.body
|
||||
if (broadcast) {
|
||||
await broadcastPush(title, message, actions)
|
||||
res.statusCode = 200
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
else if (id) {
|
||||
} else if (ids && ids.length) {
|
||||
console.log('Sending push notifications to publishers ', ids);
|
||||
await Promise.all(ids.map(_id => sendPush(_id, title, message, actions)));
|
||||
res.statusCode = 200;
|
||||
res.end();
|
||||
return;
|
||||
} else if (id) {
|
||||
console.log('Sending push notification to publisher ', id)
|
||||
await sendPush(id, title, message.actions)
|
||||
await sendPush(id, title, message, actions)
|
||||
res.statusCode = 200
|
||||
res.end()
|
||||
return
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -62,6 +62,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
||||
const [allShifts, setAllShifts] = useState(initialShifts);
|
||||
const [isPublished, setIsPublished] = useState(() => initialShifts.some(shift => shift.isPublished));
|
||||
const [value, onChange] = useState<Date>(new Date());
|
||||
const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth());
|
||||
const [shifts, setShifts] = React.useState([]);
|
||||
const [error, setError] = React.useState(null);
|
||||
const [availablePubs, setAvailablePubs] = React.useState([]);
|
||||
@ -89,7 +90,6 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
||||
handleCalDateChange(value); // Call handleCalDateChange whenever isCheckboxChecked changes
|
||||
}, [filterShowWithoutAssignments]); // Dependency array
|
||||
|
||||
const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth());
|
||||
useEffect(() => {
|
||||
const newMonth = value.getMonth();
|
||||
if (newMonth !== selectedMonth) {
|
||||
@ -607,6 +607,29 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
model Settings {
|
||||
id Int @id @default(autoincrement())
|
||||
key String
|
||||
value String
|
||||
description String?
|
||||
}
|
||||
*/
|
||||
async function setAvailabilityBlockDate(AvailabilityBlockDate: Date): Promise<void> {
|
||||
// set AvailabilityBlockDate to the selected date
|
||||
let monthInfo = common.getMonthInfo(value);
|
||||
await axiosInstance.put(`/api/?action=settings&key=AvailabilityBlockDate&value=${common.getISODateOnly(monthInfo.lastSunday)}`)
|
||||
.then((response) => {
|
||||
console.log("AvailabilityBlockDate set to:", response.data);
|
||||
// setShifts([...shifts, response.data]);
|
||||
handleCalDateChange(value);
|
||||
}
|
||||
).catch((error) => {
|
||||
console.error("Error setting AvailabilityBlockDate:", error);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
@ -700,6 +723,9 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
||||
<button className="block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center" onClick={() => generateShifts("genDay", true, true, null, 1)}>
|
||||
{isLoading('genDay') ? (<i className="fas fa-sync-alt fa-spin mr-2"></i>) : (<i className="fas fa-cogs mr-2"></i>)}
|
||||
Генерирай смени 2 </button>
|
||||
<button className="block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center" onClick={() => generateShifts("genDay", true, true, null, 2)}>
|
||||
{isLoading('genDay') ? (<i className="fas fa-sync-alt fa-spin mr-2"></i>) : (<i className="fas fa-cogs mr-2"></i>)}
|
||||
Генерирай смени 3 </button>
|
||||
<button className="block px-4 py-2 text-sm text-red-500 hover:bg-gray-100"
|
||||
onClick={() => openConfirmModal(
|
||||
'Сигурни ли сте че искате да изтриете ВСИЧКИ смени и назначения за месеца?',
|
||||
@ -715,8 +741,9 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
||||
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={generateXLS}><i className="fas fa-file-excel mr-2"></i> Генерирай XLSX</button>
|
||||
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={fetchShifts}>
|
||||
{isLoading('fetchShifts') ? (<i className="fas fa-sync-alt fa-spin mr-2"></i>) : (<i className="fas fa-sync-alt mr-2"></i>)} презареди</button>
|
||||
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={generateMonthlyStatistics}><i className="fas fa-chart-bar mr-2"></i> Генерирай статистика</button>
|
||||
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={copyOldAvailabilities}><i className="fas fa-copy mr-2"></i> Прехвърли предпочитанията</button>
|
||||
{/* <button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={generateMonthlyStatistics}><i className="fas fa-chart-bar mr-2"></i> Генерирай статистика</button>
|
||||
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={copyOldAvailabilities}><i className="fas fa-copy mr-2"></i> Прехвърли предпочитанията</button> */}
|
||||
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={setAvailabilityBlockDate}><i className="fas fa-copy mr-2"></i> Блокирай предпочитанията до края на {selectedMonth + 1} м.</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -795,7 +822,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
||||
{pub.canTransport && (<LocalShippingIcon className="mx-2 text-gray-500" />)}
|
||||
</span>
|
||||
<div className="flex space-x-1 overflow-hidden">
|
||||
<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`} >
|
||||
<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>
|
||||
<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>
|
||||
@ -809,7 +836,27 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ broadcast: true, message: "Тестово съобщение", title: "Това е тестово съобщение от https://sofia.mwitnessing.com" })
|
||||
body: JSON.stringify({
|
||||
id: pub.id,
|
||||
message: "Тестово съобщение",
|
||||
title: "Това е тестово съобщение от https://sofia.mwitnessing.com",
|
||||
actions: [
|
||||
{ action: 'OK', title: 'OK', icon: '✅' },
|
||||
{ action: 'close', title: 'Затвори', icon: '❌' }
|
||||
]
|
||||
// actions: [
|
||||
// {
|
||||
// title: 'Open URL',
|
||||
// action: 'open_url',
|
||||
// icon: '/images/open-url.png'
|
||||
// },
|
||||
// {
|
||||
// title: 'Dismiss',
|
||||
// action: 'dismiss',
|
||||
// icon: '/images/dismiss.png'
|
||||
// }
|
||||
// ]
|
||||
})
|
||||
})
|
||||
}}
|
||||
>+</button>
|
||||
|
@ -253,7 +253,7 @@ function ContactsPage({ allPublishers }) {
|
||||
<>
|
||||
<td className="border-b p-4">
|
||||
{pub.availabilities.length > 0 ? (
|
||||
<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`}>
|
||||
<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} | {pub.currentMonthAvailabilityHoursCount}
|
||||
</span>
|
||||
) : <span title="Няма възможности" className="badge py-1 px-2 rounded-md text-xs bg-gray-300 text-gray-500">0</span>}
|
||||
@ -324,10 +324,22 @@ export default ContactsPage;
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
const allPublishers = await data.getAllPublishersWithStatisticsMonth(new Date());
|
||||
//merge first and last name
|
||||
// Merge first and last name and serialize Date objects
|
||||
allPublishers.forEach(publisher => {
|
||||
publisher.name = `${publisher.firstName} ${publisher.lastName}`;
|
||||
|
||||
if (publisher.currentMonthAvailability) {
|
||||
publisher.currentMonthAvailability = publisher.currentMonthAvailability.map(availability => {
|
||||
return {
|
||||
...availability,
|
||||
startTime: availability.startTime instanceof Date ? availability.startTime.toISOString() : availability.startTime,
|
||||
endTime: availability.endTime instanceof Date ? availability.endTime.toISOString() : availability.endTime,
|
||||
dateOfEntry: availability.dateOfEntry instanceof Date ? availability.dateOfEntry.toISOString() : availability.dateOfEntry,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
allPublishers
|
||||
|
86
pages/cart/surveys/index.tsx
Normal file
86
pages/cart/surveys/index.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Layout from "../../../components/layout";
|
||||
import { GetServerSideProps } from 'next';
|
||||
import { Location, UserRole } from "@prisma/client";
|
||||
import axiosServer from '../../../src/axiosServer';
|
||||
const common = require('../../../src/helpers/common');
|
||||
// import * as common from '../../../src/helpers/common';
|
||||
import SurveyForm from '../../../components/survey/SurveyForm';
|
||||
import _ from 'lodash';
|
||||
import ProtectedRoute from 'components/protectedRoute';
|
||||
|
||||
const SurveyPage = ({ serverSurveys }) => {
|
||||
const [selectedSurvey, setSelectedSurvey] = useState(null);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]}>
|
||||
<div className="max-w-4xl mx-auto my-8 p-4 bg-white shadow-md rounded">
|
||||
<h1 className="text-2xl font-bold mb-4">Анкети</h1>
|
||||
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="w-1/2 pr-4">
|
||||
<h2 className="text-xl font-semibold mb-4">Списък</h2>
|
||||
<ul className="space-y-4">
|
||||
{serverSurveys.map((survey) => (
|
||||
<li key={survey.id} className="p-4 border rounded bg-gray-50 shadow-sm">
|
||||
<p className="font-medium">{survey.id}: {survey.content}</p>
|
||||
{/* <p className="text-gray-700">{survey.publicFrom} - {survey.publicUntil}</p> */}
|
||||
<p className="mt-2"> [{survey.answers}] </p>
|
||||
<div className="mt-2">
|
||||
{Object.entries(_.groupBy(survey.messages, message => message.answer || "Без отговор")).map(([key, items]) => (
|
||||
<div key={key} className="text-sm text-gray-700">
|
||||
{key}: {items.length}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
className="btn mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded"
|
||||
onClick={() => setSelectedSurvey(survey)}
|
||||
>
|
||||
Зареди детайли
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<button
|
||||
className="btn mt-4 bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
|
||||
onClick={() => setSelectedSurvey(null)}
|
||||
>
|
||||
Нова анкета
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-1/2 pl-4">
|
||||
<h2 className="text-xl font-semibold mb-4">Детайли</h2>
|
||||
<SurveyForm existingItem={selectedSurvey} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
</div>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default SurveyPage;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const prisma = common.getPrismaClient();
|
||||
let serverSurveys = await prisma.survey.findMany({
|
||||
where: {
|
||||
},
|
||||
include: {
|
||||
messages: true,
|
||||
},
|
||||
});
|
||||
serverSurveys = common.convertDatesToISOStrings(serverSurveys);
|
||||
|
||||
context.res.setHeader("Cache-Control", "s-maxage=1, stale-while-revalidate");
|
||||
return {
|
||||
props: {
|
||||
serverSurveys: serverSurveys
|
||||
},
|
||||
};
|
||||
};
|
||||
|
294
pages/dash.tsx
294
pages/dash.tsx
@ -1,7 +1,8 @@
|
||||
import { useSession } from "next-auth/react"
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, use } from 'react';
|
||||
import Layout from "../components/layout"
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AvCalendar from '../components/calendar/avcalendar';
|
||||
import { getSession } from "next-auth/react";
|
||||
@ -25,9 +26,9 @@ interface IProps {
|
||||
initialUserId: string;
|
||||
cartEvents: any;
|
||||
lastPublishedDate: Date;
|
||||
messages: any;
|
||||
}
|
||||
export default function DashboardPage({ initialItems, initialUserId, cartEvents, lastPublishedDate }: IProps) {
|
||||
|
||||
export default function DashboardPage({ initialItems, initialUserId, cartEvents, lastPublishedDate, messages }: IProps) {
|
||||
const router = useRouter();
|
||||
const { newLogin } = router.query;
|
||||
const { data: session } = useSession();
|
||||
@ -51,17 +52,145 @@ export default function DashboardPage({ initialItems, initialUserId, cartEvents,
|
||||
}, [session]);
|
||||
|
||||
|
||||
|
||||
// MESSAGES
|
||||
//const [notificationsVisible, setNotificationsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
//if (newLogin === 'true')
|
||||
{
|
||||
// alert("Мили братя, искаме само да ви напомним да ни изпратите вашите предпочитания за юни до 25-то число като използвате меню 'Възможности'. Ако имате проблем, моля пишете ни на 'specialnosvidetelstvanesofia@gmail.com'");
|
||||
const currentPath = router.pathname;
|
||||
router.replace(currentPath, undefined, { shallow: true }); // Removes the query without affecting the history
|
||||
}
|
||||
}, []);// show the message every time we load the page
|
||||
// useEffect(() => {
|
||||
// //if (newLogin === 'true')
|
||||
// {
|
||||
// // alert("Мили братя, искаме само да ви напомним да ни изпратите вашите предпочитания за юни до 25-то число като използвате меню 'Възможности'. Ако имате проблем, моля пишете ни на 'specialnosvidetelstvanesofia@gmail.com'");
|
||||
// const currentPath = router.pathname;
|
||||
// router.replace(currentPath, undefined, { shallow: true }); // Removes the query without affecting the history
|
||||
// }
|
||||
// }, []);// show the message every time we load the page
|
||||
|
||||
|
||||
// const [processedMessages, setProcessedMessages] = useState(new Set());
|
||||
// useEffect(() => {
|
||||
// if (messages && messages.length > 0) {
|
||||
// const unprocessedMessages = messages.filter(message => !processedMessages.has(message.id));
|
||||
// if (unprocessedMessages.length > 0) {
|
||||
// showMessageToasts(unprocessedMessages);
|
||||
// setProcessedMessages(new Set([...processedMessages, ...unprocessedMessages.map(msg => msg.id)]));
|
||||
// }
|
||||
// }
|
||||
// }, [messages, processedMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (messages && messages.length > 0) {
|
||||
showMessageToasts(messages);
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
const showMessageToasts = (messages) => {
|
||||
const handleOptionClick = async (messageId, option, toastId) => {
|
||||
try {
|
||||
await axiosInstance.put(`/api/data/messages/${messageId}`, { answer: option });
|
||||
handleClose(toastId);
|
||||
} catch (error) {
|
||||
console.error("Error updating message:", error);
|
||||
toast.error("Error updating message. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = (toastId) => {
|
||||
toast.dismiss(toastId);
|
||||
};
|
||||
|
||||
messages.forEach((message, messageIndex) => {
|
||||
const toastId = `message-${message.id}-${messageIndex}`;
|
||||
const content = (
|
||||
<div>
|
||||
<div>{message.content.message}</div>
|
||||
<div>
|
||||
{message.content.options?.map((option, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(message.id, option, toastId)}
|
||||
className="btn bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded m-1"
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
toast(content, {
|
||||
toastId,
|
||||
autoClose: false,
|
||||
closeButton: true,
|
||||
onClose: () => handleClose(toastId),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// const showMessageToastNewModal = (messages, handleMessageOptionAnswer) => {
|
||||
// let currentMessageIndex = 0;
|
||||
|
||||
// const showModal = () => {
|
||||
// if (currentMessageIndex >= messages.length) {
|
||||
// return; // All messages have been shown
|
||||
// }
|
||||
|
||||
// const message = messages[currentMessageIndex];
|
||||
// const content = (
|
||||
// <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
// <div className="bg-white rounded shadow-lg p-4 max-w-lg w-full">
|
||||
// <div className="text-right">
|
||||
// <button
|
||||
// className="text-gray-500 hover:text-gray-700"
|
||||
// onClick={handleClose}
|
||||
// >
|
||||
// ×
|
||||
// </button>
|
||||
// </div>
|
||||
// <div className="mb-4">{message.content.message}</div>
|
||||
// <div>
|
||||
// {message.content.options?.map((option, index) => (
|
||||
// <button
|
||||
// key={index}
|
||||
// onClick={() => handleOptionClick(message.id, option)}
|
||||
// className="btn bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded m-1"
|
||||
// >
|
||||
// {option}
|
||||
// </button>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
|
||||
// toast(content, {
|
||||
// autoClose: false, // Keep the toast open until manually closed
|
||||
// closeButton: false,
|
||||
// onClose: handleClose,
|
||||
// //className: 'custom-toast', // Optional custom class for additional styling
|
||||
// });
|
||||
// };
|
||||
|
||||
// const handleOptionClick = async (messageId, option) => {
|
||||
// try {
|
||||
// await axiosInstance.put(`/api/data/messages/${messageId}`, { answer: option });
|
||||
// toast.dismiss();
|
||||
// currentMessageIndex++;
|
||||
// showModal();
|
||||
// } catch (error) {
|
||||
// console.error("Error updating message:", error);
|
||||
// toast.error("Error updating message. Please try again.");
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleClose = () => {
|
||||
// toast.dismiss();
|
||||
// };
|
||||
|
||||
// showModal();
|
||||
// };
|
||||
|
||||
// FOR ADMINS ONLY
|
||||
const handleUserSelection = async (publisher) => {
|
||||
if (!publisher || publisher.id === undefined) return;
|
||||
console.log("selecting publisher", publisher.id);
|
||||
@ -107,107 +236,6 @@ export default function DashboardPage({ initialItems, initialUserId, cartEvents,
|
||||
}
|
||||
|
||||
|
||||
// async function getAvailabilities(userId) {
|
||||
// const prismaClient = common.getPrismaClient();
|
||||
// const items = await prismaClient.availability.findMany({
|
||||
// where: {
|
||||
// publisherId: userId,
|
||||
// },
|
||||
// select: {
|
||||
// id: true,
|
||||
// name: true,
|
||||
// isActive: true,
|
||||
// isFromPreviousAssignment: true,
|
||||
// isFromPreviousMonth: true,
|
||||
// dayofweek: true,
|
||||
// dayOfMonth: true,
|
||||
// startTime: true,
|
||||
// endTime: true,
|
||||
// repeatWeekly: true,
|
||||
// endDate: true,
|
||||
// publisher: {
|
||||
// select: {
|
||||
// firstName: true,
|
||||
// lastName: true,
|
||||
// id: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// // Convert Date objects to ISO strings
|
||||
// const serializableItems = items.map(item => ({
|
||||
// ...item,
|
||||
// startTime: item.startTime.toISOString(),
|
||||
// endTime: item.endTime.toISOString(),
|
||||
// name: common.getTimeFormatted(item.startTime) + "-" + common.getTimeFormatted(item.endTime),
|
||||
// //endDate can be null
|
||||
// endDate: item.endDate ? item.endDate.toISOString() : null,
|
||||
// type: 'availability',
|
||||
// // Convert other Date fields similarly if they exist
|
||||
// }));
|
||||
|
||||
// /*model Assignment {
|
||||
// id Int @id @default(autoincrement())
|
||||
// shift Shift @relation(fields: [shiftId], references: [id], onDelete: Cascade)
|
||||
// shiftId Int
|
||||
// publisher Publisher @relation(fields: [publisherId], references: [id], onDelete: Cascade)
|
||||
// publisherId String
|
||||
// isActive Boolean @default(true)
|
||||
// isConfirmed Boolean @default(false)
|
||||
// isWithTransport Boolean @default(false)
|
||||
// Report Report[]
|
||||
// }*/
|
||||
// //get assignments for this user
|
||||
// const assignments = await prismaClient.assignment.findMany({
|
||||
// where: {
|
||||
// publisherId: userId,
|
||||
// },
|
||||
// select: {
|
||||
// id: true,
|
||||
// isBySystem: true,
|
||||
// isConfirmed: true,
|
||||
// isWithTransport: true,
|
||||
// shift: {
|
||||
// select: {
|
||||
// id: true,
|
||||
// name: true,
|
||||
// startTime: true,
|
||||
// endTime: true,
|
||||
// //select all assigned publishers names as name - comma separated
|
||||
// assignments: {
|
||||
// select: {
|
||||
// publisher: {
|
||||
// select: {
|
||||
// firstName: true,
|
||||
// lastName: true,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// const serializableAssignments = assignments.map(item => ({
|
||||
// ...item,
|
||||
// startTime: item.shift.startTime.toISOString(),
|
||||
// endTime: item.shift.endTime.toISOString(),
|
||||
// // name: item.shift.publishers.map(p => p.firstName + " " + p.lastName).join(", "),
|
||||
// //name: item.shift.assignments.map(a => a.publisher.firstName[0] + " " + a.publisher.lastName).join(", "),
|
||||
// name: common.getTimeFormatted(new Date(item.shift.startTime)) + "-" + common.getTimeFormatted(new Date(item.shift.endTime)),
|
||||
// type: 'assignment',
|
||||
// //delete shift object
|
||||
// shift: null,
|
||||
// publisher: { id: userId }
|
||||
// }));
|
||||
|
||||
// serializableItems.push(...serializableAssignments);
|
||||
|
||||
// return serializableItems;
|
||||
|
||||
// }
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
const auth = await serverSideAuth({
|
||||
req: context.req,
|
||||
@ -270,7 +298,7 @@ export const getServerSideProps = async (context) => {
|
||||
}
|
||||
});
|
||||
cartEvents = common.convertDatesToISOStrings(cartEvents);
|
||||
const lastPublishedDate = (await prisma.shift.findFirst({
|
||||
let lastPublishedDate = (await prisma.shift.findFirst({
|
||||
where: {
|
||||
isPublished: true,
|
||||
},
|
||||
@ -280,7 +308,43 @@ export const getServerSideProps = async (context) => {
|
||||
orderBy: {
|
||||
endTime: 'desc'
|
||||
}
|
||||
})).endTime;
|
||||
}))?.endTime || new Date();
|
||||
|
||||
let blockedDate = await prisma.settings.findUnique({
|
||||
where: {
|
||||
key: "AvailabilityBlockDate"
|
||||
}
|
||||
});
|
||||
|
||||
if (blockedDate) {
|
||||
blockedDate.value = new Date(blockedDate.value);
|
||||
lastPublishedDate = lastPublishedDate > blockedDate.value ? lastPublishedDate : blockedDate.value;
|
||||
}
|
||||
|
||||
let messages = await prisma.message.findMany({
|
||||
where: {
|
||||
publisherId: userId,
|
||||
isPublic: false,
|
||||
answer: null,
|
||||
},
|
||||
include: {
|
||||
Survey: true,
|
||||
}
|
||||
});
|
||||
|
||||
messages = messages.filter((message) => {
|
||||
return (!message.Survey.publicFrom || message.Survey.publicFrom >= common.getStartOfDay(new Date()))
|
||||
&& (!message.Survey.publicUntil || message.Survey.publicUntil <= common.getEndOfDay(new Date()))
|
||||
});
|
||||
messages = common.convertDatesToISOStrings(messages);
|
||||
messages = messages.map(message => {
|
||||
if (message.content) {
|
||||
message.content = JSON.parse(message.content);
|
||||
message.content.options = message.content.options?.split(",");
|
||||
|
||||
}
|
||||
return message;
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
@ -288,7 +352,7 @@ export const getServerSideProps = async (context) => {
|
||||
userId: sessionServer?.user.id,
|
||||
cartEvents: cartEvents,
|
||||
lastPublishedDate: lastPublishedDate.toISOString(),
|
||||
// messages: (await import(`../content/i18n/${context.locale}.json`)).default
|
||||
messages: messages
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `Publisher` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`firstName` VARCHAR(191) NOT NULL,
|
||||
`lastName` VARCHAR(191) NOT NULL,
|
||||
`email` VARCHAR(191) NOT NULL,
|
||||
`phone` VARCHAR(191) NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`age` INTEGER NULL,
|
||||
|
||||
UNIQUE INDEX `Publisher_email_key`(`email`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Availability` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`publisherId` INTEGER NOT NULL,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`dayofweek` ENUM('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') NOT NULL,
|
||||
`startTime` DATETIME(3) NOT NULL,
|
||||
`endTime` DATETIME(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `CartEvent` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`startTime` DATETIME(3) NOT NULL,
|
||||
`endTime` DATETIME(3) NOT NULL,
|
||||
`dayofweek` ENUM('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Shift` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`cartEventId` INTEGER NOT NULL,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`startTime` DATETIME(3) NOT NULL,
|
||||
`endTime` DATETIME(3) NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`requiresTransport` BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Location` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`address` VARCHAR(191) NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`dayofweek` ENUM('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `_PublisherToShift` (
|
||||
`A` INTEGER NOT NULL,
|
||||
`B` INTEGER NOT NULL,
|
||||
|
||||
UNIQUE INDEX `_PublisherToShift_AB_unique`(`A`, `B`),
|
||||
INDEX `_PublisherToShift_B_index`(`B`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Availability` ADD CONSTRAINT `Availability_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Shift` ADD CONSTRAINT `Shift_cartEventId_fkey` FOREIGN KEY (`cartEventId`) REFERENCES `CartEvent`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_PublisherToShift` ADD CONSTRAINT `_PublisherToShift_A_fkey` FOREIGN KEY (`A`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_PublisherToShift` ADD CONSTRAINT `_PublisherToShift_B_fkey` FOREIGN KEY (`B`) REFERENCES `Shift`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `locationId` to the `CartEvent` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `shiftDuration` to the `CartEvent` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `isActive` BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `CartEvent` ADD COLUMN `isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN `locationId` INTEGER NOT NULL,
|
||||
ADD COLUMN `shiftDuration` INTEGER NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `CartEvent` ADD CONSTRAINT `CartEvent_locationId_fkey` FOREIGN KEY (`locationId`) REFERENCES `Location`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -1,12 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `dayofweek` on the `Location` table. All the data in the column will be lost.
|
||||
- Added the required column `date` to the `Shift` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Location` DROP COLUMN `dayofweek`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Shift` ADD COLUMN `date` DATETIME(3) NOT NULL;
|
@ -1,8 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `date` on the `Shift` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Shift` DROP COLUMN `date`;
|
@ -1,5 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `CartEvent` ADD COLUMN `eventType` ENUM('PW_Cart', 'KH_Cleaning') NOT NULL DEFAULT 'PW_Cart';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Shift` ADD COLUMN `isTentaive` BOOLEAN NOT NULL DEFAULT false;
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `_PublisherToShift` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `_PublisherToShift` DROP FOREIGN KEY `_PublisherToShift_A_fkey`;
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `_PublisherToShift` DROP FOREIGN KEY `_PublisherToShift_B_fkey`;
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE `_PublisherToShift`;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Assignment` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`shiftId` INTEGER NOT NULL,
|
||||
`publisherId` INTEGER NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_shiftId_fkey` FOREIGN KEY (`shiftId`) REFERENCES `Shift`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -1,11 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `isTentaive` on the `Shift` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment` ADD COLUMN `isTentaive` BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Shift` DROP COLUMN `isTentaive`;
|
@ -1,9 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `isTentaive` on the `Assignment` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment` DROP COLUMN `isTentaive`,
|
||||
ADD COLUMN `isTentative` BOOLEAN NOT NULL DEFAULT false;
|
@ -1,23 +0,0 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Assignment` DROP FOREIGN KEY `Assignment_publisherId_fkey`;
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Assignment` DROP FOREIGN KEY `Assignment_shiftId_fkey`;
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Availability` DROP FOREIGN KEY `Availability_publisherId_fkey`;
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Shift` DROP FOREIGN KEY `Shift_cartEventId_fkey`;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Availability` ADD CONSTRAINT `Availability_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Shift` ADD CONSTRAINT `Shift_cartEventId_fkey` FOREIGN KEY (`cartEventId`) REFERENCES `CartEvent`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_shiftId_fkey` FOREIGN KEY (`shiftId`) REFERENCES `Shift`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `Publisher` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Assignment` DROP FOREIGN KEY `Assignment_publisherId_fkey`;
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Availability` DROP FOREIGN KEY `Availability_publisherId_fkey`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment` MODIFY `publisherId` VARCHAR(191) NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` MODIFY `publisherId` VARCHAR(191) NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` DROP PRIMARY KEY,
|
||||
ADD COLUMN `emailVerified` DATETIME(3) NULL,
|
||||
MODIFY `id` VARCHAR(191) NOT NULL,
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Account` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`userId` VARCHAR(191) NOT NULL,
|
||||
`type` VARCHAR(191) NOT NULL,
|
||||
`provider` VARCHAR(191) NOT NULL,
|
||||
`providerAccountId` VARCHAR(191) NOT NULL,
|
||||
`refresh_token` VARCHAR(191) NULL,
|
||||
`access_token` VARCHAR(191) NULL,
|
||||
`expires_at` INTEGER NULL,
|
||||
`token_type` VARCHAR(191) NULL,
|
||||
`scope` VARCHAR(191) NULL,
|
||||
`id_token` VARCHAR(191) NULL,
|
||||
`session_state` VARCHAR(191) NULL,
|
||||
|
||||
UNIQUE INDEX `Account_provider_providerAccountId_key`(`provider`, `providerAccountId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Session` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`sessionToken` VARCHAR(191) NOT NULL,
|
||||
`userId` VARCHAR(191) NOT NULL,
|
||||
`expires` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `Session_sessionToken_key`(`sessionToken`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `VerificationToken` (
|
||||
`identifier` VARCHAR(191) NOT NULL,
|
||||
`token` VARCHAR(191) NOT NULL,
|
||||
`expires` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `VerificationToken_token_key`(`token`),
|
||||
UNIQUE INDEX `VerificationToken_identifier_token_key`(`identifier`, `token`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Availability` ADD CONSTRAINT `Availability_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Account` ADD CONSTRAINT `Account_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Session` ADD CONSTRAINT `Session_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -1,8 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `dayOfMonth` INTEGER NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `CartEvent` ADD COLUMN `numberOfPublishers` INTEGER NOT NULL DEFAULT 3;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `role` ENUM('ADMIN', 'USER') NOT NULL DEFAULT 'USER';
|
@ -1,3 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `desiredShiftsPerMonth` INTEGER NOT NULL DEFAULT 4,
|
||||
MODIFY `role` ENUM('ADMIN', 'USER', 'EXTERNAL') NOT NULL DEFAULT 'USER';
|
@ -1,7 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `isMale` BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN `isNameForeign` BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN `parentId` VARCHAR(191) NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Publisher` ADD CONSTRAINT `Publisher_parentId_fkey` FOREIGN KEY (`parentId`) REFERENCES `Publisher`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `isImported` BOOLEAN NOT NULL DEFAULT false;
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Shift` ADD COLUMN `notes` VARCHAR(191) NULL;
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `weekOfMonth` INTEGER NULL;
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `parentId` on the `Publisher` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Publisher` DROP FOREIGN KEY `Publisher_parentId_fkey`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment` ADD COLUMN `isWithTransport` BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` DROP COLUMN `parentId`,
|
||||
ADD COLUMN `familyHeadId` VARCHAR(191) NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Publisher` ADD CONSTRAINT `Publisher_familyHeadId_fkey` FOREIGN KEY (`familyHeadId`) REFERENCES `Publisher`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,8 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `isWithTransport` BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN `type` ENUM('Weekly', 'Monthly', 'OneTime', 'PreviousAssignment') NOT NULL DEFAULT 'Weekly';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `comments` VARCHAR(191) NULL,
|
||||
ADD COLUMN `town` VARCHAR(191) NULL,
|
||||
ADD COLUMN `type` ENUM('Publisher', 'Bethelite', 'RegularPioneer', 'SpecialPioneer', 'Missionary', 'CircuitOverseer') NOT NULL DEFAULT 'Publisher';
|
@ -1,3 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `isFromPreviousAssignment` BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN `isFromPreviousMonth` BOOLEAN NOT NULL DEFAULT false;
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `emailVerified` on the `Publisher` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[userId]` on the table `Publisher` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Account` DROP FOREIGN KEY `Account_userId_fkey`;
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Session` DROP FOREIGN KEY `Session_userId_fkey`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` DROP COLUMN `emailVerified`,
|
||||
ADD COLUMN `userId` VARCHAR(191) NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `User` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`name` VARCHAR(191) NULL,
|
||||
`email` VARCHAR(191) NULL,
|
||||
`emailVerified` DATETIME(3) NULL,
|
||||
`image` VARCHAR(191) NULL,
|
||||
`publisherId` VARCHAR(191) NULL,
|
||||
|
||||
UNIQUE INDEX `User_email_key`(`email`),
|
||||
UNIQUE INDEX `User_publisherId_key`(`publisherId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX `Publisher_userId_key` ON `Publisher`(`userId`);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Publisher` ADD CONSTRAINT `Publisher_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Account` ADD CONSTRAINT `Account_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Session` ADD CONSTRAINT `Session_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -1,2 +0,0 @@
|
||||
-- This is an empty migration.
|
||||
-- CREATE INDEX `Publisher_email_key` ON `Publisher` (email);
|
@ -1,23 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` MODIFY `role` ENUM('ADMIN', 'POWERUSER', 'USER', 'EXTERNAL') NOT NULL DEFAULT 'USER';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Report` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME(3) NOT NULL,
|
||||
`publisherId` VARCHAR(191) NOT NULL,
|
||||
`assignmentId` INTEGER NOT NULL,
|
||||
`placementCount` INTEGER NOT NULL,
|
||||
`videoCount` INTEGER NOT NULL,
|
||||
`returnVisitInfoCount` INTEGER NOT NULL,
|
||||
`conversationCount` INTEGER NOT NULL,
|
||||
`experienceInfo` VARCHAR(191) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Report` ADD CONSTRAINT `Report_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Report` ADD CONSTRAINT `Report_assignmentId_fkey` FOREIGN KEY (`assignmentId`) REFERENCES `Assignment`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `locationId` to the `Report` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Report` DROP FOREIGN KEY `Report_assignmentId_fkey`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Report` ADD COLUMN `locationId` INTEGER NOT NULL,
|
||||
MODIFY `assignmentId` INTEGER NULL,
|
||||
MODIFY `placementCount` INTEGER NULL,
|
||||
MODIFY `videoCount` INTEGER NULL,
|
||||
MODIFY `returnVisitInfoCount` INTEGER NULL,
|
||||
MODIFY `conversationCount` INTEGER NULL,
|
||||
MODIFY `experienceInfo` VARCHAR(191) NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Report` ADD CONSTRAINT `Report_locationId_fkey` FOREIGN KEY (`locationId`) REFERENCES `Location`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Report` ADD CONSTRAINT `Report_assignmentId_fkey` FOREIGN KEY (`assignmentId`) REFERENCES `Assignment`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Report` MODIFY `experienceInfo` LONGTEXT NULL;
|
@ -1,8 +0,0 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Report` DROP FOREIGN KEY `Report_locationId_fkey`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Report` MODIFY `locationId` INTEGER NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Report` ADD CONSTRAINT `Report_locationId_fkey` FOREIGN KEY (`locationId`) REFERENCES `Location`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,3 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `endDate` DATETIME(3) NULL,
|
||||
ADD COLUMN `repeatWeekly` BOOLEAN NULL;
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `isTentative` on the `assignment` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `assignmentId` on the `report` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[publicGuid]` on the table `Assignment` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[reportId]` on the table `Shift` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE `Report` DROP FOREIGN KEY `Report_assignmentId_fkey`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment` DROP COLUMN `isTentative`,
|
||||
ADD COLUMN `isConfirmed` BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN `isMailSent` BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN `publicGuid` VARCHAR(191) NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Report` DROP COLUMN `assignmentId`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Shift` ADD COLUMN `reportId` INTEGER NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX `Assignment_publicGuid_key` ON `Assignment`(`publicGuid`);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX `Shift_reportId_key` ON `Shift`(`reportId`);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Shift` ADD CONSTRAINT `Shift_reportId_fkey` FOREIGN KEY (`reportId`) REFERENCES `Report`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,9 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Location` ADD COLUMN `backupLocationId` INTEGER NULL,
|
||||
ADD COLUMN `content` TEXT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `isTrained` BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Location` ADD CONSTRAINT `Location_backupLocationId_fkey` FOREIGN KEY (`backupLocationId`) REFERENCES `Location`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,12 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `isActive` on the `assignment` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment` DROP COLUMN `isActive`,
|
||||
ADD COLUMN `isTentative` BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Location` MODIFY `content` LONGTEXT NULL;
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `repeatFrequency` INTEGER NULL;
|
@ -1,4 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Location` ADD COLUMN `picture1` VARCHAR(191) NULL,
|
||||
ADD COLUMN `picture2` VARCHAR(191) NULL,
|
||||
ADD COLUMN `picture3` VARCHAR(191) NULL;
|
@ -1,15 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `isWithTransport` on the `Availability` table. All the data in the column will be lost.
|
||||
- The values [SpecialPioneer,Missionary,CircuitOverseer] on the enum `Publisher_type` will be removed. If these variants are still used in the database, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` DROP COLUMN `isWithTransport`,
|
||||
ADD COLUMN `isWithTransportIn` BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN `isWithTransportOut` BOOLEAN NOT NULL DEFAULT false,
|
||||
MODIFY `type` ENUM('Weekly', 'Monthly', 'OneTime', 'PreviousAssignment', 'ReplacementOnly') NOT NULL DEFAULT 'Weekly';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` MODIFY `type` ENUM('Publisher', 'Bethelite', 'RegularPioneer', 'SpecialPioneer_Missionary') NOT NULL DEFAULT 'Publisher';
|
@ -1,5 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `alwaysAsFamily` BOOLEAN NULL DEFAULT false;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Shift` ADD COLUMN `isPublished` BOOLEAN NOT NULL DEFAULT false;
|
@ -1,18 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `dateOfEntry` DATETIME(3) NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Message` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`publisherId` VARCHAR(191) NOT NULL,
|
||||
`date` DATETIME(3) NOT NULL,
|
||||
`content` VARCHAR(191) NOT NULL,
|
||||
`isRead` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isPublic` BOOLEAN NOT NULL DEFAULT false,
|
||||
`type` ENUM('Email', 'SMS', 'Push', 'InApp') NOT NULL DEFAULT 'Email',
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Message` ADD CONSTRAINT `Message_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `isTentative` on the `Assignment` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment`
|
||||
ADD COLUMN `isBySystem` BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Depending on your DBMS, you might need to execute one statement at a time.
|
||||
-- Especially, the UPDATE statement should be run separately.
|
||||
UPDATE `Assignment` SET `isBySystem` = isTentative;
|
||||
|
||||
-- Drop the isTentative column
|
||||
ALTER TABLE `Assignment` DROP COLUMN `isTentative`;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Report`
|
||||
ADD COLUMN `type` ENUM('ServiceReport', 'Experience', 'Feedback_Problem', 'Feedback_Suggestion', 'Feedback') NOT NULL DEFAULT 'ServiceReport';
|
@ -1,5 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Availability` ADD COLUMN `parentAvailabilityId` INTEGER NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Availability` ADD CONSTRAINT `Availability_parentAvailabilityId_fkey` FOREIGN KEY (`parentAvailabilityId`) REFERENCES `Availability`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,3 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `isSubscribedToCoverMe` BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN `isSubscribedToReminders` BOOLEAN NOT NULL DEFAULT false;
|
@ -1,20 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `EventLog` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME(3) NOT NULL,
|
||||
`publisherId` VARCHAR(191) NULL,
|
||||
`shiftId` INTEGER NULL,
|
||||
`content` VARCHAR(5000) NOT NULL,
|
||||
`type` ENUM('AssignmentReplacementRequested', 'AssignmentReplacementAccepted', 'SentEmail') NOT NULL,
|
||||
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `EventLog`
|
||||
ADD CONSTRAINT `EventLog_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `EventLog`
|
||||
ADD CONSTRAINT `EventLog_shiftId_fkey` FOREIGN KEY (`shiftId`) REFERENCES `Shift` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `lastLogin` DATETIME(3) NULL;
|
@ -1,3 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `User`
|
||||
ADD COLUMN `passwordHashLocalAccount` VARCHAR(191) NULL;
|
@ -1,5 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `EventLog`
|
||||
MODIFY `type` ENUM(
|
||||
'AssignmentReplacementRequested', 'AssignmentReplacementAccepted', 'SentEmail', 'PasswordResetRequested', 'PasswordResetEmailConfirmed', 'PasswordResetCompleted'
|
||||
) NOT NULL;
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher` ADD COLUMN `pushSubscription` JSON NULL;
|
@ -1,33 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Assignment`
|
||||
ADD COLUMN `originalPublisherId` VARCHAR(191) NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Message` ADD COLUMN `publicUntil` DATETIME(3) NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Publisher`
|
||||
ADD COLUMN `congregationId` INTEGER NULL,
|
||||
ADD COLUMN `locale` VARCHAR(191) NULL DEFAULT 'bg';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Report` ADD COLUMN `comments` VARCHAR(191) NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Congregation` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`address` VARCHAR(191) NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Publisher`
|
||||
ADD CONSTRAINT `Publisher_congregationId_fkey` FOREIGN KEY (`congregationId`) REFERENCES `Congregation` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment`
|
||||
ADD CONSTRAINT `Assignment_originalPublisherId_fkey` FOREIGN KEY (`originalPublisherId`) REFERENCES `Publisher` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -1,19 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `EventLog`
|
||||
MODIFY `type` ENUM(
|
||||
'AssignmentReplacementManual', 'AssignmentReplacementRequested', 'AssignmentReplacementAccepted', 'SentEmail', 'PasswordResetRequested', 'PasswordResetEmailConfirmed', 'PasswordResetCompleted'
|
||||
) NOT NULL;
|
||||
|
||||
INSERT INTO
|
||||
`Congregation`
|
||||
VALUES (1, 'Перник', '', 1),
|
||||
(2, 'София Люлин', '', 1),
|
||||
(3, 'София Юг', '', 1),
|
||||
(4, 'София Надежда', '', 1),
|
||||
(5, 'София Руски', '', 1),
|
||||
(6, 'София Факултета', '', 1),
|
||||
(7, 'София Изток', '', 1),
|
||||
(8, 'София Младост', '', 1),
|
||||
(9, 'София Английски', '', 1),
|
||||
(10, 'Ботевград', '', 1),
|
||||
(11, 'София Дружба', '', 1);
|
@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `User` ADD COLUMN `emailVerifyToken` VARCHAR(191) NULL;
|
@ -0,0 +1,313 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `Publisher` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`firstName` VARCHAR(191) NOT NULL,
|
||||
`lastName` VARCHAR(191) NOT NULL,
|
||||
`email` VARCHAR(191) NOT NULL,
|
||||
`phone` VARCHAR(191) NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`isImported` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isTrained` BOOLEAN NOT NULL DEFAULT false,
|
||||
`age` INTEGER NULL,
|
||||
`userId` VARCHAR(191) NULL,
|
||||
`role` ENUM('ADMIN', 'POWERUSER', 'USER', 'EXTERNAL') NOT NULL DEFAULT 'USER',
|
||||
`desiredShiftsPerMonth` INTEGER NOT NULL DEFAULT 4,
|
||||
`isMale` BOOLEAN NOT NULL DEFAULT true,
|
||||
`isNameForeign` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isSubscribedToCoverMe` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isSubscribedToReminders` BOOLEAN NOT NULL DEFAULT false,
|
||||
`familyHeadId` VARCHAR(191) NULL,
|
||||
`alwaysAsFamily` BOOLEAN NULL DEFAULT false,
|
||||
`type` ENUM('Publisher', 'Bethelite', 'RegularPioneer', 'SpecialPioneer_Missionary') NOT NULL DEFAULT 'Publisher',
|
||||
`town` VARCHAR(191) NULL,
|
||||
`comments` VARCHAR(191) NULL,
|
||||
`lastLogin` DATETIME(3) NULL,
|
||||
`pushSubscription` JSON NULL,
|
||||
`congregationId` INTEGER NULL,
|
||||
`locale` VARCHAR(191) NULL DEFAULT 'bg',
|
||||
|
||||
UNIQUE INDEX `Publisher_email_key`(`email`),
|
||||
UNIQUE INDEX `Publisher_userId_key`(`userId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Congregation` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`address` VARCHAR(191) NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Availability` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`publisherId` VARCHAR(191) NOT NULL,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`dayofweek` ENUM('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') NOT NULL,
|
||||
`dayOfMonth` INTEGER NULL,
|
||||
`weekOfMonth` INTEGER NULL,
|
||||
`startTime` DATETIME(3) NOT NULL,
|
||||
`endTime` DATETIME(3) NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`type` ENUM('Weekly', 'Monthly', 'OneTime', 'PreviousAssignment', 'ReplacementOnly') NOT NULL DEFAULT 'Weekly',
|
||||
`isWithTransportIn` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isWithTransportOut` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isFromPreviousAssignment` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isFromPreviousMonth` BOOLEAN NOT NULL DEFAULT false,
|
||||
`repeatWeekly` BOOLEAN NULL,
|
||||
`repeatFrequency` INTEGER NULL,
|
||||
`endDate` DATETIME(3) NULL,
|
||||
`dateOfEntry` DATETIME(3) NULL,
|
||||
`parentAvailabilityId` INTEGER NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `CartEvent` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`startTime` DATETIME(3) NOT NULL,
|
||||
`endTime` DATETIME(3) NOT NULL,
|
||||
`shiftDuration` INTEGER NOT NULL,
|
||||
`dayofweek` ENUM('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`locationId` INTEGER NOT NULL,
|
||||
`eventType` ENUM('PW_Cart', 'KH_Cleaning') NOT NULL DEFAULT 'PW_Cart',
|
||||
`numberOfPublishers` INTEGER NOT NULL DEFAULT 3,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Shift` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`cartEventId` INTEGER NOT NULL,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`startTime` DATETIME(3) NOT NULL,
|
||||
`endTime` DATETIME(3) NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`requiresTransport` BOOLEAN NOT NULL DEFAULT false,
|
||||
`notes` VARCHAR(191) NULL,
|
||||
`reportId` INTEGER NULL,
|
||||
`isPublished` BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
UNIQUE INDEX `Shift_reportId_key`(`reportId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Assignment` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`shiftId` INTEGER NOT NULL,
|
||||
`publisherId` VARCHAR(191) NOT NULL,
|
||||
`isBySystem` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isConfirmed` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isWithTransport` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isMailSent` BOOLEAN NOT NULL DEFAULT false,
|
||||
`publicGuid` VARCHAR(191) NULL,
|
||||
`originalPublisherId` VARCHAR(191) NULL,
|
||||
|
||||
UNIQUE INDEX `Assignment_publicGuid_key`(`publicGuid`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Location` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`address` VARCHAR(191) NOT NULL,
|
||||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||
`content` LONGTEXT NULL,
|
||||
`picture1` VARCHAR(191) NULL,
|
||||
`picture2` VARCHAR(191) NULL,
|
||||
`picture3` VARCHAR(191) NULL,
|
||||
`backupLocationId` INTEGER NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Report` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME(3) NOT NULL,
|
||||
`publisherId` VARCHAR(191) NOT NULL,
|
||||
`locationId` INTEGER NULL,
|
||||
`placementCount` INTEGER NULL,
|
||||
`videoCount` INTEGER NULL,
|
||||
`returnVisitInfoCount` INTEGER NULL,
|
||||
`conversationCount` INTEGER NULL,
|
||||
`experienceInfo` LONGTEXT NULL,
|
||||
`type` ENUM('ServiceReport', 'Experience', 'Feedback_Problem', 'Feedback_Suggestion', 'Feedback') NOT NULL DEFAULT 'ServiceReport',
|
||||
`comments` VARCHAR(191) NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Survey` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`content` VARCHAR(191) NOT NULL,
|
||||
`answers` JSON NULL,
|
||||
`publicFrom` DATETIME(3) NULL,
|
||||
`publicUntil` DATETIME(3) NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Message` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`publisherId` VARCHAR(191) NOT NULL,
|
||||
`date` DATETIME(3) NOT NULL,
|
||||
`content` VARCHAR(191) NOT NULL,
|
||||
`isRead` BOOLEAN NOT NULL DEFAULT false,
|
||||
`isPublic` BOOLEAN NOT NULL DEFAULT false,
|
||||
`type` ENUM('Email', 'SMS', 'Push', 'InApp') NOT NULL DEFAULT 'Email',
|
||||
`publicUntil` DATETIME(3) NULL,
|
||||
`shownDate` DATETIME(3) NULL,
|
||||
`answer` VARCHAR(191) NULL,
|
||||
`answerDate` DATETIME(3) NULL,
|
||||
`surveyId` INTEGER NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `EventLog` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME(3) NOT NULL,
|
||||
`publisherId` VARCHAR(191) NULL,
|
||||
`shiftId` INTEGER NULL,
|
||||
`content` VARCHAR(5000) NOT NULL,
|
||||
`type` ENUM('AssignmentReplacementManual', 'AssignmentReplacementRequested', 'AssignmentReplacementAccepted', 'SentEmail', 'PasswordResetRequested', 'PasswordResetEmailConfirmed', 'PasswordResetCompleted') NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `User` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`name` VARCHAR(191) NULL,
|
||||
`email` VARCHAR(191) NULL,
|
||||
`emailVerifyToken` VARCHAR(191) NULL,
|
||||
`emailVerified` DATETIME(3) NULL,
|
||||
`image` VARCHAR(191) NULL,
|
||||
`passwordHashLocalAccount` VARCHAR(191) NULL,
|
||||
`publisherId` VARCHAR(191) NULL,
|
||||
|
||||
UNIQUE INDEX `User_email_key`(`email`),
|
||||
UNIQUE INDEX `User_publisherId_key`(`publisherId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Account` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`userId` VARCHAR(191) NOT NULL,
|
||||
`type` VARCHAR(191) NOT NULL,
|
||||
`provider` VARCHAR(191) NOT NULL,
|
||||
`providerAccountId` VARCHAR(191) NOT NULL,
|
||||
`refresh_token` VARCHAR(191) NULL,
|
||||
`access_token` VARCHAR(191) NULL,
|
||||
`expires_at` INTEGER NULL,
|
||||
`token_type` VARCHAR(191) NULL,
|
||||
`scope` VARCHAR(191) NULL,
|
||||
`id_token` VARCHAR(191) NULL,
|
||||
`session_state` VARCHAR(191) NULL,
|
||||
|
||||
UNIQUE INDEX `Account_provider_providerAccountId_key`(`provider`, `providerAccountId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Session` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`sessionToken` VARCHAR(191) NOT NULL,
|
||||
`userId` VARCHAR(191) NOT NULL,
|
||||
`expires` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `Session_sessionToken_key`(`sessionToken`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `VerificationToken` (
|
||||
`identifier` VARCHAR(191) NOT NULL,
|
||||
`token` VARCHAR(191) NOT NULL,
|
||||
`expires` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `VerificationToken_token_key`(`token`),
|
||||
UNIQUE INDEX `VerificationToken_identifier_token_key`(`identifier`, `token`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Settings` (
|
||||
`key` VARCHAR(191) NOT NULL,
|
||||
`value` VARCHAR(191) NOT NULL,
|
||||
`description` VARCHAR(191) NULL,
|
||||
|
||||
PRIMARY KEY (`key`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Publisher` ADD CONSTRAINT `Publisher_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Publisher` ADD CONSTRAINT `Publisher_familyHeadId_fkey` FOREIGN KEY (`familyHeadId`) REFERENCES `Publisher`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Publisher` ADD CONSTRAINT `Publisher_congregationId_fkey` FOREIGN KEY (`congregationId`) REFERENCES `Congregation`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Availability` ADD CONSTRAINT `Availability_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Availability` ADD CONSTRAINT `Availability_parentAvailabilityId_fkey` FOREIGN KEY (`parentAvailabilityId`) REFERENCES `Availability`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `CartEvent` ADD CONSTRAINT `CartEvent_locationId_fkey` FOREIGN KEY (`locationId`) REFERENCES `Location`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Shift` ADD CONSTRAINT `Shift_cartEventId_fkey` FOREIGN KEY (`cartEventId`) REFERENCES `CartEvent`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Shift` ADD CONSTRAINT `Shift_reportId_fkey` FOREIGN KEY (`reportId`) REFERENCES `Report`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_shiftId_fkey` FOREIGN KEY (`shiftId`) REFERENCES `Shift`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Assignment` ADD CONSTRAINT `Assignment_originalPublisherId_fkey` FOREIGN KEY (`originalPublisherId`) REFERENCES `Publisher`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Location` ADD CONSTRAINT `Location_backupLocationId_fkey` FOREIGN KEY (`backupLocationId`) REFERENCES `Location`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Report` ADD CONSTRAINT `Report_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Report` ADD CONSTRAINT `Report_locationId_fkey` FOREIGN KEY (`locationId`) REFERENCES `Location`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Message` ADD CONSTRAINT `Message_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Message` ADD CONSTRAINT `Message_surveyId_fkey` FOREIGN KEY (`surveyId`) REFERENCES `Survey`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `EventLog` ADD CONSTRAINT `EventLog_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `EventLog` ADD CONSTRAINT `EventLog_shiftId_fkey` FOREIGN KEY (`shiftId`) REFERENCES `Shift`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Account` ADD CONSTRAINT `Account_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Session` ADD CONSTRAINT `Session_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -264,6 +264,15 @@ enum MessageType {
|
||||
InApp
|
||||
}
|
||||
|
||||
model Survey {
|
||||
id Int @id @default(autoincrement())
|
||||
content String
|
||||
answers Json?
|
||||
messages Message[]
|
||||
publicFrom DateTime?
|
||||
publicUntil DateTime?
|
||||
}
|
||||
|
||||
model Message {
|
||||
id Int @id @default(autoincrement())
|
||||
publisher Publisher @relation(fields: [publisherId], references: [id])
|
||||
@ -274,6 +283,12 @@ model Message {
|
||||
isPublic Boolean @default(false)
|
||||
type MessageType @default(Email)
|
||||
publicUntil DateTime?
|
||||
shownDate DateTime?
|
||||
answer String?
|
||||
answerDate DateTime?
|
||||
|
||||
Survey Survey? @relation(fields: [surveyId], references: [id], onDelete: Cascade)
|
||||
surveyId Int?
|
||||
}
|
||||
|
||||
enum EventLogType {
|
||||
@ -348,3 +363,9 @@ model VerificationToken {
|
||||
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
|
||||
model Settings {
|
||||
key String @id
|
||||
value String
|
||||
description String?
|
||||
}
|
||||
|
@ -390,43 +390,7 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
||||
|
||||
///console.log(`publishers: ${publishers.length}, WhereClause: ${JSON.stringify(whereClause)}`);
|
||||
|
||||
// include repeating weekly availabilities. generate occurrences for the month
|
||||
// convert matching weekly availabilities to availabilities for the day to make further processing easier on the client.
|
||||
publishers.forEach(pub => {
|
||||
pub.availabilities = pub.availabilities.map(avail => {
|
||||
if (avail.dayOfMonth == null) {
|
||||
if (filterAvailabilitiesByDate && !isForTheMonth) {
|
||||
// filter out repeating availabilities when on other day of week
|
||||
if (filterTimeFrom) {
|
||||
if (avail.dayofweek != dayOfWeekEnum) {
|
||||
return 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
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (filterAvailabilitiesByDate && !isForTheMonth) {
|
||||
if (avail.startTime >= filterTimeFrom && avail.startTime <= filterTimeTo) {
|
||||
return avail;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return avail;
|
||||
}
|
||||
})
|
||||
.filter(avail => avail !== null);
|
||||
});
|
||||
|
||||
|
||||
// ---------------------------------------------- statistics ----------------------------------------------
|
||||
let currentWeekStart, currentWeekEnd;
|
||||
|
||||
if (isWithStats) {
|
||||
@ -494,8 +458,45 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
|
||||
return avail.startTime >= filterDate && avail.startTime <= filterTimeTo;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------
|
||||
// include repeating weekly availabilities. generate occurrences for the month
|
||||
// convert matching weekly availabilities to availabilities for the day to make further processing easier on the client.
|
||||
publishers.forEach(pub => {
|
||||
pub.availabilities = pub.availabilities.map(avail => {
|
||||
if (avail.dayOfMonth == null) {
|
||||
if (filterAvailabilitiesByDate && !isForTheMonth) {
|
||||
// filter out repeating availabilities when on other day of week
|
||||
if (filterTimeFrom) {
|
||||
if (avail.dayofweek != dayOfWeekEnum) {
|
||||
return 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
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (filterAvailabilitiesByDate && !isForTheMonth) {
|
||||
if (avail.startTime >= filterTimeFrom && avail.startTime <= filterTimeTo) {
|
||||
return avail;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return avail;
|
||||
}
|
||||
})
|
||||
.filter(avail => avail !== null);
|
||||
});
|
||||
|
||||
// ToDo: test case/unit test
|
||||
// ToDo: check and validate the filtering and calculations
|
||||
if (isExactTime) {
|
||||
@ -835,246 +836,302 @@ async function getCoverMePublisherEmails(shiftId) {
|
||||
return { shift, availablePublishers: availablePublishers, subscribedPublishers };
|
||||
}
|
||||
|
||||
// ### COPIED TO shift api (++) ###
|
||||
// ### COPIED TO shift api (/shiftgenerate.ts) (++) ###
|
||||
|
||||
/** JSDoc
|
||||
* Generates a schedule.
|
||||
*
|
||||
0. generate shifts and assign publishers from the previous month if still available
|
||||
1. Make sure we always put people only when they are available.
|
||||
2. First provision one male or two females that are available for transport in the first and last shifts.
|
||||
3, Then gradually fill all other shifts with day by day troughout the whole month (monthInfo.firstMonday to .lastSunday) with first one, then two, then 3 and wherever possible more (up to CartEvent.numberOfPublishers number)
|
||||
4. Some publishers are available only at specific time (somoetimes only once) and other are more available. if people are available only for this time, prioritize them so they are not left behind.
|
||||
5. prioritize based on publisher's desiredShiftsPerMonth and previous months assignments.
|
||||
6. Idealy noone should be more than once a week. disqualify publishers already on a shift this week. only assign them if there are no other options and we have less than 3 publishers on a specific shift.
|
||||
*
|
||||
* @param {Axios} axios Axios instance for making requests.
|
||||
* @param {string} date The date for the schedule.
|
||||
* @param {boolean} [copyFromPreviousMonth=false] Whether to copy from the previous month.
|
||||
* @param {boolean} [autoFill=false] Whether to autofill data.
|
||||
* @param {boolean} forDay Specific day flag.
|
||||
*/
|
||||
async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay) {
|
||||
let missingPublishers = [];
|
||||
let publishersWithChangedPref = [];
|
||||
// /** JSDoc
|
||||
// * Generates a schedule.
|
||||
// *
|
||||
// 0. generate shifts and assign publishers from the previous month if still available
|
||||
// 1. Make sure we always put people only when they are available.
|
||||
// 2. First provision one male or two females that are available for transport in the first and last shifts.
|
||||
// 3, Then gradually fill all other shifts with day by day troughout the whole month (monthInfo.firstMonday to .lastSunday) with first one, then two, then 3 and wherever possible more (up to CartEvent.numberOfPublishers number)
|
||||
// 4. Some publishers are available only at specific time (somoetimes only once) and other are more available. if people are available only for this time, prioritize them so they are not left behind.
|
||||
// 5. prioritize based on publisher's desiredShiftsPerMonth and previous months assignments.
|
||||
// 6. Idealy noone should be more than once a week. disqualify publishers already on a shift this week. only assign them if there are no other options and we have less than 3 publishers on a specific shift.
|
||||
// *
|
||||
// * @param {Axios} axios Axios instance for making requests.
|
||||
// * @param {string} date The date for the schedule.
|
||||
// * @param {boolean} [copyFromPreviousMonth=false] Whether to copy from the previous month.
|
||||
// * @param {boolean} [autoFill=false] Whether to autofill data.
|
||||
// * @param {boolean} forDay Specific day flag.
|
||||
// */
|
||||
// async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay) {
|
||||
// let missingPublishers = [];
|
||||
// let publishersWithChangedPref = [];
|
||||
|
||||
const prisma = common.getPrismaClient();
|
||||
try {
|
||||
const monthInfo = common.getMonthDatesInfo(new Date(date));
|
||||
const lastMonthInfo = common.getMonthDatesInfo(new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 1, 1));
|
||||
// const prisma = common.getPrismaClient();
|
||||
// try {
|
||||
// const monthInfo = common.getMonthDatesInfo(new Date(date));
|
||||
// const lastMonthInfo = common.getMonthDatesInfo(new Date(monthInfo.date.getFullYear(), monthInfo.date.getMonth() - 1, 1));
|
||||
|
||||
if (forDay) {
|
||||
await DeleteShiftsForDay(monthInfo.date);
|
||||
} else {
|
||||
await DeleteShiftsForMonth(monthInfo);
|
||||
}
|
||||
// if (forDay) {
|
||||
// await DeleteShiftsForDay(monthInfo.date);
|
||||
// } else {
|
||||
// await DeleteShiftsForMonth(monthInfo);
|
||||
// }
|
||||
|
||||
const events = await prisma.cartEvent.findMany({
|
||||
where: {
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
let shiftsLastMonth = await getShiftsFromLastMonth(lastMonthInfo);
|
||||
let publishers = await getAllPublishersWithStatisticsMonth('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', date, false, true, false, true, true);
|
||||
// const events = await prisma.cartEvent.findMany({
|
||||
// where: {
|
||||
// isActive: true
|
||||
// }
|
||||
// });
|
||||
// let shiftsLastMonth = await getShiftsFromLastMonth(lastMonthInfo);
|
||||
// let publishers = await getAllPublishersWithStatisticsMonth('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', date, false, true, false, true, true);
|
||||
|
||||
let shiftAssignments = [];
|
||||
let day = new Date(monthInfo.firstMonday);
|
||||
let endDate = monthInfo.lastSunday;
|
||||
let dayNr = 1;
|
||||
let weekNr = 1;
|
||||
// let shiftAssignments = [];
|
||||
// let day = new Date(monthInfo.firstMonday);
|
||||
// let endDate = monthInfo.lastSunday;
|
||||
// let dayNr = 1;
|
||||
// let weekNr = 1;
|
||||
|
||||
if (forDay) {
|
||||
day = monthInfo.date;
|
||||
endDate.setDate(monthInfo.date.getDate() + 1);
|
||||
dayNr = monthInfo.date.getDate();
|
||||
weekNr = common.getWeekNumber(monthInfo.date);
|
||||
}
|
||||
// if (forDay) {
|
||||
// day = monthInfo.date;
|
||||
// endDate.setDate(monthInfo.date.getDate() + 1);
|
||||
// dayNr = monthInfo.date.getDate();
|
||||
// weekNr = common.getWeekNumber(monthInfo.date);
|
||||
// }
|
||||
|
||||
let publishersThisWeek = [];
|
||||
// let publishersThisWeek = [];
|
||||
|
||||
// 0. generate shifts and assign publishers from the previous month if still available
|
||||
while (day < endDate) {
|
||||
let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
console.log("passing schedule generation for " + day.toLocaleDateString());
|
||||
const dayOfM = day.getDate();
|
||||
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
let dayName = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
const event = events.find((event) => event.dayofweek == dayName && (event.dayOfMonth == null || event.dayOfMonth == dayOfM));
|
||||
// // 0. generate shifts and assign publishers from the previous month if still available
|
||||
// while (day < endDate) {
|
||||
// let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
// console.log("passing schedule generation for " + day.toLocaleDateString());
|
||||
// const dayOfM = day.getDate();
|
||||
// let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
// let dayName = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
// const event = events.find((event) => event.dayofweek == dayName && (event.dayOfMonth == null || event.dayOfMonth == dayOfM));
|
||||
|
||||
if (!event) {
|
||||
day.setDate(day.getDate() + 1);
|
||||
continue;
|
||||
}
|
||||
// if (!event) {
|
||||
// day.setDate(day.getDate() + 1);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
event.startTime = new Date(event.startTime);
|
||||
event.endTime = new Date(event.endTime);
|
||||
// event.startTime = new Date(event.startTime);
|
||||
// event.endTime = new Date(event.endTime);
|
||||
|
||||
let startTime = new Date(day);
|
||||
startTime.setHours(event.startTime.getHours());
|
||||
startTime.setMinutes(event.startTime.getMinutes());
|
||||
let endTime = new Date(day);
|
||||
endTime.setHours(event.endTime.getHours());
|
||||
endTime.setMinutes(event.endTime.getMinutes());
|
||||
// let startTime = new Date(day);
|
||||
// startTime.setHours(event.startTime.getHours());
|
||||
// startTime.setMinutes(event.startTime.getMinutes());
|
||||
// let endTime = new Date(day);
|
||||
// endTime.setHours(event.endTime.getHours());
|
||||
// endTime.setMinutes(event.endTime.getMinutes());
|
||||
|
||||
let shiftStart = new Date(startTime);
|
||||
let shiftEnd = new Date(startTime);
|
||||
shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
// let shiftStart = new Date(startTime);
|
||||
// let shiftEnd = new Date(startTime);
|
||||
// shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
|
||||
let shiftNr = 0;
|
||||
while (shiftEnd <= endTime) {
|
||||
shiftNr++;
|
||||
const __shiftName = String(shiftStart.getHours()).padStart(2, "0") + ":" + String(shiftStart.getMinutes()).padStart(2, "0") + " - " + String(shiftEnd.getHours()).padStart(2, "0") + ":" + String(shiftEnd.getMinutes()).padStart(2, "0");
|
||||
shiftAssignments = [];
|
||||
let isTransportRequired = shiftNr == 1 || shiftEnd.getTime() == endTime.getTime();
|
||||
// let shiftNr = 0;
|
||||
// while (shiftEnd <= endTime) {
|
||||
// shiftNr++;
|
||||
// const __shiftName = String(shiftStart.getHours()).padStart(2, "0") + ":" + String(shiftStart.getMinutes()).padStart(2, "0") + " - " + String(shiftEnd.getHours()).padStart(2, "0") + ":" + String(shiftEnd.getMinutes()).padStart(2, "0");
|
||||
// shiftAssignments = [];
|
||||
// let isTransportRequired = shiftNr == 1 || shiftEnd.getTime() == endTime.getTime();
|
||||
|
||||
const shiftLastMonthSameDay = findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr);
|
||||
// const shiftLastMonthSameDay = findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr);
|
||||
|
||||
if (shiftLastMonthSameDay) {
|
||||
for (let assignment of shiftLastMonthSameDay.assignments) {
|
||||
let publisher = assignment.publisher;
|
||||
console.log("found publisher from last month: " + publisher.firstName + " " + publisher.lastName);
|
||||
let availability = await FindPublisherAvailability(publisher.id, shiftStart, shiftEnd, dayOfWeekEnum, weekNr);
|
||||
console.log("availability " + availability?.id + ": " + common.getDateFormattedShort(availability?.startTime) + " " + common.getTimeFormatted(availability?.startTime) + " - " + common.getTimeFormatted(availability?.endTime));
|
||||
// if (shiftLastMonthSameDay) {
|
||||
// for (let assignment of shiftLastMonthSameDay.assignments) {
|
||||
// let publisher = assignment.publisher;
|
||||
// console.log("found publisher from last month: " + publisher.firstName + " " + publisher.lastName);
|
||||
// let availability = await FindPublisherAvailability(publisher.id, shiftStart, shiftEnd, dayOfWeekEnum, weekNr);
|
||||
// console.log("availability " + availability?.id + ": " + common.getDateFormattedShort(availability?.startTime) + " " + common.getTimeFormatted(availability?.startTime) + " - " + common.getTimeFormatted(availability?.endTime));
|
||||
|
||||
if (availability && copyFromPreviousMonth && !publishersThisWeek.includes(publisher.id)) {
|
||||
shiftAssignments.push({
|
||||
publisherId: publisher.id,
|
||||
isConfirmed: true,
|
||||
isWithTransportIn: availability.isWithTransportIn,
|
||||
isWithTransportOut: availability.isWithTransportOut
|
||||
});
|
||||
publishersThisWeek.push(publisher.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (availability && copyFromPreviousMonth && !publishersThisWeek.includes(publisher.id)) {
|
||||
// shiftAssignments.push({
|
||||
// publisherId: publisher.id,
|
||||
// isConfirmed: true,
|
||||
// isWithTransportIn: availability.isWithTransportIn,
|
||||
// isWithTransportOut: availability.isWithTransportOut
|
||||
// });
|
||||
// publishersThisWeek.push(publisher.id);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
let publishersNeeded = event.numberOfPublishers - shiftAssignments.length;
|
||||
//ToDo: check if getAvailablePublishersForShift is working correctly
|
||||
let availablePublishers = await getAvailablePublishersForShift(shiftStart, shiftEnd, publishers, publishersThisWeek);
|
||||
// let publishersNeeded = event.numberOfPublishers - shiftAssignments.length;
|
||||
// //ToDo: check if getAvailablePublishersForShift is working correctly
|
||||
// let availablePublishers = await getAvailablePublishersForShift(shiftStart, shiftEnd, publishers, publishersThisWeek);
|
||||
|
||||
console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + " for the day: " + availablePubsForTheDay.length);
|
||||
// console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + " for the day: " + availablePubsForTheDay.length);
|
||||
|
||||
// Prioritize publishers with minimal availability
|
||||
// SKIP ADDING PUBLISHERS FOR NOW
|
||||
// availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
// // Prioritize publishers with minimal availability
|
||||
// // SKIP ADDING PUBLISHERS FOR NOW
|
||||
// // availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
|
||||
// for (let i = 0; i < publishersNeeded; i++) {
|
||||
// if (availablePublishers[i]) {
|
||||
// shiftAssignments.push({ publisherId: availablePublishers[i].id });
|
||||
// publishersThisWeek.push(availablePublishers[i].id);
|
||||
// }
|
||||
// }
|
||||
// // for (let i = 0; i < publishersNeeded; i++) {
|
||||
// // if (availablePublishers[i]) {
|
||||
// // shiftAssignments.push({ publisherId: availablePublishers[i].id });
|
||||
// // publishersThisWeek.push(availablePublishers[i].id);
|
||||
// // }
|
||||
// // }
|
||||
|
||||
const createdShift = await prisma.shift.create({
|
||||
data: {
|
||||
startTime: shiftStart,
|
||||
endTime: shiftEnd,
|
||||
name: event.dayofweek + " " + shiftStart.toLocaleTimeString() + " - " + shiftEnd.toLocaleTimeString(),
|
||||
requiresTransport: isTransportRequired,
|
||||
cartEvent: {
|
||||
connect: {
|
||||
id: event.id,
|
||||
},
|
||||
},
|
||||
assignments: {
|
||||
create: shiftAssignments.map((a) => {
|
||||
return {
|
||||
publisher: {
|
||||
connect: { id: a.publisherId }
|
||||
},
|
||||
isConfirmed: a.isConfirmed,
|
||||
isBySystem: true,
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
// const createdShift = await prisma.shift.create({
|
||||
// data: {
|
||||
// startTime: shiftStart,
|
||||
// endTime: shiftEnd,
|
||||
// name: event.dayofweek + " " + shiftStart.toLocaleTimeString() + " - " + shiftEnd.toLocaleTimeString(),
|
||||
// requiresTransport: isTransportRequired,
|
||||
// cartEvent: {
|
||||
// connect: {
|
||||
// id: event.id,
|
||||
// },
|
||||
// },
|
||||
// assignments: {
|
||||
// create: shiftAssignments.map((a) => {
|
||||
// return {
|
||||
// publisher: {
|
||||
// connect: { id: a.publisherId }
|
||||
// },
|
||||
// isConfirmed: a.isConfirmed,
|
||||
// isBySystem: true,
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
shiftStart = new Date(shiftEnd);
|
||||
shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
}
|
||||
// shiftStart = new Date(shiftEnd);
|
||||
// shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
|
||||
// }
|
||||
|
||||
day.setDate(day.getDate() + 1);
|
||||
dayNr++;
|
||||
if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
|
||||
weekNr++;
|
||||
publishersThisWeek = [];
|
||||
publishers.forEach(p => p.currentWeekAssignments = 0);
|
||||
}
|
||||
if (forDay) break;
|
||||
}
|
||||
// day.setDate(day.getDate() + 1);
|
||||
// dayNr++;
|
||||
// if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
|
||||
// weekNr++;
|
||||
// publishersThisWeek = [];
|
||||
// publishers.forEach(p => p.currentWeekAssignments = 0);
|
||||
// }
|
||||
// if (forDay) break;
|
||||
// }
|
||||
|
||||
let allShifts = await prisma.shift.findMany({
|
||||
where: {
|
||||
startTime: {
|
||||
gte: monthInfo.firstMonday,
|
||||
lt: monthInfo.lastSunday,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
assignments: {
|
||||
include: {
|
||||
publisher: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// let allShifts = await prisma.shift.findMany({
|
||||
// where: {
|
||||
// startTime: {
|
||||
// gte: monthInfo.firstMonday,
|
||||
// lt: monthInfo.lastSunday,
|
||||
// },
|
||||
// },
|
||||
// include: {
|
||||
// assignments: {
|
||||
// include: {
|
||||
// publisher: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
console.log(" second pass " + monthInfo.monthName + " " + monthInfo.year);
|
||||
// 2. First pass - prioritize shifts with transport where it is needed
|
||||
day = monthInfo.firstMonday;
|
||||
dayNr = 1;
|
||||
weekNr = 1;
|
||||
while (day < endDate) {
|
||||
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
|
||||
if (event) {
|
||||
let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
// console.log(" second pass " + monthInfo.monthName + " " + monthInfo.year);
|
||||
// // 2. First pass - prioritize shifts with transport where it is needed
|
||||
// day = monthInfo.firstMonday;
|
||||
// dayNr = 1;
|
||||
// weekNr = 1;
|
||||
// while (day < endDate) {
|
||||
// let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
|
||||
// let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
|
||||
// if (event) {
|
||||
// let availablePubsForTheDay = await filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, true);
|
||||
|
||||
let shifts = allShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(day));
|
||||
let transportShifts = shifts.filter(s => s.requiresTransport);
|
||||
transportShifts.forEach(shift => {
|
||||
let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
if (publishersNeeded > 0) {//get the beset match
|
||||
if (availablePublishers[0]) {
|
||||
shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
}
|
||||
// let shifts = allShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(day));
|
||||
// let transportShifts = shifts.filter(s => s.requiresTransport);
|
||||
// transportShifts.forEach(shift => {
|
||||
// let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
// availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
// let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
// if (publishersNeeded > 0) {//get the beset match
|
||||
// if (availablePublishers[0]) {
|
||||
// shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
// }
|
||||
|
||||
}
|
||||
});
|
||||
// 3. Second pass - fill the rest of the shifts
|
||||
let shiftsToFill = shifts.filter(s => !s.requiresTransport);
|
||||
shiftsToFill.forEach(shift => {
|
||||
let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
if (publishersNeeded > 0) {//get the beset match
|
||||
if (availablePublishers[0]) {
|
||||
shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
}
|
||||
// }
|
||||
// });
|
||||
// // 3. Second pass - fill the rest of the shifts
|
||||
// let shiftsToFill = shifts.filter(s => !s.requiresTransport);
|
||||
// shiftsToFill.forEach(shift => {
|
||||
// let availablePublishers = availablePubsForTheDay.filter(p => !shift.assignments.some(a => a.publisher.id === p.id));
|
||||
// availablePublishers = availablePublishers.sort((a, b) => a.currentMonthAvailabilityHoursCount - b.currentMonthAvailabilityHoursCount);
|
||||
// let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
|
||||
// if (publishersNeeded > 0) {//get the beset match
|
||||
// if (availablePublishers[0]) {
|
||||
// shift.assignments.push({ publisherId: availablePublishers[i].id });
|
||||
// }
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
day.setDate(day.getDate() + 1);
|
||||
}
|
||||
// day.setDate(day.getDate() + 1);
|
||||
// }
|
||||
|
||||
if (!forDay) {
|
||||
console.log("###############################################");
|
||||
console.log(" DONE CREATING SCHEDULE FOR " + monthInfo.monthName + " " + monthInfo.year);
|
||||
console.log("###############################################");
|
||||
}
|
||||
// if (!forDay) {
|
||||
// console.log("###############################################");
|
||||
// console.log(" DONE CREATING SCHEDULE FOR " + monthInfo.monthName + " " + monthInfo.year);
|
||||
// console.log("###############################################");
|
||||
// }
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return { error: error };
|
||||
}
|
||||
}
|
||||
// return {};
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// return { error: error };
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// async function getShiftsFromLastMonth(monthInfo) {
|
||||
// const prisma = common.getPrismaClient();
|
||||
// // Fetch shifts for the month
|
||||
// const rawShifts = await prisma.shift.findMany({
|
||||
// where: {
|
||||
// startTime: {
|
||||
// gte: monthInfo.firstMonday,
|
||||
// lte: monthInfo.lastSunday,
|
||||
// },
|
||||
// },
|
||||
// include: {
|
||||
// assignments: {
|
||||
// include: {
|
||||
// publisher: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// // Process shifts to add weekNr and shiftNr
|
||||
// return rawShifts.map(shift => ({
|
||||
// ...shift,
|
||||
// weekNr: common.getWeekNumber(new Date(shift.startTime)),
|
||||
// shiftNr: rawShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(shift.startTime)).indexOf(shift) + 1,
|
||||
// weekDay: common.DaysOfWeekArray[new Date(shift.startTime).getDayEuropean()],
|
||||
// }));
|
||||
// }
|
||||
// function findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr) {
|
||||
// let weekDay = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
// return shiftsLastMonth.find(s => {
|
||||
// return s.weekNr === weekNr &&
|
||||
// s.shiftNr === shiftNr &&
|
||||
// s.weekDay === weekDay;
|
||||
// });
|
||||
// }
|
||||
|
||||
// //ToDo use bulk find instead of loop
|
||||
// async function getAvailablePublishersForShift(startTime, endTime, allPublishers, publishersThisWeek) {
|
||||
// let availablePublishers = [];
|
||||
|
||||
// for (let publisher of allPublishers) {
|
||||
// let availability = await FindPublisherAvailability(publisher.id, startTime, endTime);
|
||||
|
||||
// if (availability && !publishersThisWeek.includes(publisher.id)) {
|
||||
// availablePublishers.push(publisher);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return availablePublishers;
|
||||
// }
|
||||
|
||||
|
||||
// ### COPIED TO shift api (--) ###
|
||||
|
||||
async function DeleteShiftsForMonth(monthInfo) {
|
||||
try {
|
||||
@ -1109,57 +1166,6 @@ async function DeleteShiftsForDay(date) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getShiftsFromLastMonth(monthInfo) {
|
||||
const prisma = common.getPrismaClient();
|
||||
// Fetch shifts for the month
|
||||
const rawShifts = await prisma.shift.findMany({
|
||||
where: {
|
||||
startTime: {
|
||||
gte: monthInfo.firstMonday,
|
||||
lte: monthInfo.lastSunday,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
assignments: {
|
||||
include: {
|
||||
publisher: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Process shifts to add weekNr and shiftNr
|
||||
return rawShifts.map(shift => ({
|
||||
...shift,
|
||||
weekNr: common.getWeekNumber(new Date(shift.startTime)),
|
||||
shiftNr: rawShifts.filter(s => common.getISODateOnly(s.startTime) === common.getISODateOnly(shift.startTime)).indexOf(shift) + 1,
|
||||
weekDay: common.DaysOfWeekArray[new Date(shift.startTime).getDayEuropean()],
|
||||
}));
|
||||
}
|
||||
function findTheSameShiftFromLastMonth(shiftsLastMonth, day, weekNr, shiftNr) {
|
||||
let weekDay = common.DaysOfWeekArray[day.getDayEuropean()];
|
||||
return shiftsLastMonth.find(s => {
|
||||
return s.weekNr === weekNr &&
|
||||
s.shiftNr === shiftNr &&
|
||||
s.weekDay === weekDay;
|
||||
});
|
||||
}
|
||||
|
||||
//ToDo use bulk find instead of loop
|
||||
async function getAvailablePublishersForShift(startTime, endTime, allPublishers, publishersThisWeek) {
|
||||
let availablePublishers = [];
|
||||
|
||||
for (let publisher of allPublishers) {
|
||||
let availability = await FindPublisherAvailability(publisher.id, startTime, endTime);
|
||||
|
||||
if (availability && !publishersThisWeek.includes(publisher.id)) {
|
||||
availablePublishers.push(publisher);
|
||||
}
|
||||
}
|
||||
|
||||
return availablePublishers;
|
||||
}
|
||||
|
||||
async function FindPublisherAvailability(publisherId, startDate, endDate, dayOfWeekEnum, weekNr) {
|
||||
const prisma = common.getPrismaClient();
|
||||
const start = new Date(startDate);
|
||||
@ -1219,9 +1225,6 @@ async function FindPublisherAvailability(publisherId, startDate, endDate, dayOfW
|
||||
return exactAvailabilities.length > 0 ? exactAvailabilities[0] : repeatingAvailabilities.length > 0 ? repeatingAvailabilities[0] : null;
|
||||
}
|
||||
|
||||
// ### COPIED TO shift api (--) ###
|
||||
|
||||
|
||||
// function matchesAvailability(avail, filterDate) {
|
||||
// // Setting the start and end time of the filterDate
|
||||
// filterDate.setHours(0, 0, 0, 0);
|
||||
@ -1267,7 +1270,7 @@ module.exports = {
|
||||
getCoverMePublisherEmails,
|
||||
getAllPublishersWithStatisticsMonth,
|
||||
getCalendarEvents,
|
||||
GenerateSchedule,
|
||||
// GenerateSchedule,
|
||||
DeleteShiftsForMonth,
|
||||
DeleteShiftsForDay,
|
||||
};
|
@ -1,11 +1,22 @@
|
||||
const winston = require('winston');
|
||||
require('winston-daily-rotate-file');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Define the logs directory path
|
||||
const logDirectory = path.join(__dirname, '../logs');
|
||||
|
||||
// Ensure the logs directory exists
|
||||
if (!fs.existsSync(logDirectory)) {
|
||||
fs.mkdirSync(logDirectory);
|
||||
}
|
||||
|
||||
// Define the log configuration
|
||||
const logConfiguration = {
|
||||
'transports': [
|
||||
transports: [
|
||||
new winston.transports.DailyRotateFile({
|
||||
filename: './logs/application-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD', // new file is created every hour: 'YYYY-MM-DD-HH'
|
||||
filename: path.join(logDirectory, 'application-%DATE%.log'),
|
||||
datePattern: 'YYYY-MM-DD', // new file is created every day
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '90d',
|
||||
@ -20,6 +31,7 @@ const logConfiguration = {
|
||||
)
|
||||
};
|
||||
|
||||
// Create the logger
|
||||
const logger = winston.createLogger(logConfiguration);
|
||||
|
||||
module.exports = logger;
|
||||
|
@ -32,7 +32,8 @@
|
||||
"components/location/LocationForm.js",
|
||||
"pages/cart/locations/[id].tsx.old",
|
||||
"components/publisher/ShiftsList.js",
|
||||
"src/helpers/data.js"
|
||||
"src/helpers/data.js",
|
||||
"components/survey/SurveyForm.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
Reference in New Issue
Block a user