Files
mwitnessing/pages/api/schedule.ts
Dobromir Popov a03ed81956 edit texts
2025-04-19 04:13:13 +03:00

291 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// pages/api/shifts.ts
import axiosServer from '../../src/axiosServer';
import { getToken } from "next-auth/jwt";
import type { NextApiRequest, NextApiResponse } from "next";
import { Prisma, PrismaClient, DayOfWeek, Publisher, Shift } from "@prisma/client";
import { levenshteinEditDistance } from "levenshtein-edit-distance";
import { filterPublishers, /* other functions */ } from './index';
import CAL from "../../src/helpers/calendar";
//const common = require("@common");
import common from "../../src/helpers/common";
import { Axios } from 'axios';
const path = require("path");
const fs = require("fs");
const generateTemplateFile = async (data, templateSrc) => {
const handlebars = require("handlebars");
const htmlDocx = require("html-docx-js");
// Compile the Handlebars template
const template = handlebars.compile(templateSrc);
// Generate the HTML output using the template and the events data
const html = template(data);
return html;
}
function splitNotes(notes) {
if (!notes) {
return { notes: '', notes_bold: '' };
}
// Combine both dash types into a single pattern for the regex
const dashPattern = /[-]/g;
// Find all occurrences of either dash
let allDashes = [...notes.matchAll(dashPattern)];
if (allDashes.length === 0) {
// No dash found, return the original notes in both parts
return { notes: notes, notes_bold: '' };
}
// Get the index of the first and last dash
const firstDashIndex = allDashes[0].index;
const lastDashIndex = allDashes[allDashes.length - 1].index;
// Extract parts before the first dash and after the last dash
const notesBeforeFirstDash = notes.substring(0, firstDashIndex + 1);
const notesAfterLastDash = notes.substring(lastDashIndex + 1);
return {
notes: notesBeforeFirstDash,
notes_bold: notesAfterLastDash
};
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
console.log(req.url);
console.log(req.query);
const prisma = common.getPrismaClient();
// If you don't have the NEXTAUTH_SECRET environment variable set,
// you will have to pass your secret as `secret` to `getToken`
const axios = await axiosServer({ req: req, res: res });
const token = await getToken({ req: req });
if (!token) {
// If no token or invalid token, return unauthorized status
return res.status(401).json({ message: "Unauthorized" });
}
if (req.method === 'GET') {
const { year, month } = req.query;
//ToDo: maybe we don't need that anymore as we are publishing the shifts and show all published shifts
let fromDate = new Date();
fromDate.setDate(fromDate.getDate() - 1);
fromDate.setHours(0, 0, 0, 0);
if (year && month) {
fromDate = new Date(parseInt(year as string), parseInt(month as string) - 1, 1);
}
const monthInfo = common.getMonthDatesInfo(fromDate);
//let toDate = new Date(monthInfo.lastSunday);
if (year && month) {
fromDate = monthInfo.firstMonday;
}
try {
const shifts = await prisma.shift.findMany({
where: {
isActive: true,
isPublished: true,
// OR: [
// { isPublished: true },
// { user: { role: 'admin' } } // Todo: example. fix this
// ],
startTime: {
gte: fromDate,
//lt: toDate,
},
},
orderBy: {
startTime: 'asc',
},
include: {
assignments: {
where: {},
include: {
publisher: true,
},
},
cartEvent: {
include: {
location: true,
},
},
},
});
let json = JSON.stringify(shifts);
const groupedShifts = {};
const startDate = new Date(shifts[0].startTime);
let i = 0;
try {
for (const shift of shifts) {
i++;
const date = new Date(shift.startTime);
const day = common.getISODateOnly(date)
const time = common.getTimeRange(shift.startTime, shift.endTime); //common.getLocalTime(date);
if (!groupedShifts[day]) {
groupedShifts[day] = {};
}
if (!groupedShifts[day][time]) {
groupedShifts[day][time] = [];
}
// let { notes, notes_bold } = splitNotes(shift.notes);//.substring(Math.max(shift.notes.lastIndexOf("-"), shift.notes.lastIndexOf("")) + 1).trim() || "";
let notes = "", notes_bold = "";
if (shift.assignments.some(a => a.isWithTransport)) {
if (shift.requiresTransport) {
notes = "Транспорт: ";
notes_bold = " " + shift.assignments.filter(a => a.isWithTransport).map(a => common.getInitials(a.publisher.firstName + " " + a.publisher.lastName)).join(", ");
}
}
let shiftSchedule = {
date: date,
placeOfEvent: shift.cartEvent.location.name,
time: time,
requiresTransport: shift.requiresTransport,
//bold the text after - in the notes
notes: notes,
notes_bold: notes_bold,
names: shift.assignments.length > 0
? shift.assignments
.map((assignment) => {
return (
assignment.publisher.firstName +
" " +
assignment.publisher.lastName
);
})
.join(", ")
: shift.name,
};
if (shiftSchedule.names.length > 0) {
groupedShifts[day][time].push(shiftSchedule);
}
}
} catch (err) {
console.log(err + " " + JSON.stringify(shifts[i]));
}
for (const day in groupedShifts) {
const times = Object.keys(groupedShifts[day]);
for (const time of times) {
const shift = groupedShifts[day][time][0];
if (shift) {
// Determine the first shift of the day if it requires transport
if (time === times[0] && shift.requiresTransport) { // Check if this is the first time slot of the day
shift.notes = "Докарва количка от Склад Сердика -"; // Update the first shift in the first time slot
}
// Determine the last shift of the day if it requires transport
if (time === times[times.length - 1] && shift.requiresTransport) { // Check if this is the last time slot of the day
shift.notes = "Прибира количка в Склад Сердика -"; // Update the last shift in the last time slot
}
}
// if day is thursday, change the note to "Специален текст с линк към снимка" : public/images/cart_EN.jpg - opened in a popup modal
//check if the day is thursday
const dayOfWeek = common.getDayOfWeekNameEnEnumForDate(shift.date);
if (dayOfWeek == "Thursday" && shift.requiresTransport) {
// For Thursday, use the regular transport text (if any) but add a special note indicator
let originalNotes = shift.notes || "";
shift.notes = `<a href="#" onclick="document.getElementById('cart-image-modal').style.display='block'; return false;" style="color: #2563eb; text-decoration: underline; cursor: pointer; font-weight: bold; padding: 2px 6px; border: 1px solid #2563eb; border-radius: 4px; background-color: #eff6ff; display: inline-block;">Специални инструкции ↗</a>
${originalNotes}
<div id="cart-image-modal" style="display: none;">
<div class="fixed inset-0 flex items-center justify-center z-50">
<div style="background-color: white; padding: 16px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); position: relative; max-width: fit-content;">
<span onclick="document.getElementById('cart-image-modal').style.display='none'" style="position: absolute; right: 10px; top: 5px; cursor: pointer; font-size: 24px; color: #6b7280;">&times;</span>
<div style="display: flex; gap: 20px; align-items: start;">
<img src="/images/cart_EN.jpg" style="max-height: 60vh; object-fit: contain;" />
<div style="max-width: 300px;">
<h3 style="font-weight: bold; margin-bottom: 10px;">Специални инструкции за четвъртък:</h3>
<ul style="list-style-type: disc; padding-left: 20px;">
<li style="margin-bottom: 8px; color: #e53e3e;"><strong>ВАЖНО:</strong> Моля, извършете всички дейности извън склада, в който се съхраняват количките.</li>
<li style="margin-bottom: 8px;"><strong>Четвъртък - първа смяна:</strong> Заредете брошури и плакати на английски език.</li>
<li style="margin-bottom: 8px;"><strong>Четвъртък - последна смяна:</strong> Подгответе количките за следващия ден с български материали.</li>
</ul>
</div>
</div>
</div>
<div class="fixed inset-0 bg-black opacity-50" onclick="document.getElementById('cart-image-modal').style.display='none'"></div>
</div>
</div>`;
}
}
}
// Create the output object in the format of the second JSON file
const monthlySchedule = {
month: common.getMonthName(shifts[0].startTime.getMonth()),
year: startDate.getFullYear(),
events: [],
};
for (const day in groupedShifts) {
var dayEvent = null;
for (const time in groupedShifts[day]) {
if (dayEvent == null) {
const shift = groupedShifts[day][time][0];
if (!shift) {
console.log("shift is null");
continue;
}
let weekday = common.getDayOfWeekName(shift.date);
let monthName = common.getMonthName(shift.date.getMonth());
weekday = weekday.charAt(0).toUpperCase() + weekday.slice(1);
let weekNr = common.getWeekNumber(shift.date);
console.log("weekday = " + weekday, " weekNr = " + weekNr);
dayEvent = {
week: weekNr,
dayOfWeek: weekday,
dayOfMonth: shift.date.getDate(),
placeOfEvent: shift.placeOfEvent,
shifts: [],
transport: shift.notes,
monthName: monthName,
};
}
dayEvent.shifts.push(...groupedShifts[day][time]);
}
if (dayEvent) {
monthlySchedule.events.push(dayEvent);
}
}
const outputPath = path.join(process.cwd(), 'public', 'content', 'output');
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}
//fs.writeFileSync(path.join(outputPath, `shifts ${year}.${month}.json`), JSON.stringify(monthlySchedule), 'utf8');
// Load the Handlebars template from a file
const template = fs.readFileSync("./src/templates/schedule.hbs", "utf8");
generateTemplateFile(monthlySchedule, template).then((result) => {
const filename = path.join(outputPath, `schedule.html`)
//fs.writeFileSync(filename, result, "utf8");
res.end(result);
}
);
}
catch (error) {
console.log(error);
res.status(500).json({ error: "Internal Server Error" });
}
} else {
res.setHeader('Allow', ['GET']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}