360 lines
13 KiB
TypeScript
360 lines
13 KiB
TypeScript
import { useSession } from "next-auth/react"
|
||
import { useRouter } from 'next/router';
|
||
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";
|
||
import common from '../src/helpers/common';
|
||
import { Availability, UserRole } from "@prisma/client";
|
||
import ProtectedRoute, { serverSideAuth } from "../components/protectedRoute";
|
||
import axiosInstance from '../src/axiosSecure';
|
||
// const dataHelper = require('../../src/helpers/data');
|
||
import dataHelper from '../src/helpers/data';
|
||
|
||
import { authOptions } from './api/auth/[...nextauth]'
|
||
import { getServerSession } from "next-auth/next"
|
||
|
||
import PublisherSearchBox from '../components/publisher/PublisherSearchBox';
|
||
import PublisherInlineForm from '../components/publisher/PublisherInlineForm';
|
||
import CartEventForm from "components/cartevent/CartEventForm";
|
||
|
||
|
||
interface IProps {
|
||
initialItems: Availability[];
|
||
initialUserId: string;
|
||
cartEvents: any;
|
||
lastPublishedDate: Date;
|
||
messages: any;
|
||
}
|
||
export default function DashboardPage({ initialItems, initialUserId, cartEvents, lastPublishedDate, messages }: IProps) {
|
||
const router = useRouter();
|
||
const { newLogin } = router.query;
|
||
const { data: session } = useSession();
|
||
const [userName, setUserName] = useState('');
|
||
const [userId, setUserId] = useState(initialUserId);
|
||
const [events, setEvents] = useState(initialItems?.map(item => ({
|
||
...item,
|
||
title: item.name,
|
||
date: new Date(item.startTime),
|
||
startTime: new Date(item.startTime),
|
||
endTime: new Date(item.endTime),
|
||
publisherId: item.publisherId,
|
||
})));
|
||
|
||
useEffect(() => {
|
||
if (session && userName === '' && session.user.name) {
|
||
setUserName(session.user.name);
|
||
setUserId(session.user.id);
|
||
//handleUserSelection({ id: session.user.id, firstName: session.user.name, lastName: '' });
|
||
}
|
||
}, [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
|
||
|
||
|
||
// 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);
|
||
setUserName(publisher.firstName + " " + publisher.lastName);
|
||
setUserId(publisher.id);
|
||
|
||
try {
|
||
let events = await axiosInstance.get(`/api/?action=getCalendarEvents&publisherId=${publisher.id}`);
|
||
setEvents(events.data);
|
||
} catch (error) {
|
||
console.error("Error fetching publisher info:", error);
|
||
// Handle the error appropriately
|
||
}
|
||
};
|
||
|
||
// EXAMPLE USAGE OF ProtectedRoute
|
||
ProtectedRoute.IsInRole(UserRole.ADMIN).then(isAdmin => {
|
||
if (isAdmin) {
|
||
console.log("User is an admin.");
|
||
} else {
|
||
console.log("User is not an admin.");
|
||
}
|
||
});
|
||
|
||
return (
|
||
<Layout>
|
||
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER, UserRole.USER, UserRole.EXTERNAL]} deniedMessage="">
|
||
<h1 className="pt-2 pb-1 text-xl font-bold text-center">Графика на {userName}</h1>
|
||
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage="">
|
||
<PublisherSearchBox selectedId={userId} infoText="" onChange={handleUserSelection} />
|
||
</ProtectedRoute>
|
||
<div className="flex flex-row md:flex-row mt-4 xs:mt-1 space-y-4 md:space-y-0 md:space-x-4 h-[calc(100vh-10rem)]">
|
||
<div className="flex-1">
|
||
<div className="text-center font-bold pb-3 xs:pb-1">
|
||
<PublisherInlineForm publisherId={userId} />
|
||
</div>
|
||
<AvCalendar publisherId={userId} events={events} selectedDate={new Date()} cartEvents={cartEvents} lastPublishedDate={lastPublishedDate} />
|
||
</div>
|
||
</div>
|
||
</ProtectedRoute>
|
||
</Layout>
|
||
);
|
||
}
|
||
|
||
|
||
export const getServerSideProps = async (context) => {
|
||
const auth = await serverSideAuth({
|
||
req: context.req,
|
||
allowedRoles: [/* ...allowed roles... */]
|
||
});
|
||
// const session = await getSession(context);
|
||
const sessionServer = await getServerSession(context.req, context.res, authOptions)
|
||
|
||
if (!sessionServer) {
|
||
return {
|
||
redirect: {
|
||
destination: '/auth/signin',
|
||
permanent: false,
|
||
},
|
||
};
|
||
}
|
||
|
||
const role = sessionServer?.user.role;
|
||
console.log("server role: " + role);
|
||
const userId = sessionServer?.user.id;
|
||
var isAdmin = sessionServer?.user.role == UserRole.ADMIN;//role.localeCompare(UserRole.ADMIN) === 0;
|
||
|
||
var items = await dataHelper.getCalendarEvents(userId, true, true, isAdmin);
|
||
// common.convertDatesToISOStrings(items);
|
||
//serializable dates
|
||
items = items.map(item => {
|
||
const updatedItem = {
|
||
...item,
|
||
startTime: item.startTime.toISOString(),
|
||
endTime: item.endTime.toISOString(),
|
||
date: item.date.toISOString(),
|
||
name: common.getTimeFormatted(item.startTime) + "-" + common.getTimeFormatted(item.endTime)
|
||
};
|
||
|
||
if (updatedItem.shift) {
|
||
updatedItem.shift = {
|
||
...updatedItem.shift,
|
||
startTime: updatedItem.shift.startTime.toISOString(),
|
||
endTime: updatedItem.shift.endTime.toISOString()
|
||
};
|
||
updatedItem.isPublished = updatedItem.shift.isPublished;
|
||
}
|
||
|
||
return updatedItem;
|
||
});
|
||
|
||
// log first availability startTime to verify timezone and UTC conversion
|
||
|
||
const prisma = common.getPrismaClient();
|
||
let cartEvents = await prisma.cartEvent.findMany({
|
||
where: {
|
||
isActive: true,
|
||
},
|
||
select: {
|
||
id: true,
|
||
startTime: true,
|
||
endTime: true,
|
||
dayofweek: true,
|
||
shiftDuration: true,
|
||
}
|
||
});
|
||
cartEvents = common.convertDatesToISOStrings(cartEvents);
|
||
let lastPublishedDate = (await prisma.shift.findFirst({
|
||
where: {
|
||
isPublished: true,
|
||
},
|
||
select: {
|
||
endTime: true,
|
||
},
|
||
orderBy: {
|
||
endTime: 'desc'
|
||
}
|
||
}))?.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) => {
|
||
let now = new Date();
|
||
return (!message.Survey.publicFrom || message.Survey.publicFrom <= common.getStartOfDay(now))
|
||
&& (!message.Survey.publicUntil || message.Survey.publicUntil >= common.getEndOfDay(now))
|
||
});
|
||
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: {
|
||
initialItems: items,
|
||
userId: sessionServer?.user.id,
|
||
cartEvents: cartEvents,
|
||
lastPublishedDate: lastPublishedDate.toISOString(),
|
||
messages: messages
|
||
},
|
||
};
|
||
}
|