This commit is contained in:
Dobromir Popov
2024-10-31 13:08:29 +02:00
30 changed files with 1027 additions and 525 deletions

View File

@ -144,5 +144,8 @@
"components/x-date-pickers/locales" "components/x-date-pickers/locales"
], ],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"i18n-ally.sourceLanguage": "bg" "i18n-ally.sourceLanguage": "bg",
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
}
} }

View File

@ -9,6 +9,7 @@ services:
# - "3000:3000" # - "3000:3000"
volumes: volumes:
- /mnt/docker_volumes/pw/app/public/content/uploads/:/app/public/content/uploads - /mnt/docker_volumes/pw/app/public/content/uploads/:/app/public/content/uploads
- /mnt/docker_volumes/pw/app/logs:/app/logs
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- TZ=Europe/Sofia - TZ=Europe/Sofia
@ -19,7 +20,7 @@ services:
- GIT_USERNAME=deploy - GIT_USERNAME=deploy
- GIT_PASSWORD=L3Kr2R438u4F7 - GIT_PASSWORD=L3Kr2R438u4F7
- ADMIN_PASSWORD=changeme - ADMIN_PASSWORD=changeme
command: sh -c " cd /app && npm install && npm run prod; tail -f /dev/null" command: sh -c " cd /app && npm install && npx next build && npm run prod; tail -f /dev/null"
#command: sh -c " cd /app && tail -f /dev/null" #command: sh -c " cd /app && tail -f /dev/null"
tty: true tty: true
stdin_open: true stdin_open: true
@ -56,15 +57,15 @@ services:
networks: networks:
- infrastructure_default - infrastructure_default
command: | command: |
apk update && \ "apk update && \
apk add --no-cache mariadb-client mariadb-connector-c && \ apk add --no-cache mariadb-client mariadb-connector-c && \
echo '0 2 * * * mysqldump -h $$MYSQL_HOST -P 3306 -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql' > /etc/crontabs/root && \ echo '0 2 * * * mysqldump -h $$MYSQL_HOST -P 3306 -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql' > /etc/crontabs/root && \
echo '0 7 * * * rclone sync /backup nextcloud:/mwitnessing' >> /etc/crontabs/root && \ crond -f -d 8"
crond -f -d 8
# wget -q https://github.com/prasmussen/gdrive/releases/download/2.1.0/gdrive-linux-x64 -O /usr/bin/gdrive && \ # wget -q https://github.com/prasmussen/gdrive/releases/download/2.1.0/gdrive-linux-x64 -O /usr/bin/gdrive && \
# chmod +x /usr/bin/gdrive && \ # chmod +x /usr/bin/gdrive && \
# gdrive about --service-account /root/.gdrive_service_account.json && \ # gdrive about --service-account /root/.gdrive_service_account.json && \
# echo '0 * * * * /usr/bin/mysqldump -h $$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE | gzip > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz && gdrive upload --parent $$GOOGLE_DRIVE_FOLDER_ID --service-account /root/.gdrive_service_account.json /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz' > /etc/crontabs/root && crond -f -d 8" # echo '0 * * * * /usr/bin/mysqldump -h $$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE | gzip > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz && gdrive upload --parent $$GOOGLE_DRIVE_FOLDER_ID --service-account /root/.gdrive_service_account.json /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz' > /etc/crontabs/root && crond -f -d 8 \
# echo '0 7 * * * rclone sync /backup nextcloud:/mwitnessing --delete-excluded ' >> /etc/crontabs/root && \"
networks: networks:
infrastructure_default: infrastructure_default:
external: true external: true

View File

@ -3,18 +3,72 @@
if [ "$UPDATE_CODE_FROM_GIT" = "true" ]; then if [ "$UPDATE_CODE_FROM_GIT" = "true" ]; then
# Install necessary packages # Install necessary packages
apk add git nano rsync apk add git nano rsync
echo "Updating code from git.d-popov.com...(as '$GIT_USERNAME')" echo "Updating code from git.d-popov.com...(as '$GIT_USERNAME')" > /app/logs/deploy.txt
# Create a temporary directory for the new clone # Create a temporary directory for the new clone
rm -rf /tmp/clone rm -rf /tmp/clone
mkdir /tmp/clone mkdir /tmp/clone
mkdir -p /app/logs
# Clear previous log
echo "Starting sync process at $(date)" > /app/logs/deploy.txt
# Clone the repository # Clone the repository
git clone -b ${GIT_BRANCH:-main} --depth 1 https://$GIT_USERNAME:${GIT_PASSWORD//@/%40}@git.d-popov.com/popov/mwitnessing.git /tmp/clone || exit 1 echo "\r\n\r\n Cloning repository..." | tee -a logs/deploy.txt
git clone -b ${GIT_BRANCH:main} --depth 1 https://$GIT_USERNAME:${GIT_PASSWORD//@/%40}@git.d-popov.com/popov/mwitnessing.git /tmp/clone || exit 1
# Synchronize all files except package.json and package-lock.json to /app # Synchronize all files except package.json, package-lock.json, and the contents of /public/content
rsync -av --delete --exclude 'package.json' --exclude 'package-lock.json' /tmp/clone/ /app/ || echo "Rsync failed: Issue synchronizing files" # rsync -av --filter='P /public/content/' --exclude 'package.json' --exclude 'package-lock.json' /tmp/clone/ /app/ || echo "Rsync failed: Issue synchronizing files"
echo "\r\n\r\n Synchronizing files..."
rsync -av /tmp/clone/_deploy/entrypoint.sh /app/entrypoint.sh || echo "Rsync failed: Issue copying entrypoint.sh"
# rsync -av --update --exclude '/public/content' --exclude 'package.json' --exclude 'package-lock.json' /tmp/clone/ /app/ || echo "Rsync failed: Issue synchronizing files" | tee -a /app/logs/deploy.txt
########################################################################################
if [ -d "/app/public/content/permits" ]; then
mv /app/public/content/permits /tmp/content/permits
echo "Permits folder backed up successfully." | tee -a /app/logs/deploy.txt
else
echo "Permits folder not found, skipping backup." | tee -a /app/logs/deploy.txt
fi
# Run rsync with verbose output and itemize-changes
echo "Running rsync..." | tee -a /app/logs/deploy.txt
rsync -av --itemize-changes \
--exclude='package.json' \
--exclude='package-lock.json' \
/tmp/clone/ /app/ >> /app/logs/deploy.txt 2>&1
# Check rsync exit status
if [ $? -ne 0 ]; then
echo "Rsync failed: Issue synchronizing files" | tee -a /app/logs/deploy.txt
cat /app/logs/deploy.txt # Display the log contents
else
echo "Rsync completed successfully" | tee -a /app/logs/deploy.txt
echo "Last few lines of rsync log:" | tee -a /app/logs/deploy.txt
tail -n 20 /app/logs/deploy.txt # Display the last 20 lines of the log
fi
# Restore permits folder
echo "Restoring permits folder..." | tee -a /app/logs/deploy.txt
if [ -d "/tmp/content/permits" ]; then
# Ensure the destination directory exists
mkdir -p /app/public/content
mv /tmp/content/permits /app/public/content/permits
echo "Permits folder restored successfully." | tee -a /app/logs/deploy.txt
else
echo "No permits folder to restore." | tee -a /app/logs/deploy.txt
fi
# Check contents after restoration
echo "Contents of /app/public/content after restoration:" >> /app/logs/deploy.txt
ls -la /app/public/content >> /app/logs/deploy.txt 2>&1
########################################################################################
echo "\r\n\r\n Checking for changes in package files..."
# Determine if package.json or package-lock.json has changed # Determine if package.json or package-lock.json has changed
PACKAGE_CHANGE=0 PACKAGE_CHANGE=0
if ! cmp -s /tmp/clone/package.json /app/package.json || ! cmp -s /tmp/clone/package-lock.json /app/package-lock.json; then if ! cmp -s /tmp/clone/package.json /app/package.json || ! cmp -s /tmp/clone/package-lock.json /app/package-lock.json; then
@ -37,7 +91,7 @@ if [ "$UPDATE_CODE_FROM_GIT" = "true" ]; then
npx next build npx next build
# Clean up # Clean up
rm -rf /tmp/clone # rm -rf /tmp/clone
echo "Update process completed." echo "Update process completed."
fi fi

View File

@ -0,0 +1,43 @@
# Check if the environment variable to update code from git is set to true
if [ "$UPDATE_CODE_FROM_GIT" = "true" ]; then
# Install necessary packages
apk add git nano rsync
echo "Updating code from git.d-popov.com...(as '$GIT_USERNAME')"
# Remove the previous clone directory to ensure a fresh start
rm -rf /tmp/clone
mkdir /tmp/clone
# Clone the specific branch of the new repository
git clone -b ${GIT_BRANCH:-main} --depth 1 https://$GIT_USERNAME:${GIT_PASSWORD//@/%40}@git.d-popov.com/popov/mwitnessing.git /tmp/clone || exit 1
# Fetch the latest commit ID and message from the cloned repository
GIT_COMMIT_ID=$(git -C /tmp/clone rev-parse HEAD)
LAST_COMMIT_MESSAGE=$(git -C /tmp/clone log -1 --pretty=%B)
echo "Current Git Commit: $LAST_COMMIT_MESSAGE: $GIT_COMMIT_ID"
export GIT_COMMIT_ID
# Use rsync to synchronize the files to /app, including deletion of files not in the source
rsync -av --delete --exclude '/public/content' /tmp/clone/ /app/ || echo "Rsync failed: Issue synchronizing files"
# Copy .env files
rsync -av /tmp/clone/.env* /app/ || echo "Rsync failed: Issue copying .env files"
# Copy the entrypoint.sh if exists in the new structure
[ -f /tmp/clone/entrypoint.sh ] && rsync -av /tmp/clone/entrypoint.sh /app/entrypoint.sh || echo "Rsync failed: Issue copying entrypoint.sh"
chmod +x /app/entrypoint.sh
# Clean up the temporary clone directory
rm -rf /tmp/clone
cd /app
echo "Installing packages in /app"
npm install --no-audit --no-fund --no-optional --omit=optional
yes | npx prisma generate
# Uncomment the next line if database migrations are necessary
# npx prisma migrate deploy
echo "Done cloning. Current Git Commit ID: $GIT_COMMIT_ID"
# Uncomment the following lines for production deployment
# npx next build
# npx next start
fi
echo "Running the main process"
exec "$@"

View File

@ -0,0 +1,19 @@
FROM node:current-alpine
# Install git and curl
RUN apk add --no-cache git curl
# Set environment variables for repo and branch
# These will be overridden by docker-compose.yml
ENV REPO=""
ENV BRANCH=""
# Create a directory for the app
WORKDIR /app
# Download the entrypoint script
CMD git clone --depth 1 --branch $BRANCH $REPO /tmp/repo && \
cp /tmp/repo/_deploy/entrypoint.sh /app/entrypoint.sh && \
chmod +x /app/entrypoint.sh && \
rm -rf /tmp/repo && \
/app/entrypoint.sh

View File

@ -262,4 +262,12 @@ in schedule admin - if a publisher is always pair & family is not in the shift -
[] allow blocking of inputs (different from publishing) TODO: fix to keep previous occurances when repeating evert week [] allow blocking of inputs (different from publishing) TODO: fix to keep previous occurances when repeating evert week
[] user - add createdAt field [] user - add createdAt field
[] FIX insecure logins [x] FIX insecure logins
[] nove push to form, - reorganize pWAManager to have session, role, subscriptions, etc...
[] add shift name in calendar/ show in schedule if no assignments.
[] show unpublished schedule if admin

View File

@ -25,8 +25,8 @@ apt install nodejs -y
##### ----------------- compose/deploy ----------------- ### ##### ----------------- compose/deploy ----------------- ###
# install docker if inside docker (vscode-server)# apt-get update && apt-get install -y docker.io # install docker if inside docker (vscode-server)# apt-get update && apt-get install -y docker.io
# .10 > /mnt/apps/DEV/SSS/next-cart-app/next-cart-app/ # !!! .10 > /mnt/apps/DEV/SSS/next-cart-app/next-cart-app/
#.11 > cd /mnt/storage/DEV/workspace/repos/git.d-popov.com/next-cart-app/next-cart-app # !!! .11 > cd /mnt/storage/DEV/workspace/repos/git.d-popov.com/next-cart-app/next-cart-app
# using dockerfile and image: # using dockerfile and image:
docker build -t jwpw:latest -f _deploy/prod.Dockerfile . docker build -t jwpw:latest -f _deploy/prod.Dockerfile .
@ -46,9 +46,9 @@ docker push docker.d-popov.com/jwpw:test
--LATEST/ --LATEST/
cd /mnt/storage/DEV/workspace/repos/git.d-popov.com/mwhitnessing cd /mnt/storage/DEV/workspace/repos/git.d-popov.com/mwhitnessing
docker build -t docker.d-popov.com/jwpw:latest -f _deploy/prod.Dockerfile . docker build -t docker.d-popov.com/jwpw:latest -f _deploy/prod.Dockerfile .
docker tag docker.d-popov.com/jwpw:latest docker.d-popov.com/jwpw:0.9.95 docker tag docker.d-popov.com/jwpw:latest docker.d-popov.com/jwpw:1.3.5
docker push docker.d-popov.com/jwpw:latest docker push docker.d-popov.com/jwpw:latest
docker push docker.d-popov.com/jwpw:0.9.95 docker push docker.d-popov.com/jwpw:1.3.5
#--- #---
@ -221,7 +221,8 @@ curl https://gist.githubusercontent.com/balazsorban44/09613175e7b37ec03f676dcefb
################### sync folders ################### sync folders
# nc: WebDAV # nc: WebDAV
apk add rclone apk add rclone
rclone config rclone config
/52 https://cloud.d-popov.com sync syncsyncsyncsyncsyncsyncsync
rclone sync /path/to/local/folder yourRemoteName:target-folder rclone sync /path/to/local/folder yourRemoteName:target-folder
# nc # nc
sudo add-apt-repository ppa:nextcloud-devs/client sudo add-apt-repository ppa:nextcloud-devs/client
@ -236,9 +237,11 @@ rclone config
rclone lsd nextcloud: # {nc=remotename} rclone lsd nextcloud: # {nc=remotename}
rclone sync /path/to/local/folder gdrive:target-folder rclone sync /path/to/local/folder gdrive:target-folder
rclone sync /backup nextcloud:/mwitnessing [--dry-run] [--progress] rclone sync /backup nextcloud:/mwitnessing [--dry-run] [--progress]
rclone sync /backup nextcloud:/mwitnessing --dry-run --progress # pw-mariadb_backup-1
rclone sync /backup nextcloud:/mwitnessing --dry-run --progress --delete-excluded
rclone sync /mnt/docker_volumes/pw/data/backup nc:/mwitnessing --dry-run --progress
crontab -e crontab -e
0 7 * * * rclone sync /backup nextcloud:/mwitnessing 0 7 * * * rclone sync /backup nextcloud:/mwitnessing --delete-excluded

View File

@ -27,7 +27,7 @@ class ErrorBoundary extends React.Component {
render() { render() {
if (this.state.hasError) { if (this.state.hasError) {
// Render any custom fallback UI // Render any custom fallback UI
return <h1>Нещо се обърка при изтриването. Моля, опитай отново и се свържете с нас ако проблема продължи. </h1>; return <h1>Нещо се обърка. Моля, опитай отново и се свържете с нас ако проблема продължи. </h1>;
} }
return this.props.children; return this.props.children;

View File

@ -6,7 +6,7 @@ import e from 'express';
import ProtectedRoute from './protectedRoute'; import ProtectedRoute from './protectedRoute';
import { UserRole } from '@prisma/client'; import { UserRole } from '@prisma/client';
function PwaManager({ subs }) { function PwaManager({ userId, subs }) {
//ToDo: for iOS, try to use apn? https://github.com/node-apn/node-apn/blob/master/doc/apn.markdown //ToDo: for iOS, try to use apn? https://github.com/node-apn/node-apn/blob/master/doc/apn.markdown
const isSupported = () => const isSupported = () =>
'Notification' in window && 'Notification' in window &&
@ -271,6 +271,37 @@ function PwaManager({ subs }) {
{ action: 'close', title: 'Затвори', icon: '❌' }] { action: 'close', title: 'Затвори', icon: '❌' }]
}) })
}); });
/*
await fetch('/api/notify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: pub.id,
message: "Тестово съобщение",
title: "Това е тестово съобщение от https://sofia.mwitnessing.com",
actions: [
{ action: 'OK', title: 'OK', icon: '✅' },
{ action: 'close', title: 'Затвори', icon: '❌' }
]
// actions: [
// {
// title: 'Open URL',
// action: 'open_url',
// icon: '/images/open-url.png'
// },
// {
// title: 'Dismiss',
// action: 'dismiss',
// icon: '/images/dismiss.png'
// }
// ]
})
})
*/
}; };
// async function sendTestReminder(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> { // async function sendTestReminder(event: MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> {
@ -382,7 +413,7 @@ function PwaManager({ subs }) {
> >
Тестово уведомление Тестово уведомление
</button> </button>
</div> </div >
{isAdmin && {isAdmin &&
<div> <div>
{/* <button {/* <button
@ -403,28 +434,31 @@ function PwaManager({ subs }) {
</button> */} </button> */}
</div> </div>
} }
{notificationPermission !== "granted" && ( {
<button notificationPermission !== "granted" && (
onClick={togglePushNotifications} <button
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${notificationPermission === "denied" ? 'bg-red-500 hover:bg-red-700 text-white' : 'bg-green-500 hover:bg-green-700 text-white' onClick={togglePushNotifications}
}`} className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${notificationPermission === "denied" ? 'bg-red-500 hover:bg-red-700 text-white' : 'bg-green-500 hover:bg-green-700 text-white'
> }`}
{notificationPermission === "denied" ? 'Notifications Denied!' : 'Enable Notifications'} >
</button> {notificationPermission === "denied" ? 'Notifications Denied!' : 'Enable Notifications'}
)} </button>
)
}
{isAdmin && <div> {
<div> isAdmin && <div>
<a href="https://t.me/mwhitnessing_bot" className="inline-flex items-center ml-4" target="_blank"> <div>
<img src="/content/icons/telegram-svgrepo-com.svg" alt="Телеграм" width="32" height="32" className="align-middle" /> <a href="https://t.me/mwhitnessing_bot" className="inline-flex items-center ml-4" target="_blank">
<span className="align-middle">Телеграм</span> <img src="/content/icons/telegram-svgrepo-com.svg" alt="Телеграм" width="32" height="32" className="align-middle" />
</a> <span className="align-middle">Телеграм</span>
</a>
<a href="/api/auth/apple-signin" className="inline-flex items-center ml-4 bg-gray-100 button" target="_blank"> <a href="/api/auth/apple-signin" className="inline-flex items-center ml-4 bg-gray-100 button" target="_blank">
<span className="align-middle">Apple sign-in</span> <span className="align-middle">Apple sign-in</span>
</a> </a>
</div>
</div> </div>
</div>
} }
</> </>
); );

View File

@ -22,7 +22,7 @@ const fetchConfig = async () => {
return config.default; return config.default;
}; };
export default function AvailabilityForm({ publisherId, existingItems, inline, onDone, date, cartEvent, datePicker = false }) { export default function AvailabilityForm({ publisherId, existingItems, inline, onDone, date, cartEvent, datePicker = false, lockedBeforeDate }) {
const router = useRouter(); const router = useRouter();
const urls = { const urls = {
@ -31,14 +31,15 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
}; };
const id = parseInt(router.query.id); const id = parseInt(router.query.id);
//coalsce existingItems to empty array
existingItems = existingItems || []; const originalAvailabilities = existingItems || [];
const [editMode, setEditMode] = useState(existingItems.length > 0); const [editMode, setEditMode] = useState(existingItems.length > 0);
const [publisher, setPublisher] = useState({ id: publisherId }); const [publisher, setPublisher] = useState({ id: publisherId });
const [day, setDay] = useState(new Date(date)); const [day, setDay] = useState(new Date(date));
const [canUpdate, setCanUpdate] = useState(true); const [canUpdate, setCanUpdate] = useState(true);
const [timeSlots, setTimeSlots] = useState([]); const [timeSlots, setTimeSlots] = useState([]);
const [availabilities, setAvailabilities] = useState(existingItems && existingItems.length > 0 ? existingItems : [{ const [availabilities, setAvailabilities] = useState(existingItems && existingItems.length > 0 ? existingItems : [{
publisherId: publisher.id, publisherId: publisher.id,
@ -143,6 +144,35 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
setAvailabilities(avs); setAvailabilities(avs);
} }
// Handle repetition logic
// const parentAvailabilityId = avs[0].id;
// originalAvailabilities.forEach(async av => {
// if (av.repeatWeekly && av.startTime < lockedBeforeDate) {
// const newDate = av.startTime;
// while (newDate < lockedBeforeDate) {
// const newAvailability = {
// ...av,
// startTime: newDate,
// parentAvailability: { connect: { id: parentAvailabilityId } },
// publisher: { connect: { id: publisher.id } },
// dateOfEntry: new Date(),
// type: "OneTime"
// };
// delete newAvailability.id;
// delete newAvailability.title;
// delete newAvailability.date;
// delete newAvailability.publisherId
// await axiosInstance.post(urls.apiUrl, newAvailability);
// newDate.setDate(newDate.getDate() + 7); // Repeat weekly
// }
// }
// console.log("Updated availability: ", av)
// }
// );
handleCompletion({ updated: true }); handleCompletion({ updated: true });
} catch (error) { } catch (error) {
alert("Нещо се обърка. Моля, опитайте отново по-късно."); alert("Нещо се обърка. Моля, опитайте отново по-късно.");
@ -220,9 +250,9 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
availability.dateOfEntry = new Date(); availability.dateOfEntry = new Date();
} }
function createAvailabilityFromGroup(group) { function createAvailabilityFromGroup(group, publisherId) {
let availability = { let availability = {
publisherId: publisher.id, publisherId: publisherId,
dayofweek: common.getDayOfWeekNameEnEnumForDate(day), dayofweek: common.getDayOfWeekNameEnEnumForDate(day),
}; };

View File

@ -62,6 +62,28 @@ const AvCalendar = ({ publisherId, events, selectedDate, cartEvents, lastPublish
}, []); }, []);
//const isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN); //const isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN);
//block dates between 1 and 18 august 2024
const blockedDates = [
new Date(2024, 7, 1),
new Date(2024, 7, 2),
new Date(2024, 7, 3),
new Date(2024, 7, 4),
new Date(2024, 7, 5),
new Date(2024, 7, 6),
new Date(2024, 7, 7),
new Date(2024, 7, 8),
new Date(2024, 7, 9),
new Date(2024, 7, 10),
new Date(2024, 7, 11),
new Date(2024, 7, 12),
new Date(2024, 7, 13),
new Date(2024, 7, 14),
new Date(2024, 7, 15),
new Date(2024, 7, 16),
new Date(2024, 7, 17),
new Date(2024, 7, 18),
];
const [date, setDate] = useState(new Date()); const [date, setDate] = useState(new Date());
//ToDo: see if we can optimize this //ToDo: see if we can optimize this
const [evts, setEvents] = useState(events); // Existing events const [evts, setEvents] = useState(events); // Existing events
@ -243,6 +265,11 @@ const AvCalendar = ({ publisherId, events, selectedDate, cartEvents, lastPublish
return; return;
} }
if (blockedDates[0] <= startdate && startdate <= blockedDates[blockedDates.length - 1]) {
toast.error(`Не можете да въвеждате предпочитания за ${common.getDateFormattedShort(startdate)}`, { autoClose: 5000 });
return;
}
} }
// Check if start and end are on the same day // Check if start and end are on the same day
if (startdate.toDateString() !== enddate.toDateString()) { if (startdate.toDateString() !== enddate.toDateString()) {
@ -539,6 +566,27 @@ const AvCalendar = ({ publisherId, events, selectedDate, cartEvents, lastPublish
}, },
// ... other custom components // ... other custom components
}} }}
dayPropGetter={(date) => {
// Highlight the current day
// if (date.toDateString() === new Date().toDateString()) {
// return {
// style: {
// // white-500 from Tailwind CSS
// backgroundColor: '#f9fafb',
// color: 'white'
// }
// };
// }
if (blockedDates[0] <= date && date <= blockedDates[blockedDates.length - 1]) {
return {
style: {
// red-100 from Tailwind CSS
backgroundColor: '#fee2e2',
color: 'white'
}
};
}
}}
eventPropGetter={(eventStyleGetter)} eventPropGetter={(eventStyleGetter)}
date={date} date={date}
showAllEvents={true} showAllEvents={true}
@ -556,6 +604,7 @@ const AvCalendar = ({ publisherId, events, selectedDate, cartEvents, lastPublish
onDone={handleDialogClose} onDone={handleDialogClose}
inline={true} inline={true}
cartEvent={cartEvent} cartEvent={cartEvent}
lockedBeforeDate={editLockedBefore}
// Pass other props as needed // Pass other props as needed
/> />
</div> </div>

View File

@ -303,7 +303,8 @@ export default function PublisherForm({ item, me }) {
{/* In-App notifications group */} {/* In-App notifications group */}
<div className="mb-4"> <div className="mb-4">
<h3 className="text-md font-semibold mb-2">Известия в приложението</h3> <h3 className="text-md font-semibold mb-2">Известия в приложението</h3>
<PwaManager /> <PwaManager userId={publisher.userId || session.user.id} />
</div> </div>
</fieldset> </fieldset>
</div> </div>

View File

@ -0,0 +1,189 @@
import React, { useEffect } 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]);
//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));
const monthShifts = shifts.filter(shift => {
const shiftDate = new Date(shift.startTime);
return shiftDate > monthInfo.firstDay && shiftDate < monthInfo.lastDay;
});
const weekShifts = monthShifts.filter(shift => {
const shiftDate = new Date(shift.startTime);
return common.getStartOfWeek(date) <= shiftDate && shiftDate <= common.getEndOfWeek(date);
});
const dayShifts = weekShifts.map(shift => {
const isAvailable = publisher?.availabilities?.some(avail =>
avail.startTime <= shift.startTime && avail.endTime >= shift.endTime
);
let color = isAvailable ? getColorForShift(shift) : 'bg-gray-300';
if (shift.isFromPreviousMonth) {
color += ' border-l-4 border-orange-500 ';
}
if (shift.isFromPreviousAssignment) {
color += ' border-l-4 border-red-500 ';
}
return { ...shift, isAvailable, color };
}).reduce((acc, shift) => {
const dayIndex = new Date(shift.startTime).getDay();
acc[dayIndex] = acc[dayIndex] || [];
acc[dayIndex].push(shift);
return acc;
}, {});
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;
});
};
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
console.log('ESC: closing modal.');
onClose(); // Call the onClose function when ESC key is pressed
}
};
// Add event listener
window.addEventListener('keydown', handleKeyDown);
// Remove event listener on cleanup
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [onClose]); // Include onClose in the dependency array
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">
<h2 className="text-xl font-semibold mb-4">График на <span title={publisher.email} className='publisher'>
<strong>{publisher.firstName} {publisher.lastName}</strong>
<span className="publisher-tooltip" onClick={common.copyToClipboard}>{publisher.email}</span>
</span> тази седмица:</h2>
{/* ... Display shifts in a calendar-like UI ... */}
<div className="grid grid-cols-6 gap-4 mb-4">
{Object.entries(dayShifts).map(([dayIndex, shiftsForDay]) => (
<div key={dayIndex} className="flex flex-col space-y-2 justify-end">
{/* Day header */}
<div className="text-center font-medium">{new Date(shiftsForDay[0].startTime).getDate()}-ти</div>
{shiftsForDay.map((shift, index) => {
const assignmentExists = hasAssignment(shift.id);
const availability = publisher.availabilities.find(avail =>
avail.startTime <= shift.startTime && avail.endTime >= shift.endTime
);
const isFromPrevMonth = availability && availability.isFromPreviousMonth;
return (
<div
key={index}
className={`text-sm text-white p-2 rounded-md ${isFromPrevMonth ? 'border-l-6 border-black-500' : ''} ${shift.color} ${assignmentExists ? 'border-2 border-blue-500' : ""} h-24 flex flex-col justify-center`}
>
{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"
>
добави
</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"
>
махни
</button>
)}
</div>
);
}
)}
</div>
))}
</div>
{/* Close button in the top right corner */}
<button
onClick={onClose}
className="absolute top-3 right-2 p-2 px-3 bg-red-500 text-white rounded-full hover:bg-red-600 active:bg-red-700 focus:outline-none"
>
&times;
</button>
{/* <Link href={`/cart/publishers/edit/${publisher.id}`}
className="mt-2 bg-blue-500 text-white p-1 rounded hover:bg-blue-600 active:bg-blue-700 focus:outline-none">
<i className="fas fa-edit" />
</Link> */}
{/* Edit button in the top right corner, next to the close button */}
<Link href={`/cart/publishers/edit/${publisher.id}`} className="absolute top-3 right-12 p-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 active:bg-blue-700 focus:outline-none">
<i className="fas fa-edit" />
</Link>
</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;

View File

@ -158,7 +158,7 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
</option> </option>
))} ))}
</select> </select>
<div className="mb-4"> {/* <div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2"> <label className="block text-gray-700 text-sm font-bold mb-2">
<input <input
type="checkbox" type="checkbox"
@ -168,7 +168,7 @@ export default function ReportForm({ shiftId, existingItem, onDone }) {
/> />
за целия ден за целия ден
</label> </label>
</div> </div> */}
</div> </div>
<div className="mb-4"> <div className="mb-4">

View File

@ -62,8 +62,8 @@ const SurveyForm: React.FC<SurveyFormProps> = ({ existingItem }) => {
...existingItem, ...existingItem,
content: existingItem?.content || "Нова анкета", content: existingItem?.content || "Нова анкета",
answers: existingItem?.answers.split(",") || [], answers: existingItem?.answers.split(",") || [],
publicFrom: existingItem?.publicFrom ? dayjs(existingItem.publicFrom).toISOString() : new Date().toISOString(), publicFrom: existingItem?.publicFrom ? dayjs(existingItem.publicFrom).toISOString() : '',
publicUntil: existingItem?.publicUntil ? dayjs(existingItem.publicUntil).toISOString() : new Date().toISOString(), publicUntil: existingItem?.publicUntil ? dayjs(existingItem.publicUntil).toISOString() : null,
}); });
@ -104,7 +104,7 @@ const SurveyForm: React.FC<SurveyFormProps> = ({ existingItem }) => {
} }
else { else {
//get all publisherIds and create a message for each //get all publisherIds and create a message for each
const messages = pubs.data.map(pub => { const messages = pubs.map(pub => {
return { return {
publisherId: pub.id, publisherId: pub.id,
content: JSON.stringify({ message: item.content, options: item.answers }), content: JSON.stringify({ message: item.content, options: item.answers }),
@ -211,7 +211,7 @@ const SurveyForm: React.FC<SurveyFormProps> = ({ existingItem }) => {
}, },
body: JSON.stringify({ body: JSON.stringify({
id, id,
title: 'Нямаме отговор', title: 'Напомняне',
message: `${message}`, message: `${message}`,
}) })
}); });
@ -236,13 +236,16 @@ const SurveyForm: React.FC<SurveyFormProps> = ({ existingItem }) => {
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="date"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="date">
Видима от Видима от
</label> </label>
<DatePicker className="textbox form-input px-4 py-2 rounded" name="publicFrom" onChange={(newDate) => handleDateChange('publicFrom', newDate)} value={dayjs(item?.publicFrom)} /> <DatePicker className="textbox form-input px-4 py-2 rounded" name="publicFrom" onChange={(newDate) => handleDateChange('publicFrom', newDate)}
value={item && item.publicFrom ? dayjs(item.publicFrom) : null} />
</div> </div>
<div className="mb-4"> <div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="date"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="date">
Видима до Видима до
</label> </label>
<DatePicker className="textbox form-input px-4 py-2 rounded" name="publicUntil" onChange={(newDate) => handleDateChange('publicUntil', newDate)} value={dayjs(item?.publicUntil)} /> <DatePicker className="textbox form-input px-4 py-2 rounded" name="publicUntil" onChange={(newDate) => handleDateChange('publicUntil', newDate)}
value={item && item.publicUntil ? dayjs(item.publicUntil) : null}
/>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="content"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="content">

577
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "smws", "name": "smws",
"version": "1.2.4", "version": "1.3.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "smws", "name": "smws",
"version": "1.2.4", "version": "1.3.5",
"dependencies": { "dependencies": {
"@auth/prisma-adapter": "^1.4.0", "@auth/prisma-adapter": "^1.4.0",
"@emotion/react": "^11.11.3", "@emotion/react": "^11.11.3",
@ -16,7 +16,7 @@
"@mui/material": "^5.15.10", "@mui/material": "^5.15.10",
"@mui/x-date-pickers": "^6.19.4", "@mui/x-date-pickers": "^6.19.4",
"@premieroctet/next-crud": "^3.0.0", "@premieroctet/next-crud": "^3.0.0",
"@prisma/client": "^5.15.0", "@prisma/client": "^5.19.1",
"@react-pdf/renderer": "^3.3.8", "@react-pdf/renderer": "^3.3.8",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@types/multer": "^1.4.11", "@types/multer": "^1.4.11",
@ -93,14 +93,14 @@
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"workbox-webpack-plugin": "^7.1.0", "workbox-webpack-plugin": "^7.1.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz", "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
"xlsx-style": "^0.8.13", "xlsx-js-style": "^1.2.0",
"xml-js": "^1.6.11", "xml-js": "^1.6.11",
"xml2js": "^0.6.2" "xml2js": "^0.6.2"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"depcheck": "^1.4.7", "depcheck": "^1.4.7",
"prisma": "^5.15.0" "prisma": "^5.19.1"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@ -3239,14 +3239,14 @@
} }
}, },
"node_modules/@next/env": { "node_modules/@next/env": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.10.tgz",
"integrity": "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==" "integrity": "sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw=="
}, },
"node_modules/@next/swc-darwin-arm64": { "node_modules/@next/swc-darwin-arm64": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.10.tgz",
"integrity": "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==", "integrity": "sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3259,9 +3259,9 @@
} }
}, },
"node_modules/@next/swc-darwin-x64": { "node_modules/@next/swc-darwin-x64": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.10.tgz",
"integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", "integrity": "sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3274,9 +3274,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-gnu": { "node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.10.tgz",
"integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", "integrity": "sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3289,9 +3289,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-musl": { "node_modules/@next/swc-linux-arm64-musl": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.10.tgz",
"integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", "integrity": "sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3304,9 +3304,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-gnu": { "node_modules/@next/swc-linux-x64-gnu": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.10.tgz",
"integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", "integrity": "sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3319,9 +3319,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-musl": { "node_modules/@next/swc-linux-x64-musl": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.10.tgz",
"integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", "integrity": "sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3334,9 +3334,9 @@
} }
}, },
"node_modules/@next/swc-win32-arm64-msvc": { "node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.10.tgz",
"integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", "integrity": "sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3349,9 +3349,9 @@
} }
}, },
"node_modules/@next/swc-win32-ia32-msvc": { "node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.10.tgz",
"integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", "integrity": "sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -3364,9 +3364,9 @@
} }
}, },
"node_modules/@next/swc-win32-x64-msvc": { "node_modules/@next/swc-win32-x64-msvc": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.10.tgz",
"integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", "integrity": "sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3915,9 +3915,9 @@
} }
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "5.15.0", "version": "5.19.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.19.1.tgz",
"integrity": "sha512-wPTeTjbd2Q0abOeffN7zCDCbkp9C9cF+e9HPiI64lmpehyq2TepgXE+sY7FXr7Rhbb21prLMnhXX27/E11V09w==", "integrity": "sha512-x30GFguInsgt+4z5I4WbkZP2CGpotJMUXy+Gl/aaUjHn2o1DnLYNTA+q9XdYmAQZM8fIIkvUiA2NpgosM3fneg==",
"hasInstallScript": true, "hasInstallScript": true,
"engines": { "engines": {
"node": ">=16.13" "node": ">=16.13"
@ -3932,39 +3932,39 @@
} }
}, },
"node_modules/@prisma/debug": { "node_modules/@prisma/debug": {
"version": "5.15.0", "version": "5.19.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.19.1.tgz",
"integrity": "sha512-QpEAOjieLPc/4sMny/WrWqtpIAmBYsgqwWlWwIctqZO0AbhQ9QcT6x2Ut3ojbDo/pFRCCA1Z1+xm2MUy7fAkZA==", "integrity": "sha512-lAG6A6QnG2AskAukIEucYJZxxcSqKsMK74ZFVfCTOM/7UiyJQi48v6TQ47d6qKG3LbMslqOvnTX25dj/qvclGg==",
"devOptional": true "devOptional": true
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "5.15.0", "version": "5.19.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.19.1.tgz",
"integrity": "sha512-hXL5Sn9hh/ZpRKWiyPA5GbvF3laqBHKt6Vo70hYqqOhh5e0ZXDzHcdmxNvOefEFeqxra2DMz2hNbFoPvqrVe1w==", "integrity": "sha512-kR/PoxZDrfUmbbXqqb8SlBBgCjvGaJYMCOe189PEYzq9rKqitQ2fvT/VJ8PDSe8tTNxhc2KzsCfCAL+Iwm/7Cg==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@prisma/debug": "5.15.0", "@prisma/debug": "5.19.1",
"@prisma/engines-version": "5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022", "@prisma/engines-version": "5.19.1-2.69d742ee20b815d88e17e54db4a2a7a3b30324e3",
"@prisma/fetch-engine": "5.15.0", "@prisma/fetch-engine": "5.19.1",
"@prisma/get-platform": "5.15.0" "@prisma/get-platform": "5.19.1"
} }
}, },
"node_modules/@prisma/engines-version": { "node_modules/@prisma/engines-version": {
"version": "5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022", "version": "5.19.1-2.69d742ee20b815d88e17e54db4a2a7a3b30324e3",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.19.1-2.69d742ee20b815d88e17e54db4a2a7a3b30324e3.tgz",
"integrity": "sha512-3BEgZ41Qb4oWHz9kZNofToRvNeS4LZYaT9pienR1gWkjhky6t6K1NyeWNBkqSj2llgraUNbgMOCQPY4f7Qp5wA==", "integrity": "sha512-xR6rt+z5LnNqTP5BBc+8+ySgf4WNMimOKXRn6xfNRDSpHvbOEmd7+qAOmzCrddEc4Cp8nFC0txU14dstjH7FXA==",
"devOptional": true "devOptional": true
}, },
"node_modules/@prisma/fetch-engine": { "node_modules/@prisma/fetch-engine": {
"version": "5.15.0", "version": "5.19.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.19.1.tgz",
"integrity": "sha512-z6AY5yyXxc20Klj7wwnfGP0iIUkVKzybqapT02zLYR/nf9ynaeN8bq73WRmi1TkLYn+DJ5Qy+JGu7hBf1pE78A==", "integrity": "sha512-pCq74rtlOVJfn4pLmdJj+eI4P7w2dugOnnTXpRilP/6n5b2aZiA4ulJlE0ddCbTPkfHmOL9BfaRgA8o+1rfdHw==",
"devOptional": true, "devOptional": true,
"dependencies": { "dependencies": {
"@prisma/debug": "5.15.0", "@prisma/debug": "5.19.1",
"@prisma/engines-version": "5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022", "@prisma/engines-version": "5.19.1-2.69d742ee20b815d88e17e54db4a2a7a3b30324e3",
"@prisma/get-platform": "5.15.0" "@prisma/get-platform": "5.19.1"
} }
}, },
"node_modules/@prisma/generator-helper": { "node_modules/@prisma/generator-helper": {
@ -3981,12 +3981,12 @@
"integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA==" "integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA=="
}, },
"node_modules/@prisma/get-platform": { "node_modules/@prisma/get-platform": {
"version": "5.15.0", "version": "5.19.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.19.1.tgz",
"integrity": "sha512-1GULDkW4+/VQb73vihxCBSc4Chc2x88MA+O40tcZFjmBzG4/fF44PaXFxUqKSFltxU9L9GIMLhh0Gfkk/pUbtg==", "integrity": "sha512-sCeoJ+7yt0UjnR+AXZL7vXlg5eNxaFOwC23h0KvW1YIXUoa7+W2ZcAUhoEQBmJTW4GrFqCuZ8YSP0mkDa4k3Zg==",
"devOptional": true, "devOptional": true,
"dependencies": { "dependencies": {
"@prisma/debug": "5.15.0" "@prisma/debug": "5.19.1"
} }
}, },
"node_modules/@prisma/internals": { "node_modules/@prisma/internals": {
@ -4366,6 +4366,11 @@
"sourcemap-codec": "^1.4.8" "sourcemap-codec": "^1.4.8"
} }
}, },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
},
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.4.36", "version": "0.4.36",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.36.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.36.tgz",
@ -4443,26 +4448,6 @@
"@types/ms": "*" "@types/ms": "*"
} }
}, },
"node_modules/@types/eslint": {
"version": "8.56.7",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz",
"integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==",
"peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"peer": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@ -4939,10 +4924,10 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/acorn-import-assertions": { "node_modules/acorn-import-attributes": {
"version": "1.9.0", "version": "1.9.5",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
"peer": true, "peer": true,
"peerDependencies": { "peerDependencies": {
"acorn": "^8" "acorn": "^8"
@ -5396,11 +5381,11 @@
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.7", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.4", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@ -5617,20 +5602,20 @@
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.1", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"content-type": "~1.0.4", "content-type": "~1.0.5",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"destroy": "1.2.0", "destroy": "1.2.0",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.13.0",
"raw-body": "2.5.1", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
}, },
@ -5639,20 +5624,6 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/body-parser/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -5663,11 +5634,11 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -5878,9 +5849,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001588", "version": "1.0.30001651",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
"integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -7284,9 +7255,9 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
}, },
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "1.0.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@ -7323,9 +7294,9 @@
} }
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.16.0", "version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
"integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
@ -7697,6 +7668,14 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1" "url": "https://github.com/sindresorhus/execa?sponsor=1"
} }
}, },
"node_modules/exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
"integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/expand-tilde": { "node_modules/expand-tilde": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@ -7710,36 +7689,36 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.18.2", "version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.1", "body-parser": "1.20.3",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.5.0", "cookie": "0.6.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"etag": "~1.8.1", "etag": "~1.8.1",
"finalhandler": "1.2.0", "finalhandler": "1.3.1",
"fresh": "0.5.2", "fresh": "0.5.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"merge-descriptors": "1.0.1", "merge-descriptors": "1.0.3",
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.11.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "0.18.0", "send": "0.19.0",
"serve-static": "1.15.0", "serve-static": "1.16.2",
"setprototypeof": "1.2.0", "setprototypeof": "1.2.0",
"statuses": "2.0.1", "statuses": "2.0.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
@ -7768,32 +7747,10 @@
"resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz", "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz",
"integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==" "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ=="
}, },
"node_modules/express/node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/path-to-regexp": { "node_modules/express/node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/express/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
}, },
"node_modules/extend": { "node_modules/extend": {
"version": "3.0.2", "version": "3.0.2",
@ -7877,6 +7834,11 @@
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
}, },
"node_modules/fflate": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz",
"integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A=="
},
"node_modules/file-stream-rotator": { "node_modules/file-stream-rotator": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
@ -7921,9 +7883,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -7932,12 +7894,12 @@
} }
}, },
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "1.2.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
@ -8006,9 +7968,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.5", "version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -10643,9 +10605,12 @@
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
}, },
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
@ -10669,11 +10634,11 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.5", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": { "dependencies": {
"braces": "^3.0.2", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
@ -10945,12 +10910,12 @@
} }
}, },
"node_modules/next": { "node_modules/next": {
"version": "14.1.0", "version": "14.2.10",
"resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", "resolved": "https://registry.npmjs.org/next/-/next-14.2.10.tgz",
"integrity": "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==", "integrity": "sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==",
"dependencies": { "dependencies": {
"@next/env": "14.1.0", "@next/env": "14.2.10",
"@swc/helpers": "0.5.2", "@swc/helpers": "0.5.5",
"busboy": "1.6.0", "busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579", "caniuse-lite": "^1.0.30001579",
"graceful-fs": "^4.2.11", "graceful-fs": "^4.2.11",
@ -10964,18 +10929,19 @@
"node": ">=18.17.0" "node": ">=18.17.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@next/swc-darwin-arm64": "14.1.0", "@next/swc-darwin-arm64": "14.2.10",
"@next/swc-darwin-x64": "14.1.0", "@next/swc-darwin-x64": "14.2.10",
"@next/swc-linux-arm64-gnu": "14.1.0", "@next/swc-linux-arm64-gnu": "14.2.10",
"@next/swc-linux-arm64-musl": "14.1.0", "@next/swc-linux-arm64-musl": "14.2.10",
"@next/swc-linux-x64-gnu": "14.1.0", "@next/swc-linux-x64-gnu": "14.2.10",
"@next/swc-linux-x64-musl": "14.1.0", "@next/swc-linux-x64-musl": "14.2.10",
"@next/swc-win32-arm64-msvc": "14.1.0", "@next/swc-win32-arm64-msvc": "14.2.10",
"@next/swc-win32-ia32-msvc": "14.1.0", "@next/swc-win32-ia32-msvc": "14.2.10",
"@next/swc-win32-x64-msvc": "14.1.0" "@next/swc-win32-x64-msvc": "14.2.10"
}, },
"peerDependencies": { "peerDependencies": {
"@opentelemetry/api": "^1.1.0", "@opentelemetry/api": "^1.1.0",
"@playwright/test": "^1.41.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"sass": "^1.3.0" "sass": "^1.3.0"
@ -10984,6 +10950,9 @@
"@opentelemetry/api": { "@opentelemetry/api": {
"optional": true "optional": true
}, },
"@playwright/test": {
"optional": true
},
"sass": { "sass": {
"optional": true "optional": true
} }
@ -11025,9 +10994,9 @@
} }
}, },
"node_modules/next-auth/node_modules/jose": { "node_modules/next-auth/node_modules/jose": {
"version": "4.15.4", "version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
} }
@ -11431,10 +11400,11 @@
"integrity": "sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q==" "integrity": "sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q=="
}, },
"node_modules/next/node_modules/@swc/helpers": { "node_modules/next/node_modules/@swc/helpers": {
"version": "0.5.2", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
"integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
"dependencies": { "dependencies": {
"@swc/counter": "^0.1.3",
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
@ -14583,9 +14553,9 @@
} }
}, },
"node_modules/openid-client/node_modules/jose": { "node_modules/openid-client/node_modules/jose": {
"version": "4.15.4", "version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
} }
@ -15118,20 +15088,34 @@
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
}, },
"node_modules/printj": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
"integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==",
"bin": {
"printj": "bin/printj.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/prisma": { "node_modules/prisma": {
"version": "5.15.0", "version": "5.19.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.15.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.19.1.tgz",
"integrity": "sha512-JA81ACQSCi3a7NUOgonOIkdx8PAVkO+HbUOxmd00Yb8DgIIEpr2V9+Qe/j6MLxIgWtE/OtVQ54rVjfYRbZsCfw==", "integrity": "sha512-c5K9MiDaa+VAAyh1OiYk76PXOme9s3E992D7kvvIOhCrNsBQfy2mP2QAQtX0WNj140IgG++12kwZpYB9iIydNQ==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@prisma/engines": "5.15.0" "@prisma/engines": "5.19.1"
}, },
"bin": { "bin": {
"prisma": "build/index.js" "prisma": "build/index.js"
}, },
"engines": { "engines": {
"node": ">=16.13" "node": ">=16.13"
},
"optionalDependencies": {
"fsevents": "2.3.3"
} }
}, },
"node_modules/prisma-json-schema-generator": { "node_modules/prisma-json-schema-generator": {
@ -15248,11 +15232,11 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.11.2", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": { "dependencies": {
"side-channel": "^1.0.4" "side-channel": "^1.0.6"
}, },
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@ -15367,9 +15351,9 @@
} }
}, },
"node_modules/raw-body": { "node_modules/raw-body": {
"version": "2.5.1", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
@ -16318,9 +16302,9 @@
"dev": true "dev": true
}, },
"node_modules/send": { "node_modules/send": {
"version": "0.18.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
@ -16340,6 +16324,14 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/mime": { "node_modules/send/node_modules/mime": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@ -16365,14 +16357,14 @@
} }
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.15.0", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": { "dependencies": {
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"send": "0.18.0" "send": "0.19.0"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
@ -18386,21 +18378,20 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.91.0", "version": "5.94.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
"integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5", "@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1",
"@webassemblyjs/wasm-parser": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1",
"acorn": "^8.7.1", "acorn": "^8.7.1",
"acorn-import-assertions": "^1.9.0", "acorn-import-attributes": "^1.9.5",
"browserslist": "^4.21.10", "browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.16.0", "enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1", "es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1", "eslint-scope": "5.1.1",
"events": "^3.2.0", "events": "^3.2.0",
@ -18474,26 +18465,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/webpack-bundle-analyzer/node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/webpack-sources": { "node_modules/webpack-sources": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
@ -18644,6 +18615,22 @@
"node": ">= 12.0.0" "node": ">= 12.0.0"
} }
}, },
"node_modules/wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/wordwrap": { "node_modules/wordwrap": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@ -19092,6 +19079,26 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/x-is-array": { "node_modules/x-is-array": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/x-is-array/-/x-is-array-0.1.0.tgz", "resolved": "https://registry.npmjs.org/x-is-array/-/x-is-array-0.1.0.tgz",
@ -19114,6 +19121,88 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/xlsx-js-style": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz",
"integrity": "sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==",
"dependencies": {
"adler-32": "~1.2.0",
"cfb": "^1.1.4",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"fflate": "^0.3.8",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx-js-style/node_modules/adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz",
"integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==",
"dependencies": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
},
"bin": {
"adler32": "bin/adler32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx-js-style/node_modules/codepage": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz",
"integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==",
"dependencies": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"bin": {
"codepage": "bin/codepage.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx-js-style/node_modules/codepage/node_modules/commander": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz",
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw=="
},
"node_modules/xlsx-js-style/node_modules/commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
},
"node_modules/xlsx-js-style/node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx-js-style/node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx-style": { "node_modules/xlsx-style": {
"version": "0.8.13", "version": "0.8.13",
"resolved": "https://registry.npmjs.org/xlsx-style/-/xlsx-style-0.8.13.tgz", "resolved": "https://registry.npmjs.org/xlsx-style/-/xlsx-style-0.8.13.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "smws", "name": "smws",
"version": "1.3.0", "version": "1.3.5",
"private": true, "private": true,
"description": "SMWS | ССОМ | Специално Свидетелстване София", "description": "SMWS | ССОМ | Специално Свидетелстване София",
"repository": "http://git.d-popov.com/popov/next-cart-app.git", "repository": "http://git.d-popov.com/popov/next-cart-app.git",
@ -34,7 +34,7 @@
"@mui/material": "^5.15.10", "@mui/material": "^5.15.10",
"@mui/x-date-pickers": "^6.19.4", "@mui/x-date-pickers": "^6.19.4",
"@premieroctet/next-crud": "^3.0.0", "@premieroctet/next-crud": "^3.0.0",
"@prisma/client": "^5.15.0", "@prisma/client": "^5.19.1",
"@react-pdf/renderer": "^3.3.8", "@react-pdf/renderer": "^3.3.8",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@types/multer": "^1.4.11", "@types/multer": "^1.4.11",
@ -111,13 +111,13 @@
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"workbox-webpack-plugin": "^7.1.0", "workbox-webpack-plugin": "^7.1.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz", "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
"xlsx-style": "^0.8.13", "xlsx-js-style": "^1.2.0",
"xml-js": "^1.6.11", "xml-js": "^1.6.11",
"xml2js": "^0.6.2" "xml2js": "^0.6.2"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"depcheck": "^1.4.7", "depcheck": "^1.4.7",
"prisma": "^5.15.0" "prisma": "^5.19.1"
} }
} }

View File

@ -77,6 +77,10 @@ export const authOptions: NextAuthOptions = {
{ id: "1", name: "admin", email: "admin@example.com", password: process.env.ADMIN_PASSWORD, role: "ADMIN", static: true } { id: "1", name: "admin", email: "admin@example.com", password: process.env.ADMIN_PASSWORD, role: "ADMIN", static: true }
]; ];
if (process.env.ADMIN_PASSWORD !== credentials.password) {
throw new Error('невалидна парола');
}
const user = users.find(user => const user = users.find(user =>
user.name === credentials.username && user.password === credentials.password user.name === credentials.username && user.password === credentials.password
); );

View File

@ -12,6 +12,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import { all } from "axios"; import { all } from "axios";
import { logger } from "src/helpers/common"; import { logger } from "src/helpers/common";
import { excel } from "src/helpers/excel";
/** /**
* *
@ -432,7 +433,13 @@ export default async function handler(req, res) {
case "getAllPublishersWithStatistics": case "getAllPublishersWithStatistics":
let noEndDate = common.parseBool(req.query.noEndDate); let noEndDate = common.parseBool(req.query.noEndDate);
res.status(200).json(await dataHelper.getAllPublishersWithStatisticsMonth(day, noEndDate)); res.status(200).json(await dataHelper.getAllPublishersWithStatisticsMonth(day, noEndDate));
case "exportPublishersExcel":
try {
await excel.ExportPublishersToExcel(req, res);
} catch (error) {
console.error(JSON.stringify(error));
}
break;
default: default:
res.status(200).json({ res.status(200).json({
"message": "no action '" + action + "' found" "message": "no action '" + action + "' found"

View File

@ -94,6 +94,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
where: { where: {
isActive: true, isActive: true,
isPublished: true, isPublished: true,
// OR: [
// { isPublished: true },
// { user: { role: 'admin' } } // Todo: example. fix this
// ],
startTime: { startTime: {
gte: fromDate, gte: fromDate,
//lt: toDate, //lt: toDate,
@ -152,15 +156,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
//bold the text after - in the notes //bold the text after - in the notes
notes: notes, notes: notes,
notes_bold: notes_bold, notes_bold: notes_bold,
names: shift.assignments names: shift.assignments.length > 0
.map((assignment) => { ? shift.assignments
return ( .map((assignment) => {
assignment.publisher.firstName + return (
" " + assignment.publisher.firstName +
assignment.publisher.lastName " " +
); assignment.publisher.lastName
}) );
.join(", "), })
.join(", ")
: shift.name,
}; };
if (shiftSchedule.names.length > 0) { if (shiftSchedule.names.length > 0) {
@ -246,6 +252,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
); );
} }
catch (error) { catch (error) {
console.log(error);
res.status(500).json({ error: "Internal Server Error" }); res.status(500).json({ error: "Internal Server Error" });
} }
} else { } else {

View File

@ -9,6 +9,7 @@ import Shift from '../../../components/calendar/ShiftComponent';
import { DayOfWeek, UserRole } from '@prisma/client'; import { DayOfWeek, UserRole } from '@prisma/client';
import { env } from 'process' import { env } from 'process'
import ShiftComponent from '../../../components/calendar/ShiftComponent'; import ShiftComponent from '../../../components/calendar/ShiftComponent';
import PublisherShiftsModal from '../../../components/publisher/PublisherShiftsModal';
//import { set } from 'date-fns'; //import { set } from 'date-fns';
const common = require('src/helpers/common'); const common = require('src/helpers/common');
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@ -66,6 +67,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
const [shifts, setShifts] = React.useState([]); const [shifts, setShifts] = React.useState([]);
const [error, setError] = React.useState(null); const [error, setError] = React.useState(null);
const [availablePubs, setAvailablePubs] = React.useState([]); const [availablePubs, setAvailablePubs] = React.useState([]);
const [selectedPublisher, setSelectedPublisher] = React.useState(null);
const [selectedShiftId, setSelectedShiftId] = useState(null); const [selectedShiftId, setSelectedShiftId] = useState(null);
@ -214,8 +216,8 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
}; };
const handleSelectedPublisher = (publisher) => { const handleSelectedPublisher = (publisher) => {
// Do something with the selected publisher
console.log("handle pub clicked:", publisher); console.log("handle pub clicked:", publisher);
setSelectedPublisher(publisher);
} }
const handlePublisherModalOpen = async (publisher) => { const handlePublisherModalOpen = async (publisher) => {
// Do something with the selected publisher // Do something with the selected publisher
@ -355,7 +357,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
} }
return <div>{" "}</div>; return <div>{" "}</div>;
}; };
//ToDo: DRY - move to common
const addAssignment = async (publisher, shiftId) => { const addAssignment = async (publisher, shiftId) => {
try { try {
console.log(`calendar.idx: new assignment for publisher ${publisher.id} - ${publisher.firstName} ${publisher.lastName}`); console.log(`calendar.idx: new assignment for publisher ${publisher.id} - ${publisher.firstName} ${publisher.lastName}`);
@ -365,6 +367,10 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
isConfirmed: true isConfirmed: true
}; };
const { data } = await axiosInstance.post("/api/data/assignments", newAssignment); const { data } = await axiosInstance.post("/api/data/assignments", newAssignment);
if (selectedShiftId == shiftId) {
handleShiftSelection(shifts.find(shift => shift.id === shiftId));
}
// Update the 'publisher' property of the returned data with the full publisher object // Update the 'publisher' property of the returned data with the full publisher object
data.publisher = publisher; data.publisher = publisher;
data.shift = shifts.find(shift => shift.id === shiftId); data.shift = shifts.find(shift => shift.id === shiftId);
@ -779,7 +785,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
<input type="checkbox" className="toggle-checkbox" id="filterIncludeOldAvailabilities" onChange={handleCheckboxChange} /> <input type="checkbox" className="toggle-checkbox" id="filterIncludeOldAvailabilities" onChange={handleCheckboxChange} />
<span className="toggle-slider m-1">със стари предпочитания</span> <span className="toggle-slider m-1">със стари предпочитания</span>
</label> </label>
<ul className="w-full max-w-md"> <ul className="w-full max-w-md" id="availablePubsList" name="availablePubsList">
{Array.isArray(availablePubs) && availablePubs?.map((pub, index) => { {Array.isArray(availablePubs) && availablePubs?.map((pub, index) => {
// Determine background and border classes based on conditions // Determine background and border classes based on conditions
let bgAndBorderColorClass; let bgAndBorderColorClass;
@ -814,8 +820,8 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
<li key={index} <li key={index}
className={`flex justify-between items-center p-4 sm:py-2 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} ${bgAndBorderColorClass} ${selectedBorderClass} ${activeOpacityClass}
${pub.currentMonthAssignments >= pub.desiredShiftsPerMonth ? 'text-gray-400' : 'text-gray-800'}`} ${pub.currentMonthAssignments === pub.desiredShiftsPerMonth ? 'text-gray-400' : pub.currentMonthAssignments > pub.desiredShiftsPerMonth ? 'text-orange-300' : 'text-gray-800'}`}
onDoubleClick={(handlePublisherModalOpen.bind(this, pub))} onDoubleClick={() => handlePublisherModalOpen(pub)}
> >
<span className={`${pub.isAvailableForShift ? 'font-bold' : 'font-medium'} `}> <span className={`${pub.isAvailableForShift ? 'font-bold' : 'font-medium'} `}>
{pub.firstName} {pub.lastName} {pub.firstName} {pub.lastName}
@ -831,36 +837,10 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
<button tooltip="желани участия на месец" title="желани участия" className={`badge py-1 px-2 rounded-md text-xs ${pub.desiredShiftsPerMonth ? 'bg-purple-500 text-white' : 'bg-purple-200 text-gray-400'}`}>{pub.desiredShiftsPerMonth || 0}</button> <button tooltip="желани участия на месец" title="желани участия" className={`badge py-1 px-2 rounded-md text-xs ${pub.desiredShiftsPerMonth ? 'bg-purple-500 text-white' : 'bg-purple-200 text-gray-400'}`}>{pub.desiredShiftsPerMonth || 0}</button>
<button tooltip="push" title="push" className={`badge py-1 px-2 rounded-md text-xs bg-red-100`} <button tooltip="push" title="push" className={`badge py-1 px-2 rounded-md text-xs bg-red-100`}
onClick={async () => { onClick={async () => {
await fetch('/api/notify', { handleSelectedPublisher(pub);
method: 'POST', addAssignment(pub, selectedShiftId);
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: pub.id,
message: "Тестово съобщение",
title: "Това е тестово съобщение от https://sofia.mwitnessing.com",
actions: [
{ action: 'OK', title: 'OK', icon: '✅' },
{ action: 'close', title: 'Затвори', icon: '❌' }
]
// actions: [
// {
// title: 'Open URL',
// action: 'open_url',
// icon: '/images/open-url.png'
// },
// {
// title: 'Dismiss',
// action: 'dismiss',
// icon: '/images/dismiss.png'
// }
// ]
})
})
}} }}
>+</button> >+</button>
</div> </div>
</li> </li>
); );
@ -899,151 +879,22 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
<div> <div>
{/* <CustomCalendar date={value} shifts={shifts} /> */} {/* <CustomCalendar date={value} shifts={shifts} /> */}
</div> </div>
{isModalOpen && <PublisherShiftsModal publisher={modalPub} shifts={allShifts} onClose={() => setIsModalOpen(false)} />} {isModalOpen && (
<PublisherShiftsModal
publisher={modalPub}
_shifts={allShifts}
onClose={() => setIsModalOpen(false)}
date={value}
onAssignmentChange={handleAssignmentChange}
/>
)}
</ProtectedRoute > </ProtectedRoute >
</Layout > </Layout >
</> </>
); );
function PublisherShiftsModal({ publisher, shifts, onClose }) {
const monthInfo = common.getMonthDatesInfo(new Date(value));
const monthShifts = shifts.filter(shift => {
const shiftDate = new Date(shift.startTime);
return shiftDate > monthInfo.firstDay && shiftDate < monthInfo.lastDay;
});
const weekShifts = monthShifts.filter(shift => {
const shiftDate = new Date(shift.startTime);
return common.getStartOfWeek(value) <= shiftDate && shiftDate <= common.getEndOfWeek(value);
});
const dayShifts = weekShifts.map(shift => {
const isAvailable = publisher.availabilities?.some(avail =>
avail.startTime <= shift.startTime && avail.endTime >= shift.endTime
);
let color = isAvailable ? getColorForShift(shift) : 'bg-gray-300';
if (shift.isFromPreviousMonth) {
color += ' border-l-4 border-orange-500 ';
}
if (shift.isFromPreviousAssignment) {
color += ' border-l-4 border-red-500 ';
}
return { ...shift, isAvailable, color };
}).reduce((acc, shift) => {
const dayIndex = new Date(shift.startTime).getDay();
acc[dayIndex] = acc[dayIndex] || [];
acc[dayIndex].push(shift);
return acc;
}, {});
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;
});
};
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
console.log('ESC: closing modal.');
onClose(); // Call the onClose function when ESC key is pressed
}
};
// Add event listener
window.addEventListener('keydown', handleKeyDown);
// Remove event listener on cleanup
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [onClose]); // Include onClose in the dependency array
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">
<h2 className="text-xl font-semibold mb-4">График на <span title={publisher.email} className='publisher'>
<strong>{publisher.firstName} {publisher.lastName}</strong>
<span className="publisher-tooltip" onClick={common.copyToClipboard}>{publisher.email}</span>
</span> тази седмица:</h2>
{/* ... Display shifts in a calendar-like UI ... */}
<div className="grid grid-cols-6 gap-4 mb-4">
{Object.entries(dayShifts).map(([dayIndex, shiftsForDay]) => (
<div key={dayIndex} className="flex flex-col space-y-2 justify-end">
{/* Day header */}
<div className="text-center font-medium">{new Date(shiftsForDay[0].startTime).getDate()}-ти</div>
{shiftsForDay.map((shift, index) => {
const assignmentExists = hasAssignment(shift.id);
const availability = publisher.availabilities.find(avail =>
avail.startTime <= shift.startTime && avail.endTime >= shift.endTime
);
const isFromPrevMonth = availability && availability.isFromPreviousMonth;
return (
<div
key={index}
className={`text-sm text-white p-2 rounded-md ${isFromPrevMonth ? 'border-l-6 border-black-500' : ''} ${shift.color} ${assignmentExists ? 'border-2 border-blue-500' : ""} h-24 flex flex-col justify-center`}
>
{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"
>
добави
</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"
>
махни
</button>
)}
</div>
);
}
)}
</div>
))}
</div>
{/* Close button in the top right corner */}
<button
onClick={onClose}
className="absolute top-3 right-2 p-2 px-3 bg-red-500 text-white rounded-full hover:bg-red-600 active:bg-red-700 focus:outline-none"
>
&times;
</button>
{/* <Link href={`/cart/publishers/edit/${modalPub.id}`}
className="mt-2 bg-blue-500 text-white p-1 rounded hover:bg-blue-600 active:bg-blue-700 focus:outline-none">
<i className="fas fa-edit" />
</Link> */}
{/* Edit button in the top right corner, next to the close button */}
<Link href={`/cart/publishers/edit/${modalPub.id}`} className="absolute top-3 right-12 p-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 active:bg-blue-700 focus:outline-none">
<i className="fas fa-edit" />
</Link>
</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';
}
}
} }
import axiosServer from '../../../src/axiosServer'; import axiosServer from '../../../src/axiosServer';

View File

@ -15,8 +15,7 @@ import { levenshteinEditDistance } from "levenshtein-edit-distance";
import ProtectedRoute from '../../../components/protectedRoute'; import ProtectedRoute from '../../../components/protectedRoute';
import ConfirmationModal from '../../../components/ConfirmationModal'; import ConfirmationModal from '../../../components/ConfirmationModal';
import { relative } from "path"; import { relative } from "path";
import { set } from "lodash";
interface IProps { interface IProps {
initialItems: Publisher[]; initialItems: Publisher[];
@ -24,14 +23,31 @@ interface IProps {
function PublishersPage({ publishers = [] }: IProps) { function PublishersPage({ publishers = [] }: IProps) {
const [shownPubs, setShownPubs] = useState(publishers); const [shownPubs, setShownPubs] = useState(publishers);
const [filter, setFilter] = useState(""); const [filter, setFilter] = useState("");
const [showZeroShiftsOnly, setShowZeroShiftsOnly] = useState(false);
const [filterIsImported, setFilterIsImported] = useState({ const [filterIsImported, setFilterIsImported] = useState({
checked: false, checked: false,
indeterminate: true, indeterminate: true,
}); });
const [showZeroShiftsOnly, setShowZeroShiftsOnly] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
// const cbRefFilterTraining = useRef<HTMLInputElement>(null);
// const getCheckboxState = (currentState: boolean | null) => {
// if (currentState === true) return 'unchecked';
// if (currentState === false) return 'checked';
// return 'indeterminate';
// };
// const cbRefFilterTraining = useRef<HTMLInputElement>(null);
// const [cbFilterTrainingState, setcbFilterTrainingState] = useState<boolean | null>(null);
// useEffect(() => {
// if (cbRefFilterTraining.current) {
// cbRefFilterTraining.current.indeterminate = cbFilterTrainingState === null;
// }
// }, [cbFilterTrainingState]);
const [flterNoTraining, setFilterNoTraining] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [isModalOpenDeleteAllVisible, setIsModalOpenDeleteAllVisible] = useState(false); const [isModalOpenDeleteAllVisible, setIsModalOpenDeleteAllVisible] = useState(false);
const [isModalOpenDeleteAllAvaillabilities, setIsModalOpenDeleteAllAvaillabilities] = useState(false); const [isModalOpenDeleteAllAvaillabilities, setIsModalOpenDeleteAllAvaillabilities] = useState(false);
@ -102,10 +118,15 @@ function PublishersPage({ publishers = [] }: IProps) {
? filteredPublishers.filter(p => p.assignments.length === 0) ? filteredPublishers.filter(p => p.assignments.length === 0)
: filteredPublishers; : filteredPublishers;
setShownPubs(filteredPublishers); // trained filter
}, [filter, showZeroShiftsOnly]); if (flterNoTraining) {
filteredPublishers = filteredPublishers.filter(p => p.isTrained === false);
}
setShownPubs(filteredPublishers);
}, [filter, showZeroShiftsOnly, flterNoTraining]);
const checkboxRef = useRef();
const renderPublishers = () => { const renderPublishers = () => {
if (shownPubs.length === 0) { if (shownPubs.length === 0) {
@ -138,31 +159,33 @@ function PublishersPage({ publishers = [] }: IProps) {
if (type === 'text') { if (type === 'text') {
setFilter(value); setFilter(value);
} else if (type === 'checkbox') { } else if (type === 'checkbox') {
// setFilterIsImported({ ...checkboxFilter, [name]: checked }); if (name === 'filterIsImported') {
const { checked, indeterminate } = checkboxRef.current; setFilterIsImported({ checked, indeterminate: false });
if (!checked && !indeterminate) { }
// Checkbox was unchecked, set it to indeterminate state if (name === 'filterTrained') {
checkboxRef.current.indeterminate = true; // const nextState = cbFilterTrainingState === false ? null : cbFilterTrainingState === null ? true : false;
setFilterIsImported({ checked: false, indeterminate: true }); // setcbFilterTrainingState(nextState);
} else if (!checked && indeterminate) { setFilterNoTraining(checked);
// Checkbox was indeterminate, set it to checked state
checkboxRef.current.checked = true;
checkboxRef.current.indeterminate = false;
setFilterIsImported({ checked: true, indeterminate: false });
} else if (checked && !indeterminate) {
// Checkbox was checked, set it to unchecked state
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = false;
setFilterIsImported({ checked: false, indeterminate: false });
} else {
// Checkbox was checked and indeterminate (should not happen), set it to unchecked state
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = false;
setFilterIsImported({ checked: false, indeterminate: false });
} }
} }
}; };
const exportPublishers = async () => {
try {
const response = await axiosInstance.get('/api/?action=exportPublishersExcel');
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'publishers.xlsx';
a.click();
} catch (error) {
console.error(JSON.stringify(error)); // Log the error
toast.error("Грешка при експорт на данни");
}
}
return ( return (
<Layout> <Layout>
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]}> <ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]}>
@ -195,8 +218,13 @@ function PublishersPage({ publishers = [] }: IProps) {
<div className="flex justify-center m-4"> <div className="flex justify-center m-4">
<a href="/cart/publishers/import" className="btn">Import publishers</a> <a href="/cart/publishers/import" className="btn">Import publishers</a>
</div> </div>
{/* export by calling excel helper .ExportPublishersToExcel() */}
<div className="flex justify-center m-4">
<button className="btn" onClick={exportPublishers}>Export to Excel</button>
</div>
</div> </div>
<div className="z-60 sticky top-0" style={{ zIndex: 60, position: relative }}> <div className="z-60 sticky top-0" style={{ zIndex: 60, position: relative }}>
<div name="filters" className="flex items-center justify-center space-x-4 m-4 bg-gray-100 p-2" > <div name="filters" className="flex items-center justify-center space-x-4 m-4 bg-gray-100 p-2" >
<label htmlFor="filter">Filter:</label> <label htmlFor="filter">Filter:</label>
@ -212,7 +240,17 @@ function PublishersPage({ publishers = [] }: IProps) {
<span className="ml-2">само без смени</span> <span className="ml-2">само без смени</span>
</label> </label>
<span id="filter-info" className="ml-4">{publishers.length} от {publishers.length} вестителя</span> <label htmlFor="filterTrained" className="ml-4 inline-flex items-center">
<input type="checkbox" id="filterTrained" name="filterTrained"
// support intermediate state if checkboxState is null
checked={flterNoTraining}
onChange={handleFilterChange}
className="form-checkbox text-indigo-600"
/>
<span className="ml-2">без обучение</span>
</label>
<span id="filter-info" className="ml-4">{shownPubs.length} от {publishers.length} вестителя</span>
</div> </div>
</div> </div>
<div className="grid gap-4 grid-cols-1 md:grid-cols-4 z-0"> <div className="grid gap-4 grid-cols-1 md:grid-cols-4 z-0">
@ -226,12 +264,12 @@ function PublishersPage({ publishers = [] }: IProps) {
export default PublishersPage; export default PublishersPage;
//import { set } from "date-fns"; //import {set} from "date-fns";
export const getServerSideProps = async (context) => { export const getServerSideProps = async (context) => {
// const axios = await axiosServer(context); // const axios = await axiosServer(context);
// //ToDo: refactor all axios calls to use axiosInstance and this URL // //ToDo: refactor all axios calls to use axiosInstance and this URL
// const { data: publishers } = await axios.get('/api/data/publishers?select=id,firstName,lastName,email,isActive,isTrained,isImported,assignments.shift.startTime,availabilities.startTime&dev=fromuseefect'); // const {data: publishers } = await axios.get('/api/data/publishers?select=id,firstName,lastName,email,isActive,isTrained,isImported,assignments.shift.startTime,availabilities.startTime&dev=fromuseefect');
//use prisma instead of axios //use prisma instead of axios
const prisma = common.getPrismaClient(); const prisma = common.getPrismaClient();
let publishers = await prisma.publisher.findMany({ let publishers = await prisma.publisher.findMany({

View File

@ -9,7 +9,7 @@ import ProtectedRoute from '../../../components/protectedRoute';
function NewPage(loc: Location) { function NewPage(loc: Location) {
return ( return (
<Layout> <Layout>
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN]}> <ProtectedRoute allowedRoles={[UserRole.USER, UserRole.POWERUSER, UserRole.ADMIN]}>
<div className="h-5/6 grid place-items-center"> <div className="h-5/6 grid place-items-center">
<ExperienceForm /> <ExperienceForm />
</div></ProtectedRoute> </div></ProtectedRoute>

View File

@ -105,7 +105,12 @@ export default PDFViewerPage;
export const getServerSideProps = async (context) => { export const getServerSideProps = async (context) => {
const permitsFolder = '/public/content/permits/'; const permitsFolder = path.join('public', 'content', 'permits');
// Create folders if they do not exist
if (!fs.existsSync(permitsFolder)) {
fs.mkdirSync(permitsFolder, { recursive: true });
}
//get all the files in the permits folder order them by date desc and display them //get all the files in the permits folder order them by date desc and display them
const pdfFiles = fs.readdirSync(path.join(process.cwd(), permitsFolder)).map(file => { const pdfFiles = fs.readdirSync(path.join(process.cwd(), permitsFolder)).map(file => {
return { return {

View File

@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE `Message` MODIFY `content` TEXT NOT NULL;
-- AlterTable
ALTER TABLE `Survey` MODIFY `content` TEXT NOT NULL;

View File

@ -0,0 +1,6 @@
-- DropForeignKey
ALTER TABLE `Message` DROP FOREIGN KEY `Message_publisherId_fkey`;
-- AddForeignKey
ALTER TABLE `Message`
ADD CONSTRAINT `Message_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -266,7 +266,7 @@ enum MessageType {
model Survey { model Survey {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
content String content String @db.Text
answers Json? answers Json?
messages Message[] messages Message[]
publicFrom DateTime? publicFrom DateTime?
@ -275,10 +275,10 @@ model Survey {
model Message { model Message {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
publisher Publisher @relation(fields: [publisherId], references: [id]) publisher Publisher @relation(fields: [publisherId], references: [id], onDelete: Cascade)
publisherId String publisherId String
date DateTime date DateTime
content String content String @db.Text
isRead Boolean @default(false) isRead Boolean @default(false)
isPublic Boolean @default(false) isPublic Boolean @default(false)
type MessageType @default(Email) type MessageType @default(Email)
@ -305,7 +305,7 @@ model EventLog {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
date DateTime date DateTime
publisherId String? publisherId String?
publisher Publisher? @relation(fields: [publisherId], references: [id]) publisher Publisher? @relation(fields: [publisherId], references: [id], onDelete: SetNull)
shiftId Int? shiftId Int?
shift Shift? @relation(fields: [shiftId], references: [id]) shift Shift? @relation(fields: [shiftId], references: [id])
content String @db.VarChar(5000) content String @db.VarChar(5000)

View File

@ -574,7 +574,7 @@ nextApp
// --------------- EXCEL EXPORT ROUTE ---------------- // --------------- EXCEL EXPORT ROUTE ----------------
server.get("/generatexcel/:year/:month/:process", async (req, res) => { server.get("/generatexcel/:year/:month/:process", async (req, res) => {
await excel.GenerateExcel(req, res); await excel.ScheduleGenerateExcel(req, res);
}); });

View File

@ -14,8 +14,12 @@ const data = require("./data");
// for nodejs // for nodejs
//const api = require("./pages/api/index"); //const api = require("./pages/api/index");
// import dynamic from 'next/dynamic';
exports.GenerateExcel = async function (req, res) { // const XLSX = dynamic(() => import('xlsx-style'), { ssr: false });
exports.ScheduleGenerateExcel = async function (req, res) {
const prisma = common.getPrismaClient(); const prisma = common.getPrismaClient();
const year = req.params.year; const year = req.params.year;
@ -162,7 +166,7 @@ exports.GenerateExcel = async function (req, res) {
const ws = XLSX.utils.aoa_to_sheet(ws_data); const ws = XLSX.utils.aoa_to_sheet(ws_data);
wb.Sheets["График КОЛИЧКИ"] = ws; wb.Sheets["График КОЛИЧКИ"] = ws;
const xlsxstyle = require("xlsx-style"); const xlsxstyle = require("xlsx-js-style");
try { try {
const workbook = xlsxstyle.readFile(filePath); const workbook = xlsxstyle.readFile(filePath);
const sheetNames = workbook.SheetNames; const sheetNames = workbook.SheetNames;
@ -611,6 +615,55 @@ exports.ReadDocxFileForMonth = async function (filePath, buffer, month, year, pr
} }
}; };
exports.ExportPublishersToExcel = async function (req, res) {
const prisma = common.getPrismaClient();
const publishers = await prisma.publisher.findMany({
// where: { isActive: true, },
include: {
// availabilities: { where: { isActive: true, }, },
// assignments: { include: { shift: true, }, },
congregation: true,
},
});
const ExcelJS = require("exceljs");
const xjswb = new ExcelJS.Workbook();
const sheet = xjswb.addWorksheet("Publishers");
sheet.columns = [
{ header: "Name", key: "name", width: 32 },
{ header: "Trained", key: "trained", width: 10 },
{ header: "Email", key: "email", width: 32 },
{ header: "Phone", key: "phone", width: 32 },
{ header: "Role", key: "role", width: 32 },
{ header: "Congregation", key: "congregationName", width: 32 },
{ header: "Last Login", key: "lastLogin", width: 32 },
{ header: "Type", key: "PublisherTypeText", width: 32 },
{ header: "Active", key: "isActive", width: 10 },
{ header: "Created At", key: "createdAt", width: 32 },
{ header: "Updated At", key: "updatedAt", width: 32 },
];
publishers.forEach((publisher) => {
sheet.addRow({
name: publisher.firstName + " " + publisher.lastName,
trained: publisher.isTrained,
email: publisher.email,
phone: publisher.phone,
role: publisher.role,
congregationName: publisher.congregation.name,
lastLogin: publisher.lastLogin,
PublisherTypeText: publisher.PublisherTypeText,
isActive: publisher.isActive,
createdAt: publisher.createdAt,
updatedAt: publisher.updatedAt,
});
});
res.setHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
res.setHeader("Content-Disposition", "attachment; filename=" + encodeURI("Publishers.xlsx"));
xjswb.xlsx.write(res);
}
const weekNames = [ const weekNames = [
"Понеделник", "Понеделник",
"Вторник", "Вторник",