Merge commit '53729a5926cef1f5d155bc3aff90ccaee1b96c86'

This commit is contained in:
Dobromir Popov
2024-10-31 16:12:14 +02:00
18 changed files with 992 additions and 247 deletions

6
.gitignore vendored
View File

@ -35,7 +35,5 @@ content/output/*
public/content/output/*
public/content/output/shifts 2024.1.json
!public/content/uploads/*
shift_generate_log_*.txt
.aider.input.history
.aider.chat.history.md
.aider.tags.cache.v3/*
.aider*
/shift_generate_log_*.txt

View File

@ -39,6 +39,8 @@ if [ "$UPDATE_CODE_FROM_GIT" = "true" ]; then
rsync -av --itemize-changes \
--exclude='package.json' \
--exclude='package-lock.json' \
--exclude='/public/content/permits' \
--exclude='/public/content/uploads' \
/tmp/clone/ /app/ >> /app/logs/deploy.txt 2>&1
# Check rsync exit status

View File

@ -118,6 +118,11 @@ next start
export OPENAI_API_KEY=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 # personal
export OPENAI_API_KEY=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 # dev-bro
aider --openai-api-key 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 --no-auto-commits --
## build - prod
npx run build
npm run prod
# ----------------------------------------------update PRISMA schema/sync database ----------------------------------------------- #
# prisma migrate dev --create-only
NODE_ENV=production npx prisma migrate deploy

View File

@ -51,10 +51,15 @@ const messages = {
const AvCalendar = ({ publisherId, events, selectedDate, cartEvents, lastPublishedDate }) => {
const [editLockedBefore, setEditLockedBefore] = useState(new Date(lastPublishedDate));
const [isAdmin, setIsAdmin] = useState(false);
// const [isPowerUser, setIsPowerUser] = useState(false);
// const [currentUserId, setCurrentUserId] = useState(null);
useEffect(() => {
(async () => {
try {
setIsAdmin(await ProtectedRoute.IsInRole(UserRole.ADMIN));
// setIsPowerUser(await ProtectedRoute.IsInRole(UserRole.POWERUSER));
// // Assuming you have a way to get the current user's ID
// setCurrentUserId(await ProtectedRoute.GetCurrentUserId());
} catch (error) {
console.error("Failed to check admin role:", error);
}
@ -256,7 +261,9 @@ const AvCalendar = ({ publisherId, events, selectedDate, cartEvents, lastPublish
const enddate = common.setTimezone(end);
if (!start || !end) return;
//readonly for past dates (ToDo: if not admin)
//readonly for past dates (ToDo: if not admin or current user)
//const isCurrentUser = selectedEvents.some(event => event.userId === currentUserId);
if (!isAdmin) {
if (startdate < new Date() || end < new Date() || startdate > end) return;
//or if schedule is published (lastPublishedDate)

View File

@ -85,5 +85,10 @@ export async function serverSideAuth({ req, allowedRoles }) {
// Static method to check if the user has a specific role
ProtectedRoute.IsInRole = async (roleName) => {
const session = await getSession();
return session && session.user && session.user.role === roleName;
return (session && session.user && session.user.role === roleName) || false;
};
ProtectedRoute.GetCurrentUserId = async () => {
const session = await getSession();
return session && session.user && session.user.id;
}

View File

@ -1,5 +1,5 @@
import Link from "next/link";
import { Publisher } from "@prisma/client"
import { Publisher, UserRole } from "@prisma/client"
// import {IsDateXMonthsAgo} from "../../helpers/const"
import { useEffect, useState } from 'react'
import toast from "react-hot-toast";
@ -97,26 +97,27 @@ export default function PublisherCard({ publisher }) {
</div>
</a>
<div className="absolute bottom-2 right-2">
<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" />
<path d="M4 7H20" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6 7H12H18V18C18 19.6569 16.6569 21 15 21H9C7.34315 21 6 19.6569 6 18V7Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" ">
<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" />
<path d="M4 7H20" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6 7H12H18V18C18 19.6569 16.6569 21 15 21H9C7.34315 21 6 19.6569 6 18V7Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
{/* <path d="M8 9a1 1 0 000 2h4a1 1 0 100-2H8z" />
{/* <path d="M8 9a1 1 0 000 2h4a1 1 0 100-2H8z" />
<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>
</svg>
</button>
<ConfirmationModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onConfirm={() => handleDelete(publisher.id)}
message="Сигурни ли сте, че искате да изтриете този профил? Това действие не може да бъде отменено."
/>
</ProtectedRoute>
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]} deniedMessage=" ">
<button onClick={() => handleLoginAs(publisher.id)}>Login as</button>
</ProtectedRoute>
</div>

View File

@ -317,7 +317,7 @@ export default function PublisherForm({ item, me }) {
{/* ADMINISTRATORS ONLY */}
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" " className="">
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]} deniedMessage=" " className="">
<div className="border border-blue-500 border-solid p-2">
{/* prompt to install PWA */}
<div className="mb-4">
@ -341,12 +341,16 @@ export default function PublisherForm({ item, me }) {
</div>
<div className="mb-4">
<div className="form-check">
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" ">
<input className="checkbox disabled" type="checkbox" id="isImported" name="isImported" onChange={handleChange} checked={publisher.isImported} autoComplete="off" />
<label className="label " htmlFor="isImported">Импортиран от график</label>
</ProtectedRoute>
<input className="checkbox" type="checkbox" id="isActive" name="isActive" onChange={handleChange} checked={publisher.isActive} autoComplete="off" />
<label className="label" htmlFor="isActive">Активен</label>
<input className="checkbox" type="checkbox" id="isTrained" name="isTrained" onChange={handleChange} checked={publisher.isTrained} autoComplete="off" />
<label className="label" htmlFor="isTrained">Получил обучение</label>
<input className="checkbox disabled" type="checkbox" id="isImported" name="isImported" onChange={handleChange} checked={publisher.isImported} autoComplete="off" />
<label className="label " htmlFor="isImported">Импортиран от график</label>
</div>
</div>
<div className="mb-4">
@ -356,7 +360,9 @@ export default function PublisherForm({ item, me }) {
<option value={`${UserRole.USER}`}>Потребител</option>
<option value={`${UserRole.EXTERNAL}`}>Външен</option>
<option value={`${UserRole.POWERUSER}`}>Организатор</option>
<option value={`${UserRole.ADMIN}`}>Администратор</option>
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" ">
<option value={`${UserRole.ADMIN}`}>Администратор</option>
</ProtectedRoute>
{/* Add other roles as needed */}
</select>
</div>

View File

@ -1,17 +1,22 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import Link from 'next/link';
import common from 'src/helpers/common';
import axiosInstance from 'src/axiosSecure';
const PublisherShiftsModal = ({ publisher, _shifts, onClose, date, onAssignmentChange }) => {
const [shifts, setShifts] = React.useState([..._shifts]);
const [shifts, setShifts] = useState([..._shifts]);
const [assignments, setAssignments] = useState(publisher.assignments || []);
//Refactor ToDo: show the whole month instead of just the current week by showing the shift start time in front of the rows, and show all shifts in the month from the first to the last week in the cell where we show one shift now
const monthInfo = common.getMonthDatesInfo(new Date(date));
let monthInfo = common.getMonthDatesInfo(new Date(date));
//if date is before monthInfo.firstMonday, get the previous month indexOf
if (date < monthInfo.firstMonday) {
monthInfo = common.getMonthDatesInfo(new Date(date.getFullYear(), date.getMonth() - 1, 1));
}
const monthShifts = shifts.filter(shift => {
const shiftDate = new Date(shift.startTime);
return shiftDate > monthInfo.firstDay && shiftDate < monthInfo.lastDay;
return shiftDate > monthInfo.firstDay && shiftDate < monthInfo.lastSunday;
});
const weekShifts = monthShifts.filter(shift => {
const shiftDate = new Date(shift.startTime);
@ -38,11 +43,13 @@ const PublisherShiftsModal = ({ publisher, _shifts, onClose, date, onAssignmentC
console.log("dayShifts:", dayShifts);
const hasAssignment = (shiftId) => {
// return publisher.assignments.some(ass => ass.shift.id == shiftId);
return publisher.assignments?.some(ass => {
//console.log(`Comparing: ${ass.shift.id} to ${shiftId}: ${ass.shift.id === shiftId}`);
return ass.shift.id === shiftId;
});
return assignments.some(ass => ass.shift.id === shiftId);
// // return publisher.assignments.some(ass => ass.shift.id == shiftId);
// return publisher.assignments?.some(ass => {
// //console.log(`Comparing: ${ass.shift.id} to ${shiftId}: ${ass.shift.id === shiftId}`);
// return ass.shift.id === shiftId;
// });
};
@ -63,6 +70,61 @@ const PublisherShiftsModal = ({ publisher, _shifts, onClose, date, onAssignmentC
};
}, [onClose]); // Include onClose in the dependency array
function getColorForShift(shift) {
const assignedCount = shift.assignedCount || 0; // Assuming each shift has an assignedCount property
switch (assignedCount) {
case 0: return 'bg-blue-300';
case 1: return 'bg-green-300';
case 2: return 'bg-yellow-300';
case 3: return 'bg-orange-300';
case 4: return 'bg-red-200';
default: return 'bg-gray-300';
}
}
//ToDo: DRY - move to common
const addAssignment = async (publisher, shiftId) => {
try {
console.log(`calendar.idx: new assignment for publisher ${publisher.id} - ${publisher.firstName} ${publisher.lastName}`);
const newAssignment = {
publisher: { connect: { id: publisher.id } },
shift: { connect: { id: shiftId } },
isConfirmed: true
};
const { data } = await axiosInstance.post("/api/data/assignments", newAssignment);
// Update the 'publisher' property of the returned data with the full publisher object
data.publisher = publisher;
data.shift = shifts.find(shift => shift.id === shiftId);
publisher.assignments = [...publisher.assignments, data];
// handleAssignmentChange(publisher.id, 'add');
setAssignments(prevAssignments => [...prevAssignments, data]);
if (onAssignmentChange) { onAssignmentChange(publisher.id, 'add'); }
} catch (error) {
console.error("Error adding assignment:", error);
}
};
const removeAssignment = async (publisher, shiftId) => {
try {
const assignment = publisher.assignments.find(ass => ass.shift.id === shiftId);
console.log(`calendar.idx: remove assignment for shift ${shiftId}`);
const { data } = await axiosInstance.delete(`/api/data/assignments/${assignment.id}`);
//remove from local assignments:
publisher.assignments = publisher.assignments.filter(a => a.id !== assignment.id)
// Update local state
setAssignments(prevAssignments => prevAssignments.filter(a => a.id !== assignment.id));
//
// handleAssignmentChange(publisher.id, 'remove')
if (onAssignmentChange) {
onAssignmentChange(publisher.id, 'remove')
}
} catch (error) {
console.error("Error removing assignment:", error);
}
}
return (
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="relative bg-white p-8 rounded-lg shadow-xl max-w-xl w-full h-auto overflow-y-auto">
@ -91,24 +153,23 @@ const PublisherShiftsModal = ({ publisher, _shifts, onClose, date, onAssignmentC
>
{common.getTimeRange(shift.startTime, shift.endTime)} {shift.id}
{!assignmentExists && shift.isAvailable && (
<button onClick={() => { addAssignment(publisher, shift.id); }}
className="mt-2 bg-green-500 text-white p-1 rounded hover:bg-green-600 active:bg-green-700 focus:outline-none"
{shift.isAvailable && (
<button
onClick={() => {
if (assignmentExists) {
removeAssignment(publisher, shift.id);
} else {
addAssignment(publisher, shift.id);
}
}}
className={`mt-2 ${assignmentExists ? 'bg-red-500 hover:bg-red-600 active:bg-red-700' : 'bg-green-500 hover:bg-green-600 active:bg-green-700'} text-white p-1 rounded focus:outline-none`}
>
добави
</button>
)}
{assignmentExists && (
<button onClick={() => { removeAssignment(publisher, shift.id) }} // Implement the removeAssignment function
className="mt-2 bg-red-500 text-white p-1 rounded hover:bg-red-600 active:bg-red-700 focus:outline-none"
>
махни
{assignmentExists ? 'махни' : 'добави'}
</button>
)}
</div>
);
}
)}
})}
</div>
))}
</div>
@ -133,57 +194,10 @@ const PublisherShiftsModal = ({ publisher, _shifts, onClose, date, onAssignmentC
</div>
</div >
);
}
function getColorForShift(shift) {
const assignedCount = shift.assignedCount || 0; // Assuming each shift has an assignedCount property
switch (assignedCount) {
case 0: return 'bg-blue-300';
case 1: return 'bg-green-300';
case 2: return 'bg-yellow-300';
case 3: return 'bg-orange-300';
case 4: return 'bg-red-200';
default: return 'bg-gray-300';
}
}
//ToDo: DRY - move to common
const addAssignment = async (publisher, shiftId) => {
try {
console.log(`calendar.idx: new assignment for publisher ${publisher.id} - ${publisher.firstName} ${publisher.lastName}`);
const newAssignment = {
publisher: { connect: { id: publisher.id } },
shift: { connect: { id: shiftId } },
isConfirmed: true
};
const { data } = await axiosInstance.post("/api/data/assignments", newAssignment);
// Update the 'publisher' property of the returned data with the full publisher object
data.publisher = publisher;
data.shift = shifts.find(shift => shift.id === shiftId);
publisher.assignments = [...publisher.assignments, data];
// handleAssignmentChange(publisher.id, 'add');
if (onAssignmentChange) { onAssignmentChange(publisher.id, 'add'); }
} catch (error) {
console.error("Error adding assignment:", error);
}
};
const removeAssignment = async (publisher, shiftId) => {
try {
const assignment = publisher.assignments.find(ass => ass.shift.id === shiftId);
console.log(`calendar.idx: remove assignment for shift ${shiftId}`);
const { data } = await axiosInstance.delete(`/api/data/assignments/${assignment.id}`);
//remove from local assignments:
publisher.assignments = publisher.assignments.filter(a => a.id !== assignment.id)
//
// handleAssignmentChange(publisher.id, 'remove')
if (onAssignmentChange) {
onAssignmentChange(publisher.id, 'remove')
}
} catch (error) {
console.error("Error removing assignment:", error);
}
}
export default PublisherShiftsModal;

689
package-lock.json generated
View File

@ -100,6 +100,7 @@
"devDependencies": {
"cross-env": "^7.0.3",
"depcheck": "^1.4.7",
"eslint-plugin-no-unsanitized": "^4.1.0",
"prisma": "^5.19.1"
}
},
@ -2128,6 +2129,200 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
"integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
"dev": true,
"peer": true,
"dependencies": {
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"peer": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
"integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
"dev": true,
"peer": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/config-array": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
"integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint/object-schema": "^2.1.4",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/config-array/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true,
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@eslint/config-array/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"peer": true
},
"node_modules/@eslint/eslintrc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
"dev": true,
"peer": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"peer": true
},
"node_modules/@eslint/eslintrc/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true,
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"peer": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@eslint/eslintrc/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"peer": true
},
"node_modules/@eslint/js": {
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
"dev": true,
"peer": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/object-schema": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
"integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
"dev": true,
"peer": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
"dev": true,
"peer": true,
"dependencies": {
"levn": "^0.4.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@fast-csv/format": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz",
@ -2293,6 +2488,34 @@
"react": ">= 16"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=12.22"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/retry": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
"integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
"dev": true,
"peer": true,
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.2",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz",
@ -4914,9 +5137,9 @@
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"bin": {
"acorn": "bin/acorn"
},
@ -4933,6 +5156,16 @@
"acorn": "^8"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"peer": true,
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
@ -6616,6 +6849,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true,
"peer": true
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@ -7499,6 +7739,75 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint": {
"version": "9.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0",
"@eslint/config-array": "^0.18.0",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "9.10.0",
"@eslint/plugin-kit": "^0.1.0",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.3.0",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.0.2",
"eslint-visitor-keys": "^4.0.0",
"espree": "^10.1.0",
"esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^8.0.0",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"text-table": "^0.2.0"
},
"bin": {
"eslint": "bin/eslint.js"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
},
"peerDependencies": {
"jiti": "*"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
}
}
},
"node_modules/eslint-plugin-no-unsanitized": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.0.tgz",
"integrity": "sha512-9A8Yrbkkex8e56ivxJ2f5dXN2Js2BmKC8QgmeYZjadyiGUngo3KLXDlq6ZzalmCHyLwLF5MoQLPR6FWlNc+Qbw==",
"dev": true,
"peerDependencies": {
"eslint": "^8 || ^9"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -7512,6 +7821,178 @@
"node": ">=8.0.0"
}
},
"node_modules/eslint-visitor-keys": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"dev": true,
"peer": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"peer": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/eslint/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"peer": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/eslint/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"peer": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/eslint/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true,
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/eslint/node_modules/eslint-scope": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
"integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
"dev": true,
"peer": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/eslint/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"peer": true,
"dependencies": {
"is-glob": "^4.0.3"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/eslint/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/eslint/node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true,
"peer": true
},
"node_modules/eslint/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"peer": true
},
"node_modules/eslint/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/espree": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
"dev": true,
"peer": true,
"dependencies": {
"acorn": "^8.12.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@ -7525,6 +8006,29 @@
"node": ">=4"
}
},
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"dev": true,
"peer": true,
"dependencies": {
"estraverse": "^5.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/esquery/node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@ -7807,6 +8311,13 @@
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"peer": true
},
"node_modules/fast-write-atomic": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz",
@ -7839,6 +8350,19 @@
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz",
"integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A=="
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
"peer": true,
"dependencies": {
"flat-cache": "^4.0.0"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/file-stream-rotator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
@ -7935,7 +8459,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"optional": true,
"devOptional": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@ -7962,6 +8486,27 @@
"node": ">= 10.13.0"
}
},
"node_modules/flat-cache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
"peer": true,
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.4"
},
"engines": {
"node": ">=16"
}
},
"node_modules/flatted": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true,
"peer": true
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
@ -9157,6 +9702,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@ -9533,7 +10088,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"optional": true,
"devOptional": true,
"engines": {
"node": ">=8"
}
@ -9881,6 +10436,13 @@
"bignumber.js": "^9.0.0"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true,
"peer": true
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@ -9896,6 +10458,13 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true,
"peer": true
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@ -10052,6 +10621,16 @@
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"peer": true,
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@ -10133,6 +10712,20 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"peer": true,
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@ -10185,7 +10778,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"optional": true,
"devOptional": true,
"dependencies": {
"p-locate": "^5.0.0"
},
@ -10887,6 +11480,13 @@
"node": "^18 || >=20"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true,
"peer": true
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@ -14560,6 +15160,24 @@
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"peer": true,
"dependencies": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0",
"word-wrap": "^1.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/p-filter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz",
@ -14585,7 +15203,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"optional": true,
"devOptional": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@ -14600,7 +15218,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"optional": true,
"devOptional": true,
"dependencies": {
"p-limit": "^3.0.2"
},
@ -15072,6 +15690,16 @@
"preact": ">=10"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@ -16934,6 +17562,19 @@
"node": ">=8"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/styled-jsx": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
@ -17491,6 +18132,13 @@
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true,
"peer": true
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -17862,6 +18510,19 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"peer": true,
"dependencies": {
"prelude-ls": "^1.2.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/type-fest": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
@ -18631,6 +19292,16 @@
"node": ">=0.8"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@ -19352,7 +20023,7 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"optional": true,
"devOptional": true,
"engines": {
"node": ">=10"
},

View File

@ -118,6 +118,7 @@
"devDependencies": {
"cross-env": "^7.0.3",
"depcheck": "^1.4.7",
"eslint-plugin-no-unsanitized": "^4.1.0",
"prisma": "^5.19.1"
}
}

View File

@ -12,7 +12,7 @@ import fs from 'fs';
import path from 'path';
import { all } from "axios";
import { logger } from "src/helpers/common";
import { excel } from "src/helpers/excel";
import { ExportPublishersToExcel } from "src/helpers/excel";
/**
*
@ -435,7 +435,7 @@ export default async function handler(req, res) {
res.status(200).json(await dataHelper.getAllPublishersWithStatisticsMonth(day, noEndDate));
case "exportPublishersExcel":
try {
await excel.ExportPublishersToExcel(req, res);
await ExportPublishersToExcel(req, res);
} catch (error) {
console.error(JSON.stringify(error));
}

View File

@ -143,7 +143,7 @@ function flattenRegistry(dayKey) {
return Object.values(weekEntries).flat();
}
async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay, algType = 0) {
async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay, algType = 0, until) {
let missingPublishers = [];
let publishersWithChangedPref = [];
@ -176,7 +176,14 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
if (forDay) {
day = monthInfo.date;
endDate.setDate(monthInfo.date.getDate() + 1);
if (until === undefined) {
const oneDayInMs = 24 * 60 * 60 * 1000;
endDate = new Date(monthInfo.date.getTime() + oneDayInMs);
}
else {
endDate = new Date(until);
}
dayNr = monthInfo.date.getDate();
weekNr = common.getWeekNumber(monthInfo.date);
}
@ -184,7 +191,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
let publishersThisWeek = [];
// # # # # # # # # # # #
// 0. generate shifts and assign publishers from the previous month if still available
while (day < endDate) {
let availablePubsForTheDay = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, false);
@ -262,7 +269,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
//ToDo: check if getAvailablePublishersForShift is working correctly. It seems not to!
let availablePublishers = await getAvailablePublishersForShiftNew(shiftStart, shiftEnd, availablePubsForTheDay, publishersThisWeek);
console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + " for the day: " + availablePubsForTheDay.length);
console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + ", for the day: " + availablePubsForTheDay.length);
const createdShift = await prisma.shift.create({
data: {
@ -294,21 +301,31 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
}
day.setDate(day.getDate() + 1);
dayNr++;
if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
weekNr++;
publishersThisWeek = [];
publishers.forEach(p => p.currentWeekAssignments = 0);
if (forDay) { break; }
else {
day.setDate(day.getDate() + 1);
dayNr++;
if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
weekNr++;
publishersThisWeek = [];
publishers.forEach(p => p.currentWeekAssignments = 0);
}
}
if (forDay) break;
}
let from = monthInfo.firstMonday, to = monthInfo.lastSunday;
if (forDay) {
from = day;
to = endDate;
}
let allShifts = await prisma.shift.findMany({
where: {
startTime: {
gte: monthInfo.firstMonday,
lt: monthInfo.lastSunday,
gte: from,
lt: to,
},
},
include: {
@ -320,17 +337,23 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
},
});
let publishersToday = [];
let rankedPublishers = [];
// # # # # # # # # # # #
// Second pass - prioritize shifts with transport where it is needed
if (forDay) { }
else {
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
}
console.log("\r\n\r\n\r\n" + "# ".repeat(50));
console.log("Second pass - fix transports " + monthInfo.monthName + " " + monthInfo.year);
console.log("Second pass - fix transports " + day.toLocaleDateString());
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
while (day < endDate) {
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
@ -371,7 +394,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
else if (publishersNeeded > 0) {
console.log("shift " + shift.name + " requires transport (" + transportCapable.length + " transport capable)");
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', shift.startTime, true, false, false, true, false);
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type,familyHeadId', shift.startTime, true, false, false, true, false);
let availablePublishers = availablePubsForTheShift.filter(p => {
const hasTransportInAvailability = shift.transportIn && p.availabilities.some(avail => avail.isWithTransportIn);
@ -386,45 +409,35 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
} else if (algType == 1) {
rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr);
}
// if (rankedPublishers.length > 0) {
// const newAssignment = await prisma.assignment.create({
// data: {
// shift: {
// connect: {
// id: shift.id,
// },
// },
// publisher: {
// connect: {
// id: rankedPublishers[0].id,
// },
// },
// isWithTransport: true,
// isConfirmed: true,
// isBySystem: false,
// },
// });
// shift.assignments.push(newAssignment);
// publishersToday.push(rankedPublishers[0].id);
// updateRegistry(rankedPublishers[0].id, day, weekNr);
// }
AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
}
}
}
day.setDate(day.getDate() + 1);
if (forDay) { break; }
else {
day.setDate(day.getDate() + 1);
}
}
// Fill the rest of the shifts
// # # # # # # # # # # #
// 3. Fill the rest of the shifts
let goal = 1;
while (goal <= 4) {
console.log("\r\n\r\n\r\n" + "# ".repeat(50));
console.log("Filling shifts with " + goal + " publishers " + monthInfo.monthName + " " + monthInfo.year);
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
if (forDay) {
}
else {
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
}
console.log("\r\n\r\n" + "# ".repeat(50));
console.log("Filling shifts with " + goal + " publishers | " + day.toLocaleDateString());
while (day < endDate) {
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
@ -452,7 +465,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
console.log("Filling shift " + shift.name + " with " + goal + " publishers");
let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
if (publishersNeeded > 0 && shift.assignments.length < goal) {
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', shift.startTime, true, false, false, true, false);
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type,familyHeadId', shift.startTime, true, false, false, true, false);
let availablePublishers = await FilterInappropriatePublishers([...availablePubsForTheShift], publishersToday, shift);
if (algType == 0) {
@ -461,12 +474,17 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr);
}
AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
await AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
}
}
}
day.setDate(day.getDate() + 1);
if (forDay) { break; }
else {
day.setDate(day.getDate() + 1);
}
}
goal += 1;
}
@ -486,14 +504,18 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheShift, rankedPublishers, publishersToday, day, weekNr) {
if (rankedPublishers.length == 0) {
console.log("No available publishers for shift " + shift.name);
console.log("! ! ! No available publishers for shift " + shift.name + " ! ! !");
} else {
for (let i = 0; i < rankedPublishers.length; i++) {
let mainPublisher = rankedPublishers[i];
let familyMembers = availablePubsForTheShift.filter(p => (p.familyHeadId && p.familyHeadId === mainPublisher.familyHeadId) || (p.familyHeadId === mainPublisher.id));
let familyMembers = availablePubsForTheShift.filter(p => (p.id !== mainPublisher.id && (p.id === mainPublisher.familyHeadId) || (p.familyHeadId === mainPublisher.id)));
if (familyMembers.length > 0 && (shift.assignments.length + familyMembers.length + 1) <= event.numberOfPublishers) {
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " and family members to " + new Date(shift.startTime).getDate() + " " + shift.name);
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " and " + familyMembers.length + " available family members to " + new Date(shift.startTime).getDate() + " " + shift.name);
const hasTransportInAvailability = shift.transportIn && mainPublisher.availabilities.some(avail => avail.isWithTransportIn);
const hasTransportOutAvailability = shift.transportOut && mainPublisher.availabilities.some(avail => avail.isWithTransportOut);
const newAssignment = await prisma.assignment.create({
data: {
shift: {
@ -508,7 +530,7 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
},
isConfirmed: false,
isBySystem: false,
isWithTransport: shift.requiresTransport,
isWithTransport: (hasTransportInAvailability || hasTransportOutAvailability),
},
});
shift.assignments.push(newAssignment);
@ -540,6 +562,10 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
break;
} else if (familyMembers.length == 0) {
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " to " + new Date(shift.startTime).getDate() + " " + shift.name);
const hasTransportInAvailability = shift.transportIn && mainPublisher.availabilities.some(avail => avail.isWithTransportIn);
const hasTransportOutAvailability = shift.transportOut && mainPublisher.availabilities.some(avail => avail.isWithTransportOut);
const newAssignment = await prisma.assignment.create({
data: {
shift: {
@ -554,7 +580,7 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
},
isConfirmed: false,
isBySystem: false,
isWithTransport: shift.requiresTransport,
isWithTransport: (hasTransportInAvailability || hasTransportOutAvailability),
},
});
shift.assignments.push(newAssignment);
@ -564,17 +590,16 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
}
}
}
}
async function FilterInappropriatePublishers(availablePublishers, pubsToExclude, shift) {
async function FilterInappropriatePublishers(availablePublishers, pubsToExclude, shift, maxShifts = 0) {
//ToDo: Optimization: store number of publishers, so we process the shifts from least to most available publishers later.
let goodPublishers = availablePublishers.filter(p => {
const isNotAssigned = !shift.assignments.some(a => a.publisher?.id === p.id);
const isNotAssignedToday = !pubsToExclude.includes(p.id);
const isAssignedEnough = p.currentMonthAssignments >= p.desiredShiftsPerMonth
|| p.currentMonthAssignments >= 6; // overwrite the desiredShiftsPerMonth to max 6 shifts per month
return isNotAssigned && isNotAssignedToday && !isAssignedEnough;
|| p.currentMonthAssignments >= maxShifts; // overwrite the desiredShiftsPerMonth to max 10 shifts per month
return isNotAssigned && isNotAssignedToday && (!isAssignedEnough || maxShifts == 0);
});
return goodPublishers;
}
@ -696,10 +721,17 @@ async function RankPublishersForShiftWeighted(publishers, scheduledPubsPerDayAnd
const result = calculateScoreAndPenalties(p);
p.score = result.score;
p.penalties = result.penalties;
p.finalScore = p.score;
if (p.finalScore > 0) {
p.penalties.forEach(penalty => {
p.finalScore *= penalty.penalty;
});
}
});
// Sort publishers based on score
let ranked = publishers.sort((a, b) => b.score - a.score);
let ranked = publishers.sort((a, b) => b.finalScore - a.finalScore);
// Log the scores and penalties of the top publisher
if (ranked.length > 0) {

View File

@ -625,7 +625,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
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)}`)
await axiosInstance.put(`/api/?action=settings&key=AvailabilityBlockDate&value=${common.getISODateOnly(value)}`)
.then((response) => {
console.log("AvailabilityBlockDate set to:", response.data);
// setShifts([...shifts, response.data]);
@ -703,9 +703,11 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
<button className="block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center" onClick={() => generateShifts("genEmptyDay", false, false, true)}>
{isLoading('genEmptyDay') ? (<i className="fas fa-sync-alt fa-spin mr-2"></i>) : (<i className="fas fa-plus mr-2"></i>)}
създай празни ({value.getDate()}-ти) </button>
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={() => generateShifts("genDay", false, true, true)}>
<button className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" onClick={() => generateShifts("genDay", false, true, true, 1)}>
{isLoading('genDay') ? (<i className="fas fa-sync-alt fa-spin mr-2"></i>) : (<i className="fas fa-cogs mr-2"></i>)}
Генерирай смени ({value.getDate()}-ти) </button>
<button className="block px-4 py-2 text-sm text-red-500 hover:bg-gray-100"
onClick={() => openConfirmModal(
'Сигурни ли сте че искате да изтриете смените и назначения на този ден?',
@ -749,7 +751,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
{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={setAvailabilityBlockDate}><i className="fas fa-copy mr-2"></i> Блокирай предпочитанията до края на {selectedMonth + 1} м.</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> Блокирай предпочитанията до {value.getDate()}-ти</button>
</div>
</div>
)}

View File

@ -219,7 +219,7 @@ export default function DashboardPage({ initialItems, initialUserId, cartEvents,
<Layout>
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER, UserRole.USER, UserRole.EXTERNAL]} deniedMessage="">
<h1 className="pt-2 pb-1 text-xl font-bold text-center">Графика на {userName}</h1>
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage="">
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]} deniedMessage="">
<PublisherSearchBox selectedId={userId} infoText="" onChange={handleUserSelection} />
</ProtectedRoute>
<div className="flex flex-row md:flex-row mt-4 xs:mt-1 space-y-4 md:space-y-0 md:space-x-4 h-[calc(100vh-10rem)]">
@ -318,7 +318,7 @@ export const getServerSideProps = async (context) => {
if (blockedDate) {
blockedDate.value = new Date(blockedDate.value);
lastPublishedDate = lastPublishedDate > blockedDate.value ? lastPublishedDate : blockedDate.value;
lastPublishedDate = blockedDate.value;
}
let messages = await prisma.message.findMany({
@ -333,8 +333,9 @@ export const getServerSideProps = async (context) => {
});
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()))
let now = new Date();
return (!message.Survey.publicFrom || message.Survey.publicFrom <= common.getStartOfDay(now))
&& (!message.Survey.publicUntil || message.Survey.publicUntil >= common.getEndOfDay(now))
});
messages = common.convertDatesToISOStrings(messages);
messages = messages.map(message => {

View File

@ -49,7 +49,7 @@ const PDFViewerPage = ({ pdfFiles }) => {
return (
<Layout>
<h1 className="text-3xl font-bold p-4 pt-8">Разрешителни</h1>
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage="">
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]} deniedMessage="">
<div className="border border-blue-500 p-4 rounded shadow-md">
<div className="mb-6">
<p className="text-lg mb-2">За да качите файл кликнете на бутона по-долу и изберете файл от вашия компютър.</p>

View File

@ -17,7 +17,7 @@ const CREDENTIALS_PATH = path.join(
);
//generates iCalendar file for a single shift
GenerateICS = function (shifts) {
const GenerateICS = function (shifts) {
// https://stackoverflow.com/questions/3665115/create-a-file-in-memory-for-user-to-download-not-through-server
var content = "BEGIN:VCALENDAR\n";
@ -174,76 +174,76 @@ GenerateICS = function (shifts) {
// const TOKEN_PATH = path.join(process.cwd(), 'content/token.json');
// const CREDENTIALS_PATH = path.join(process.cwd(), 'content/client_secret_926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com.json');
createEvent = async function createEvent(id) {
// var eventTest2 = {
// 'summary': 'Google I/O 2015',
// 'location': '800 Howard St., San Francisco, CA 94103',
// 'description': 'A chance to hear more about Google\'s developer products.',
// 'start': {
// 'dateTime': '2015-05-28T09:00:00-07:00',
// 'timeZone': 'America/Los_Angeles',
// },
// 'end': {
// 'dateTime': '2015-05-28T17:00:00-07:00',
// 'timeZone': 'America/Los_Angeles',
// },
// 'recurrence': [
// 'RRULE:FREQ=DAILY;COUNT=2'
// ],
// 'attendees': [
// { 'email': '' },
// const createEvent = async function createEvent(id) {
// // var eventTest2 = {
// // 'summary': 'Google I/O 2015',
// // 'location': '800 Howard St., San Francisco, CA 94103',
// // 'description': 'A chance to hear more about Google\'s developer products.',
// // 'start': {
// // 'dateTime': '2015-05-28T09:00:00-07:00',
// // 'timeZone': 'America/Los_Angeles',
// // },
// // 'end': {
// // 'dateTime': '2015-05-28T17:00:00-07:00',
// // 'timeZone': 'America/Los_Angeles',
// // },
// // 'recurrence': [
// // 'RRULE:FREQ=DAILY;COUNT=2'
// // ],
// // 'attendees': [
// // { 'email': '' },
// ],
// 'reminders': {
// 'useDefault': false,
// 'overrides': [
// { 'method': 'email', 'minutes': 24 * 60 },
// { 'method': 'popup', 'minutes': 10 },
// ],
// },
// };
// var errors;
// var auth = await exports.authorize().then((auth) => {
// // ],
// // 'reminders': {
// // 'useDefault': false,
// // 'overrides': [
// // { 'method': 'email', 'minutes': 24 * 60 },
// // { 'method': 'popup', 'minutes': 10 },
// // ],
// // },
// // };
// // var errors;
// // var auth = await exports.authorize().then((auth) => {
// const calendar = google.calendar({ version: 'v3', auth });
// const res = calendar.events.insert({
// calendarId: 'primary',
// resource: eventTest,
// // const calendar = google.calendar({ version: 'v3', auth });
// // const res = calendar.events.insert({
// // calendarId: 'primary',
// // resource: eventTest,
// }, (err, event) => {
// if (err) {
// console.log(`There was an error creating the event: ${err}`);
// return;
// }
// console.log(`Event created: ${event.data.htmlLink}`);
// });
// }).catch(console.error).then((err) => {
// errors = err;
// });
// return errors;
// }
// // }, (err, event) => {
// // if (err) {
// // console.log(`There was an error creating the event: ${err}`);
// // return;
// // }
// // console.log(`Event created: ${event.data.htmlLink}`);
// // });
// // }).catch(console.error).then((err) => {
// // errors = err;
// // });
// // return errors;
// // }
// authorizeNew = async function authorizeNew() {
// let client = await loadSavedCredentialsIfExist();
// if (client) {
// return client;
// }
// // Set up the Google Calendar API client
// const calendar = google.calendar({ version: 'v3', auth });
// // authorizeNew = async function authorizeNew() {
// // let client = await loadSavedCredentialsIfExist();
// // if (client) {
// // return client;
// // }
// // // Set up the Google Calendar API client
// // const calendar = google.calendar({ version: 'v3', auth });
// // Load the client secrets from a JSON file
// // // Load the client secrets from a JSON file
fs.readFile("./path/to/client_secret.json", (err, content) => {
if (err) return console.error("Error loading client secret file:", err);
// fs.readFile("./path/to/client_secret.json", (err, content) => {
// if (err) return console.error("Error loading client secret file:", err);
// Authorize a client with the loaded credentials
authorizeOA(JSON.parse(content), calendar.calendarList.list);
});
if (client.credentials) {
await saveCredentials(client);
}
return client;
};
// // Authorize a client with the loaded credentials
// authorizeOA(JSON.parse(content), calendar.calendarList.list);
// });
// if (client.credentials) {
// await saveCredentials(client);
// }
// return client;
// };
// var googletoken = axiosInstance.get("/content/google_token.json");
// console.log("googletoken: " + googletoken);
@ -372,7 +372,7 @@ async function importModules() {
};
}
createEvent = async (event) => {
const createEvent = async (event) => {
const { open, getPort } = await importModules();
@ -474,7 +474,7 @@ createEvent = async (event) => {
}
};
SaveEventsInGoogleCalendar = async function SaveEventsInGoogleCalendar(events) {
const SaveEventsInGoogleCalendar = async function SaveEventsInGoogleCalendar(events) {
// Load client secrets from a local file.
try {
const content = await fs.readFile(CREDENTIALS_PATH);

View File

@ -5,7 +5,7 @@ const path = require('path');
const { MailtrapClient } = require("mailtrap");
const nodemailer = require("nodemailer");
const CON = require("./const");
const CAL = require("./calendar");
const { GenerateICS } = require("./calendar");
const common = require("./common");
const Handlebars = require('handlebars');