added surveys
This commit is contained in:
@ -1,5 +1,9 @@
|
|||||||
|
|
||||||
import { UserRole } from "@prisma/client";
|
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 = [
|
const sidemenu = [
|
||||||
@ -103,6 +107,19 @@ const sidemenu = [
|
|||||||
text: "Календар",
|
text: "Календар",
|
||||||
url: "/cart/calendar",
|
url: "/cart/calendar",
|
||||||
roles: [UserRole.ADMIN, UserRole.POWERUSER],
|
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",
|
id: "cart-reports",
|
||||||
text: "Отчети",
|
text: "Отчети",
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
import axiosInstance from '../../src/axiosSecure';
|
|
||||||
import { 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';
|
|
||||||
|
|
||||||
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?
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function SurveyForm({ existingItem }) {
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const [editMode, setEditMode] = useState(existingItem ? true : false);
|
|
||||||
const [item, setItem] = useState(existingItem || {
|
|
||||||
content: "Нова анкета",
|
|
||||||
answers: [],
|
|
||||||
publicFrom: new Date().toISOString(),
|
|
||||||
publicUntil: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
useState(() => setEditMode(existingItem ? true : false), [existingItem]);
|
|
||||||
|
|
||||||
|
|
||||||
const handleChange = ({ target }) => {
|
|
||||||
setItem({ ...item, [target.name]: target.value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDateChange = (fieldName, newDate) => {
|
|
||||||
setItem((prevItem) => ({
|
|
||||||
...prevItem,
|
|
||||||
[fieldName]: newDate
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
//get all publisherIds and create a message for each
|
|
||||||
const pubs = await axiosInstance.get("/api/data/publishers");
|
|
||||||
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",
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
//alert("Нещо се обърка при изтриването. Моля, опитайте отново или се свържете с нас");
|
|
||||||
console.log(JSON.stringify(error));
|
|
||||||
toast.error(error.response?.data?.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" id="date" name="date" type="date" 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" id="date" name="date" type="date" 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" id="content" name="content" onChange={handleChange} value={item.content} autoComplete="off" />
|
|
||||||
</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" id="answers" name="answers" type="text" onChange={handleChange} value={item.answers} autoComplete="off" />
|
|
||||||
</div>
|
|
||||||
{/* show count of each answer and the total answered/unanswered messages */}
|
|
||||||
{/* {item.answers.map((answer) => (
|
|
||||||
<div className="mb-4">
|
|
||||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="answers">
|
|
||||||
{answer}
|
|
||||||
</label>
|
|
||||||
{item.messages.filter((message) => message.answer === answer).length}
|
|
||||||
|
|
||||||
</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="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 >
|
|
||||||
);
|
|
||||||
}
|
|
233
components/survey/SurveyForm.tsx
Normal file
233
components/survey/SurveyForm.tsx
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import axiosInstance from '../../src/axiosSecure';
|
||||||
|
import { 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 [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]);
|
||||||
|
|
||||||
|
|
||||||
|
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 pubs = await axiosInstance.get("/api/data/publishers");
|
||||||
|
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 || "Нещо се обърка при изтриването. Моля, опитай отново и се свържете с нас ако проблема продължи.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => (
|
||||||
|
<div key={index} className="mb-2">
|
||||||
|
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor={`answer-${index}`}>
|
||||||
|
{answer}
|
||||||
|
</label>
|
||||||
|
<p className="text-gray-700">
|
||||||
|
{item.messages ? item.messages.filter((message) => message.answer === answer).length : 0}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="block text-gray-700 text-sm font-bold mb-2">Общо отговорили</label>
|
||||||
|
<p className="text-gray-700">{item.messages ? item.messages.filter((message) => message.answer).length : 0}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="block text-gray-700 text-sm font-bold mb-2">Общо неотговорили</label>
|
||||||
|
<p className="text-gray-700">{item.messages ? item.messages.filter((message) => !message.answer).length : 0}</p>
|
||||||
|
</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;
|
@ -13,39 +13,60 @@ const SurveyPage = ({ serverSurveys }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="view-location-page max-w-4xl mx-auto my-8">
|
<div className="max-w-4xl mx-auto my-8 p-4 bg-white shadow-md rounded">
|
||||||
<h1>Survey Page</h1>
|
<h1 className="text-2xl font-bold mb-4">Анкети</h1>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<div className="w-1/2">
|
<div className="w-1/2 pr-4">
|
||||||
<h2>Surveys</h2>
|
<h2 className="text-xl font-semibold mb-4">Списък</h2>
|
||||||
<ul>
|
<ul className="space-y-4">
|
||||||
{serverSurveys.map((survey) => (
|
{serverSurveys.map((survey) => (
|
||||||
<li key={survey.id}>
|
<li key={survey.id} className="p-4 border rounded bg-gray-50 shadow-sm">
|
||||||
{survey.id}: {survey.context}: <br />
|
<p className="font-medium">{survey.id}: {survey.content}</p>
|
||||||
{Object.entries(_.groupBy(survey.messages, 'answer')).map(([key, items]) => (
|
{/* <p className="text-gray-700">{survey.publicFrom} - {survey.publicUntil}</p> */}
|
||||||
<div key={key}>
|
<p className="mt-2"> [{survey.answers}] </p>
|
||||||
{key}: {items.length}
|
<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>
|
</div>
|
||||||
))}
|
<button
|
||||||
<button className='btn' onClick={() => setSelectedSurvey(survey)}>Edit</button>
|
className="btn mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded"
|
||||||
</li>
|
onClick={() => setSelectedSurvey(survey)}
|
||||||
))}
|
>
|
||||||
</ul>
|
Зареди детайли
|
||||||
<button className='btn' onClick={() => setSelectedSurvey(null)}>New Survey</button>
|
</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 className="w-1/2 pl-4">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Selected Survey</h2>
|
||||||
|
{selectedSurvey ? (
|
||||||
|
<div className="p-4 border rounded bg-gray-50 shadow-sm">
|
||||||
|
<h3 className="text-lg font-bold">{selectedSurvey.title}</h3>
|
||||||
|
<p className="text-gray-700 mt-2">{selectedSurvey.description}</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-700">No survey selected.</p>
|
||||||
|
)}
|
||||||
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/2">
|
<div className="mt-8">
|
||||||
<h2>Selected Survey</h2>
|
|
||||||
{selectedSurvey && (
|
|
||||||
<div>
|
|
||||||
<h3>{selectedSurvey.title}</h3>
|
|
||||||
<p>{selectedSurvey.description}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SurveyForm existingItem={selectedSurvey} />
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
264
pages/dash.tsx
264
pages/dash.tsx
@ -2,6 +2,7 @@ import { useSession } from "next-auth/react"
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Layout from "../components/layout"
|
import Layout from "../components/layout"
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AvCalendar from '../components/calendar/avcalendar';
|
import AvCalendar from '../components/calendar/avcalendar';
|
||||||
import { getSession } from "next-auth/react";
|
import { getSession } from "next-auth/react";
|
||||||
@ -25,9 +26,9 @@ interface IProps {
|
|||||||
initialUserId: string;
|
initialUserId: string;
|
||||||
cartEvents: any;
|
cartEvents: any;
|
||||||
lastPublishedDate: Date;
|
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 router = useRouter();
|
||||||
const { newLogin } = router.query;
|
const { newLogin } = router.query;
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@ -51,17 +52,132 @@ export default function DashboardPage({ initialItems, initialUserId, cartEvents,
|
|||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MESSAGES
|
||||||
//const [notificationsVisible, setNotificationsVisible] = useState(false);
|
//const [notificationsVisible, setNotificationsVisible] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
//if (newLogin === 'true')
|
// //if (newLogin === 'true')
|
||||||
{
|
// {
|
||||||
// alert("Мили братя, искаме само да ви напомним да ни изпратите вашите предпочитания за юни до 25-то число като използвате меню 'Възможности'. Ако имате проблем, моля пишете ни на 'specialnosvidetelstvanesofia@gmail.com'");
|
// // alert("Мили братя, искаме само да ви напомним да ни изпратите вашите предпочитания за юни до 25-то число като използвате меню 'Възможности'. Ако имате проблем, моля пишете ни на 'specialnosvidetelstvanesofia@gmail.com'");
|
||||||
const currentPath = router.pathname;
|
// const currentPath = router.pathname;
|
||||||
router.replace(currentPath, undefined, { shallow: true }); // Removes the query without affecting the history
|
// router.replace(currentPath, undefined, { shallow: true }); // Removes the query without affecting the history
|
||||||
}
|
// }
|
||||||
}, []);// show the message every time we load the page
|
// }, []);// show the message every time we load the page
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (messages.length > 0) {
|
||||||
|
showMessageToasts(messages);
|
||||||
|
}
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const showMessageToasts = (messages, handleMessageOptionAnswer) => {
|
||||||
|
const handleOptionClick = async (messageId, option, toastId) => {
|
||||||
|
try {
|
||||||
|
await axiosInstance.put(`/api/data/messages/${messageId}`, { answer: option });
|
||||||
|
toast.dismiss(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-${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, // Keep the toast open until manually closed
|
||||||
|
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) => {
|
const handleUserSelection = async (publisher) => {
|
||||||
if (!publisher || publisher.id === undefined) return;
|
if (!publisher || publisher.id === undefined) return;
|
||||||
console.log("selecting publisher", publisher.id);
|
console.log("selecting publisher", publisher.id);
|
||||||
@ -107,107 +223,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) => {
|
export const getServerSideProps = async (context) => {
|
||||||
const auth = await serverSideAuth({
|
const auth = await serverSideAuth({
|
||||||
req: context.req,
|
req: context.req,
|
||||||
@ -293,13 +308,38 @@ export const getServerSideProps = async (context) => {
|
|||||||
lastPublishedDate = lastPublishedDate > blockedDate.value ? lastPublishedDate : 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 {
|
return {
|
||||||
props: {
|
props: {
|
||||||
initialItems: items,
|
initialItems: items,
|
||||||
userId: sessionServer?.user.id,
|
userId: sessionServer?.user.id,
|
||||||
cartEvents: cartEvents,
|
cartEvents: cartEvents,
|
||||||
lastPublishedDate: lastPublishedDate.toISOString(),
|
lastPublishedDate: lastPublishedDate.toISOString(),
|
||||||
// messages: (await import(`../content/i18n/${context.locale}.json`)).default
|
messages: messages
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,8 @@
|
|||||||
"components/location/LocationForm.js",
|
"components/location/LocationForm.js",
|
||||||
"pages/cart/locations/[id].tsx.old",
|
"pages/cart/locations/[id].tsx.old",
|
||||||
"components/publisher/ShiftsList.js",
|
"components/publisher/ShiftsList.js",
|
||||||
"src/helpers/data.js"
|
"src/helpers/data.js",
|
||||||
|
"components/survey/SurveyForm.js"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
Reference in New Issue
Block a user