Merge branch 'main' into production

This commit is contained in:
Dobromir Popov
2024-04-25 20:34:49 +03:00
18 changed files with 208 additions and 68 deletions

View File

@ -214,9 +214,15 @@ fix published schedule to cover end of the week
имейлите - ОК
графика - синк - ОК
вестителите от Фабио -
вестителите от Фабио - OK
потребителите с двойни имейли -
админс can send *urgent* email to everybody to ask for shift
in schedule admin - if a publisher is always pair & family is not in the shift - add + button to add them
last login. pubs.
otchet - za denq
делете цонфирм
статистика - фкс (янка) + posledno vlizane

View File

@ -1,10 +1,12 @@
import zIndex from "@mui/material/styles/zIndex";
export default function ConfirmationModal({ isOpen, onClose, onConfirm, message }) {
//export default function ConfirmationModal({ isOpen, onClose, onConfirm, message })
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center z-50">
<div className="bg-white p-4 rounded-md shadow-lg modal-content">
<div className="opacity-100 fixed inset-0 flex items-center justify-center z-1002" >
<div className="bg-white p-4 rounded-md shadow-lg modal-content" style={{ zIndex: 1002 }}>
<p className="mb-4">{message}</p>
<button
className="bg-red-500 text-white px-4 py-2 rounded mr-2"

View File

@ -193,7 +193,7 @@ function ShiftComponent({ shift, onShiftSelect, isSelected, onPublisherSelect, a
return (
<div key={index}
className={`flow space-x-2 rounded-md px-2 py-1 my-1 ${ass.isConfirmed ? 'bg-green-100' : 'bg-gray-100'} ${borderStyles}`}
className={`flow rounded-md px-2 py-1 sm:py-0.5 my-1 ${ass.isConfirmed ? 'bg-green-100' : 'bg-gray-100'} ${borderStyles}`}
>
<div className="flex justify-between items-center" onClick={() => handlePublisherClick(ass.publisher)}>
<span className="text-gray-700">{publisherInfo.firstName} {publisherInfo.lastName}</span>
@ -202,12 +202,12 @@ function ShiftComponent({ shift, onShiftSelect, isSelected, onPublisherSelect, a
{shift.requiresTransport && (
<span
onClick={ass.canTransport || true ? () => toggleTransport(ass) : undefined}
className={`material-icons ${ass.isWithTransport ? 'text-green-500 font-bold' : (transportProvided ? 'text-gray-400 ' : 'text-orange-400 font-bold')} ${ass.canTransport || ass.isWithTransport || true ? ' cursor-pointer' : 'cursor-not-allowed'} px-3 py-1 ml-2 rounded-md`}
className={`material-icons ${ass.isWithTransport ? 'text-green-500 font-bold' : (transportProvided ? 'text-gray-400 ' : 'text-orange-400 font-bold')} ${ass.canTransport || ass.isWithTransport || true ? ' cursor-pointer' : 'cursor-not-allowed'} px-3 py-0 ml-2 rounded-md`}
>
{ass.isWithTransport ? "транспорт" : ass.canTransport ? "може транспорт" : "без транспорт"} <LocalShippingIcon />
</span>
)}
<button onClick={() => removeAssignment(ass.id)} className="text-white bg-red-500 hover:bg-red-600 px-3 py-1 ml-2 rounded-md" >
<button onClick={() => removeAssignment(ass.id)} className="items-center leading-snug text-white text-center bg-red-500 hover:bg-red-600 px-3 py-1 md:py-0.5 ml-2 rounded-md" >
махни
</button>

View File

@ -55,12 +55,12 @@ export default function Layout({ children }) {
return (
<div className="">
<div className="flex flex-col">
<div className="flex flex-row min-h-screen w-screen">
<div className="flex flex-col ">
<div className="flex flex-row min-h-screen w-screen pr-8">
<ToastContainer position="top-center" style={{ zIndex: 9999 }} />
<Sidebar isSidebarOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
<main className={`flex-1 transition-all duration-300 ${marginLeftClass}`}>
<div className="p-4 mx-auto pr-8 pl-0">
<div className="">
{children}
</div>
</main>

View File

@ -6,6 +6,7 @@ import toast from "react-hot-toast";
import axiosInstance from '../../src/axiosSecure';
import ProtectedRoute, { serverSideAuth } from "../../components/protectedRoute";
import ConfirmationModal from "../../components/ConfirmationModal";
//add months to date. works with negative numbers and numbers > 12
export function addMonths(numOfMonths, date) {
@ -41,6 +42,7 @@ export function IsDateInXMonths(date, monthsFrom, monthsTo) {
export default function PublisherCard({ publisher }) {
const [isCardVisible, setIsCardVisible] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false);
const handleDelete = async (id) => {
try {
@ -74,7 +76,7 @@ export default function PublisherCard({ publisher }) {
return isCardVisible ? (
// className="block p-6 max-w-sm bg-white rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3"
<div id={`publisher-card-${publisher.id}`} className={`relative block p-6 max-w-sm rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3
<div id={`publisher-card-${publisher.id}`} className={`relative block p-6 max-w-sm rounded-lg border border-gray-200 shadow-md z-50 hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3
${!publisher.isActive ? "opacity-50 bg-gray-200 border-gray-300 text-gray-400" : (publisher.isImported ? "bg-orange-50" : (publisher.isTrained ? "bg-white" : "bg-red-50"))}`}
>
<a
@ -95,7 +97,7 @@ export default function PublisherCard({ publisher }) {
</div>
</a>
<div className="absolute bottom-2 right-2">
<button onClick={() => handleDelete(publisher.id)} aria-label="Изтрий Publisher">
<button onClick={() => { setIsModalOpen(true) }} aria-label="Изтрий Publisher">
<svg className="w-5 h-6 text-red-500 hover:text-red-700" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M10 11V17" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14 11V17" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
@ -107,6 +109,12 @@ export default function PublisherCard({ publisher }) {
<path fillRule="evenodd" d="M4.293 4.293A1 1 0 015.707 3.707L10 8l4.293-4.293a1 1 0 111.414 1.414L11.414 9l4.293 4.293a1 1 0 01-1.414 1.414L10 10.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 9 4.293 4.707a1 1 0 010-1.414z" clipRule="evenodd" /> */}
</svg>
</button>
<ConfirmationModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onConfirm={() => handleDelete(publisher.id)}
message="Сигурни ли сте, че искате да изтриете този профил? Това действие не може да бъде отменено."
/>
<ProtectedRoute>
<button onClick={() => handleLoginAs(publisher.id)}>Login as</button>

View File

@ -7,13 +7,13 @@ import axiosInstance from '../../src/axiosSecure';
//import { getDate } from "date-fns";
import PwaManager from "../PwaManager";
import common from "../../src/helpers/common";
import ProtectedRoute from '../../components/protectedRoute';
import { monthNamesBG, GetTimeFormat, GetDateFormat } from "../../src/helpers/const"
import PublisherSearchBox from './PublisherSearchBox';
import AvailabilityList from "../availability/AvailabilityList";
import ShiftsList from "../publisher/ShiftsList.tsx";
import common from "../../src/helpers/common";
import ProtectedRoute from '../../components/protectedRoute';
import ConfirmationModal from "../ConfirmationModal";
import { UserRole } from "@prisma/client";
// import { Tabs, List } from 'tw-elements'
@ -66,6 +66,8 @@ export default function PublisherForm({ item, me }) {
}
const [helpers, setHelper] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const fetchModules = async () => {
const h = (await import("../../src/helpers/const.js")).default;
//console.log("fetchModules: " + JSON.stringify(h));
@ -312,9 +314,17 @@ export default function PublisherForm({ item, me }) {
<div className="panel-actions">
<Link href={urls.indexUrl} className="action-button"> Отмени </Link>
{/* delete */}
<button className="button bg-red-500 hover:bg-red-700 focus:outline-none focus:shadow-outline" type="button" onClick={handleDelete}>
<button className="button bg-red-500 hover:bg-red-700 focus:outline-none focus:shadow-outline" type="button" onClick={() => setIsModalOpen(true)}>
Изтрий
</button>
<ConfirmationModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onConfirm={handleDelete}
message="Сигурни ли сте, че искате да изтриете този профил? Това действие не може да бъде отменено."
/>
{/* save */}
<button className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline" type="submit">
{router.query?.id ? "Update" : "Create"}
</button>

View File

@ -46,6 +46,7 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
const initialDate = getFormattedDate(new Date());
const { data: session, status } = useSession()
const [publisherId, setPublisher] = useState(null);
const [allDay, setAllDay] = useState(false);
useEffect(() => {
if (session) {
setPublisher(session.user.id);
@ -88,7 +89,11 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
const handleSubmit = async (e) => {
e.preventDefault();
item.publisher = { connect: { id: publisherId } };
item.shift = { connect: { id: parseInt(item.shiftId) } };
if (allDay) {
delete item.shift;
} else {
item.shift = { connect: { id: parseInt(item.shiftId) } };
}
item.date = new Date(item.date);
item.type = ReportType.Report;
delete item.publisherId;
@ -117,6 +122,15 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
}
}
const handleCheckboxChange = (event) => {
setAllDay(event.target.checked);
// Set shiftId to null if the checkbox is checked
if (event.target.checked) {
handleChange({ target: { name: 'shiftId', value: null } });
}
};
return (
<div className="w-full max-w-md mx-auto">
{/* <iframe src="https://docs.google.com/forms/d/e/1FAIpQLSdjbqgQEGY5-fA4A0B4cXjKRQVRWk5_-uoHVIAwdMcZ5bB7Zg/viewform?embedded=true" width="640" height="717" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe> */}
@ -134,7 +148,8 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="shiftId">
Смяна
</label>
<select className="textbox form-select px-4 py-2 rounded"
<select className={`textbox form-select px-4 py-2 rounded ${allDay ? 'bg-gray-200' : 'bg-white'}`}
disabled={allDay}
id="shiftId" name="shiftId" onChange={handleChange} value={item.shiftId} autoComplete="off" >
{shifts.map((shift) => (
<option key={shift.id} value={shift.id}>
@ -142,6 +157,17 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
</option>
))}
</select>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
<input
type="checkbox"
checked={allDay}
onChange={handleCheckboxChange}
className="mr-2 leading-tight"
/>
за целия ден
</label>
</div>
</div>
<div className="mb-4">

112
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "pwwa",
"version": "1.1.2",
"version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pwwa",
"version": "1.1.2",
"version": "1.2.0",
"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.12.1",
"@prisma/client": "^5.13.0",
"@react-pdf/renderer": "^3.3.8",
"@tailwindcss/forms": "^0.5.7",
"@types/multer": "^1.4.11",
@ -85,7 +85,8 @@
"uuid": "^9.0.1",
"web-push": "^3.6.7",
"webpack-bundle-analyzer": "^4.10.1",
"winston": "^3.11.0",
"winston": "^3.13.0",
"winston-daily-rotate-file": "^5.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
"xlsx-style": "^0.8.13",
"xml-js": "^1.6.11",
@ -94,7 +95,7 @@
"devDependencies": {
"cross-env": "^7.0.3",
"depcheck": "^1.4.7",
"prisma": "^5.12.1"
"prisma": "^5.13.0"
}
},
"node_modules/@alloc/quick-lru": {
@ -3760,9 +3761,9 @@
}
},
"node_modules/@prisma/client": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.12.1.tgz",
"integrity": "sha512-6/JnizEdlSBxDIdiLbrBdMW5NqDxOmhXAJaNXiPpgzAPr/nLZResT6MMpbOHLo5yAbQ1Vv5UU8PTPRzb0WIxdA==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.13.0.tgz",
"integrity": "sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg==",
"hasInstallScript": true,
"engines": {
"node": ">=16.13"
@ -3777,39 +3778,39 @@
}
},
"node_modules/@prisma/debug": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.12.1.tgz",
"integrity": "sha512-kd/wNsR0klrv79o1ITsbWxYyh4QWuBidvxsXSParPsYSu0ircUmNk3q4ojsgNc3/81b0ozg76iastOG43tbf8A==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.13.0.tgz",
"integrity": "sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==",
"devOptional": true
},
"node_modules/@prisma/engines": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.12.1.tgz",
"integrity": "sha512-HQDdglLw2bZR/TXD2Y+YfDMvi5Q8H+acbswqOsWyq9pPjBLYJ6gzM+ptlTU/AV6tl0XSZLU1/7F4qaWa8bqpJA==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.13.0.tgz",
"integrity": "sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/debug": "5.12.1",
"@prisma/engines-version": "5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab",
"@prisma/fetch-engine": "5.12.1",
"@prisma/get-platform": "5.12.1"
"@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"
}
},
"node_modules/@prisma/engines-version": {
"version": "5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab.tgz",
"integrity": "sha512-6yvO8s80Tym61aB4QNtYZfWVmE3pwqe807jEtzm8C5VDe7nw8O1FGX3TXUaXmWV0fQTIAfRbeL2Gwrndabp/0g==",
"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==",
"devOptional": true
},
"node_modules/@prisma/fetch-engine": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.12.1.tgz",
"integrity": "sha512-qSs3KcX1HKcea1A+hlJVK/ljj0PNIUHDxAayGMvgJBqmaN32P9tCidlKz1EGv6WoRFICYnk3Dd/YFLBwnFIozA==",
"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==",
"devOptional": true,
"dependencies": {
"@prisma/debug": "5.12.1",
"@prisma/engines-version": "5.12.0-21.473ed3124229e22d881cb7addf559799debae1ab",
"@prisma/get-platform": "5.12.1"
"@prisma/debug": "5.13.0",
"@prisma/engines-version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b",
"@prisma/get-platform": "5.13.0"
}
},
"node_modules/@prisma/generator-helper": {
@ -3826,12 +3827,12 @@
"integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA=="
},
"node_modules/@prisma/get-platform": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.12.1.tgz",
"integrity": "sha512-pgIR+pSvhYHiUcqXVEZS31NrFOTENC9yFUdEAcx7cdQBoZPmHVjtjN4Ss6NzVDMYPrKJJ51U14EhEoeuBlMioQ==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.13.0.tgz",
"integrity": "sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==",
"devOptional": true,
"dependencies": {
"@prisma/debug": "5.12.1"
"@prisma/debug": "5.13.0"
}
},
"node_modules/@prisma/internals": {
@ -7624,6 +7625,14 @@
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
},
"node_modules/file-stream-rotator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
"integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==",
"dependencies": {
"moment": "^2.29.1"
}
},
"node_modules/file-type": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
@ -14352,13 +14361,13 @@
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
},
"node_modules/prisma": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.12.1.tgz",
"integrity": "sha512-SkMnb6wyIxTv9ACqiHBI2u9gD6y98qXRoCoLEnZsF6yee5Qg828G+ARrESN+lQHdw4maSZFFSBPPDpvSiVTo0Q==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.13.0.tgz",
"integrity": "sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/engines": "5.12.1"
"@prisma/engines": "5.13.0"
},
"bin": {
"prisma": "build/index.js"
@ -17763,9 +17772,9 @@
}
},
"node_modules/winston": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz",
"integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==",
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz",
"integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==",
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.2",
@ -17777,12 +17786,37 @@
"safe-stable-stringify": "^2.3.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.5.0"
"winston-transport": "^4.7.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-daily-rotate-file": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
"integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
"dependencies": {
"file-stream-rotator": "^0.6.1",
"object-hash": "^3.0.0",
"triple-beam": "^1.4.1",
"winston-transport": "^4.7.0"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"winston": "^3"
}
},
"node_modules/winston-daily-rotate-file/node_modules/object-hash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"engines": {
"node": ">= 6"
}
},
"node_modules/winston-transport": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz",

View File

@ -33,7 +33,7 @@
"@mui/material": "^5.15.10",
"@mui/x-date-pickers": "^6.19.4",
"@premieroctet/next-crud": "^3.0.0",
"@prisma/client": "^5.12.1",
"@prisma/client": "^5.13.0",
"@react-pdf/renderer": "^3.3.8",
"@tailwindcss/forms": "^0.5.7",
"@types/multer": "^1.4.11",
@ -102,7 +102,8 @@
"uuid": "^9.0.1",
"web-push": "^3.6.7",
"webpack-bundle-analyzer": "^4.10.1",
"winston": "^3.11.0",
"winston": "^3.13.0",
"winston-daily-rotate-file": "^5.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
"xlsx-style": "^0.8.13",
"xml-js": "^1.6.11",
@ -111,6 +112,6 @@
"devDependencies": {
"cross-env": "^7.0.3",
"depcheck": "^1.4.7",
"prisma": "^5.12.1"
"prisma": "^5.13.0"
}
}
}

View File

@ -145,11 +145,17 @@ export const authOptions: NextAuthOptions = {
user.id = dbUser.id;
//user.permissions = dbUser.permissions;
const session = { ...user };
await prisma.publisher.update({
where: { id: dbUser.id },
data: { lastLogin: new Date() }
});
return true; // Sign-in successful
} else {
// Optionally create a new user in your DB
// Or return false to deny access
return false;
//Let's customize the error message to give a better user experience
throw new Error(`Твоят имейл '${user.email}' не е регистриран в системата. Моля свържи се с нас за да те регистрираме ако искаш да ползваш този имейл.`);
}
} catch (e) {
console.log(e);

View File

@ -8,6 +8,8 @@ import { authOptions } from "../auth/[...nextauth]";
const common = require("../../../src/helpers/common");
import jwt from 'jsonwebtoken';
import { decode } from 'next-auth/jwt';
const logger = require('../../../src/logger');
// import { getToken } from "next-auth/jwt";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
@ -25,6 +27,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const authHeader = req.headers.authorization || '';
//console.log('authHeader', authHeader);
if (session) {
//get target table
const targetTable = req.query.nextcrud[0];
//get target action
if (req.method === 'DELETE') {
const targetId = req.query.nextcrud[1];
logger.info('[nextCrud] ' + targetTable + ': ' + targetId + 'DELETED by ' + session.user.email);
}
return nextCrudHandler(req, res);
}
else {

View File

@ -677,7 +677,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
list of publishers for the selected date with availabilities
------------------AVAILABLE PUBLISHERS LIST FOR THE SELECTED DATE0 ------------------ */}
<div className="flex flex-col items-center my-8 sticky top-0">
<div className="flex flex-col items-center my-4 sticky top-0">
<h2 className="text-lg font-semibold mb-4">Достъпни за този ден: <span className="text-blue-600">{availablePubs.length}</span></h2>
<label className="toggle pb-3">
@ -719,7 +719,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
return (
<li key={index}
className={`flex justify-between items-center p-4 rounded-lg shadow-sm mb-2
className={`flex justify-between items-center p-4 sm:py-2 rounded-lg shadow-sm mb-2
${bgAndBorderColorClass} ${selectedBorderClass} ${activeOpacityClass}`}
onDoubleClick={(handlePublisherModalOpen.bind(this, pub))}
>

View File

@ -39,6 +39,7 @@ function ContactsPage({ publishers, allPublishers }) {
<th className="border-b font-medium p-4 pl-8 pt-0 pb-3">Име</th>
<th className="border-b font-medium p-4 pt-0 pb-3">Възможности</th>
<th className="border-b font-medium p-4 pt-0 pb-3">Участия</th>
<th className="border-b font-medium p-4 pt-0 pb-3">Последно влизане</th>
</tr>
</thead>
<tbody>
@ -67,6 +68,7 @@ function ContactsPage({ publishers, allPublishers }) {
</div>
</div>
</td>
<td className="border-b p-4">{pub.lastLogin ? new Date(pub.lastLogin).toLocaleString("bg") : ""}</td>
</>
) : (
<>
@ -125,7 +127,7 @@ export const getServerSideProps = async (context) => {
const prisma = common.getPrismaClient();
const dateStr = new Date().toISOString().split('T')[0];
let publishers = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth', dateStr, false, true, true);
let publishers = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin', dateStr, false, true, true, true);
// const axios = await axiosServer(context);
// const { data: publishers } = await axios.get(`api/?action=filterPublishers&assignments=true&availabilities=true&date=${dateStr}&select=id,firstName,lastName,isActive,desiredShiftsPerMonth`);
@ -158,6 +160,7 @@ export const getServerSideProps = async (context) => {
}
}
});
publisher.lastLogin = publisher.lastLogin ? publisher.lastLogin.toISOString() : null;
//remove availabilities that isFromPreviousAssignment
publisher.availabilities = publisher.availabilities.filter(availability => !availability.isFromPreviousAssignment);
@ -175,6 +178,7 @@ export const getServerSideProps = async (context) => {
phone: true,
isActive: true,
desiredShiftsPerMonth: true,
lastLogin: true,
assignments: {
select: {
id: true,
@ -211,6 +215,7 @@ export const getServerSideProps = async (context) => {
publisher.currentMonthAssignments = countAssignments(publisher.assignments, currentMonthStart, currentMonthEnd);
publisher.previousMonthAssignments = countAssignments(publisher.assignments, previousMonthStart, previousMonthEnd);
publisher.lastLogin = publisher.lastLogin ? publisher.lastLogin.toISOString() : null;
// Convert date formats within the same iteration
convertShiftDates(publisher.assignments);
});

View File

@ -63,9 +63,9 @@ export default function IndexPage({ initialItems, initialUserId }: IProps) {
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage="">
<PublisherSearchBox selectedId={userId} infoText="" onChange={handleUserSelection} />
</ProtectedRoute>
<div className="flex flex-row md:flex-row mt-4 space-y-4 md:space-y-0 md:space-x-4 h-[calc(100vh-10rem)]">
<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">
<div className="text-center font-bold pb-3 xs:pb-1">
<PublisherInlineForm publisherId={userId} />
</div>
<AvCalendar publisherId={userId} events={events} selectedDate={new Date()} />

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `publisher` ADD COLUMN `lastLogin` DATETIME(3) NULL;

View File

@ -122,6 +122,7 @@ model Publisher {
reports Report[]
Message Message[]
EventLog EventLog[]
lastLogin DateTime?
}
model Availability {

View File

@ -228,7 +228,7 @@ async function getAvailabilities(userId) {
}
async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isWithStats = true, includeOldAvailabilities = false) {
async function filterPublishersNew(selectFields, filterDate, isExactTime = false, isForTheMonth = false, isNoEndDateFilter = false, isWithStats = true, includeOldAvailabilities = false) {
filterDate = new Date(filterDate); // Convert to date object if not already
@ -341,7 +341,7 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
{
dayOfMonth: { not: null },
startTime: { gte: monthInfo.firstMonday },
endTime: { lte: monthInfo.lastSunday }
// endTime: { lte: monthInfo.lastSunday }
},
// Check if dayOfMonth is null and match by day of week using the enum (Assigments every week)
{
@ -358,6 +358,10 @@ async function filterPublishersNew(selectFields, filterDate, isExactTime = false
]
}
};
if (!isNoEndDateFilter) { // Check if we need to apply the endTime filter
whereClause["availabilities"].some.OR[0].endTime = { lte: monthInfo.lastSunday };
}
}
console.log(`getting publishers for date: ${filterDate}, isExactTime: ${isExactTime}, isForTheMonth: ${isForTheMonth}`);

25
src/logger.js Normal file
View File

@ -0,0 +1,25 @@
const winston = require('winston');
require('winston-daily-rotate-file');
const logConfiguration = {
'transports': [
new winston.transports.DailyRotateFile({
filename: './logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '90d',
level: 'info'
})
],
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
)
};
const logger = winston.createLogger(logConfiguration);
module.exports = logger;