Merge commit '68915628a95b0fc947877826c5587bd94f4ffed0' into production
This commit is contained in:
@@ -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
|
||||
@@ -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}
|
||||
|
||||
@@ -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: "Отчети",
|
||||
|
||||
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;
|
||||
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"
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -114,7 +114,7 @@ const Notification = async (req, res) => {
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
@@ -715,8 +738,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 +819,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 +833,20 @@ 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: 'open_url',
|
||||
title: 'Open URL',
|
||||
icon: '/images/open-url.png'
|
||||
},
|
||||
{
|
||||
action: 'dismiss',
|
||||
title: '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>}
|
||||
|
||||
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
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
297
pages/dash.tsx
297
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,148 @@ 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 +239,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 +301,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 +311,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 +355,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
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
9
prisma/migrations/20240614185207_/migration.sql
Normal file
9
prisma/migrations/20240614185207_/migration.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `Settings` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`key` VARCHAR(191) NOT NULL,
|
||||
`value` VARCHAR(191) NOT NULL,
|
||||
`description` VARCHAR(191) NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Warnings:
|
||||
- The primary key for the `settings` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- You are about to drop the column `id` on the `settings` table. All the data in the column will be lost.
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Settings`
|
||||
DROP PRIMARY KEY,
|
||||
DROP COLUMN `id`,
|
||||
ADD PRIMARY KEY (`key`);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `Message`
|
||||
ADD COLUMN `answer` VARCHAR(191) NULL,
|
||||
ADD COLUMN `answerDate` DATETIME(3) NULL,
|
||||
ADD COLUMN `shownDate` DATETIME(3) NULL,
|
||||
ADD COLUMN `surveyId` INTEGER NULL;
|
||||
|
||||
-- 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;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Message`
|
||||
ADD CONSTRAINT `Message_surveyId_fkey` FOREIGN KEY (`surveyId`) REFERENCES `Survey` (`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?
|
||||
}
|
||||
|
||||
@@ -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