// import { format, startOfMonth, endOfMonth, eachDayOfInterval, startOfWeek, endOfWeek, isSameMonth } from 'date-fns'; const levenshtein = require('fastest-levenshtein'); const fs = typeof window === 'undefined' ? require('fs') : undefined; const path = typeof window === 'undefined' ? require('path') : undefined; const { PrismaClient, UserRole } = require('@prisma/client'); const DayOfWeek = require("@prisma/client").DayOfWeek; const winston = require('winston'); const { getSession } = require("next-auth/react"); const logger = winston.createLogger({ level: 'info', // Set the default log level format: winston.format.json(), // Choose a log format transports: [ // Define where logs should be output (e.g., to a file or the console) new winston.transports.Console(), ], }); exports.logger = logger; // import { DayOfWeek } from "@prisma/client"; //const UserRole = require("@prisma/client").UserRole; - does not allow usage of UserRole in code // import { UserRole } from '@prisma/client'; // const getConfig = require("next/config"); // exports.nextconfig = getConfig(); // //const { serverRuntimeConfig } = getConfig(); // const dotenv = require("dotenv"); // dotenv.config(); // // dotenv.config({ path: ".env.local" }); exports.isValidPhoneNumber = function (phone) { if (typeof phone !== 'string') { return false; // or handle as you see fit } // Remove spaces and dashes for validation const cleanedPhone = phone.replace(/\s|-/g, ''); // Check if the phone starts with '08' and has 10 digits if ((cleanedPhone.startsWith('08') || cleanedPhone.startsWith('07')) && cleanedPhone.length === 10) { return true; } // Check if the phone starts with '+359' and has the correct length if (cleanedPhone.startsWith('+359') && cleanedPhone.length === 13) { return true; } // If neither condition is met, the phone number is invalid return false; } exports.getBaseUrl = function (relative = "", req = null) { return process.env.NEXT_PUBLIC_PUBLIC_URL + relative; }; let prisma; exports.getPrismaClient = function getPrismaClient() { if (!prisma) { logger.debug("getPrismaClient: process.env.DATABASE = ", process.env.DATABASE); prisma = new PrismaClient({ // Optional: Enable Prisma logging //log: ['query', 'info', 'warn', 'error'], datasources: { db: { url: process.env.DATABASE } }, }); } logger.debug("getPrismaClient: process.env.DATABASE = ", process.env.DATABASE); return prisma; } exports.removeAccentsAndSpecialCharacters = function (str) { return str.normalize("NFD") // split an accented letter in the base letter and the accent .replace(/[\u0300-\u036f]/g, "") // remove all previously split accents .replace(/[^a-zA-Z0-9]/g, ""); // remove all chars not letters and not digits } Date.prototype.getDayEuropean = function () { const day = this.getDay(); return (day === 0) ? 6 : day - 1; // Convert 0 (Sunday) to 6, and decrement other days by 1 }; // Helper function to convert month name to 0-based index exports.getMonthNames = function () { return ["януари", "февруари", "март", "април", "май", "юни", "юли", "август", "септември", "октомври", "ноември", "декември"]; } exports.getMonthIndex = function (monthName) { return exports.getMonthNames().indexOf(monthName.toLowerCase()); }; exports.getMonthName = function (monthIndex) { return exports.getMonthNames()[monthIndex]; }; exports.getMonthNameEn = function (monthIndex) { const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; return monthNames[monthIndex]; }; exports.dayOfWeekNames = ["понеделник", "вторник", "сряда", "четвъртък", "петък", "събота", "неделя"]; exports.DaysOfWeekArray = [DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday]; // Helper function to convert day of week name to 0-based index (Sunday = 0) exports.getDayOfWeekIndex = function (dayOfWeekName) { dayOfWeekName = dayOfWeekName.toLowerCase().trim(); const abbreviatedDayOfWeekNames = ["пон", "вт", "ср", "четв", "пет", "съб", "нед"]; const enDayOfWeekNames = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].map((d) => d.toLowerCase()); let index = exports.dayOfWeekNames.indexOf(dayOfWeekName); // If not found in the full names, look in the abbreviated names if (index === -1) { index = abbreviatedDayOfWeekNames.indexOf(dayOfWeekName); } if (index === -1) { index = enDayOfWeekNames.indexOf(dayOfWeekName); } logger.debug("getDayOfWeekIndex: dayOfWeekName = ", dayOfWeekName, "index = ", index); return index; }; exports.getDayOfWeekName = function (date) { const dayOfWeekIndex = date.getDayEuropean(); return exports.dayOfWeekNames[dayOfWeekIndex]; }; exports.getDayOfWeekNameEnEnumForDate = function (date) { date = new Date(date); const dayOfWeekIndex = date.getDayEuropean(); return exports.DaysOfWeekArray[dayOfWeekIndex]; } //obsolete: we want to ensure getDayEuropean() is used, hense we will not use this function // exports.getDayOfWeekNameEnEnum = function (dayOfWeekIndex) { // //return enum instead of string // // const dayOfWeekNames: Record = { // // 0: DayOfWeek.Monday, // // 1: DayOfWeek.Tuesday, // // 2: DayOfWeek.Wednesday, // // 3: DayOfWeek.Thursday, // // 4: DayOfWeek.Friday, // // 5: DayOfWeek.Saturday, // // 6: DayOfWeek.Sunday // // }; // return exports.DaysOfWeekArray[dayOfWeekIndex]; // } exports.getPubTypeEnum = function (text) { const input = text.trim(); const mapping = { 'Вестител': 'Publisher', 'Бетелит': 'Bethelite', 'Редовен Пионер': 'RegularPioneer', 'Специален Пионер/Мисионер': 'SpecialPioneer_Missionary', }; // Default to 'Publisher' if the input does not match any key return mapping[input] || 'Publisher'; } /* sets the date portion of the date object to the correct day of week, keeping the time portion */ exports.getDayOfWeekDate = function (dayOfWeekName, date = new Date()) { const targetDay = exports.DaysOfWeekArray.indexOf(dayOfWeekName); // 0 = Sunday, 1 = Monday, etc. if (targetDay === -1) { throw new Error('Invalid day of week name provided'); } // Adjust currentDay to match our custom mapping let currentDay = date.getDayEuropean(); const daysDifference = targetDay - currentDay; date.setDate(date.getDate() + daysDifference); return date; }; //common.getWeekOfMonth(date) exports.getWeekOfMonth = function (inputDate) { //getWeekOfMonth? let date = new Date(inputDate); let firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1); let firstMonday = new Date(firstDayOfMonth); // Adjust firstDayOfMonth to the first Monday of the month if (firstDayOfMonth.getDay() === 0) { // Sunday firstMonday.setDate(2); } else if (firstDayOfMonth.getDay() !== 1) { // Not Monday firstMonday.setDate(9 - firstDayOfMonth.getDay()); } // Calculate the difference in days let diff = (date - firstMonday) / (1000 * 60 * 60 * 24); // Calculate week number let weekNumber = Math.ceil((diff + 1) / 7); return weekNumber; }; exports.getDateFromWeekNrAndDayOfWeek = function (firstMonday, weekNr, dayOfWeekEnum, startTime) { firstMonday = new Date(firstMonday); startTime = new Date(startTime); if (!weekNr || weekNr < 1 || weekNr > 5) { weekNr = this.getWeekOfMonth(startTime); } //get int from dayOfWeekEnum let dayOfWeekNr = this.getDayOfWeekIndex(dayOfWeekEnum); if (dayOfWeekNr < 0 || dayOfWeekNr > 6) { dayOfWeekNr = 0; } // Calculate the day offset from the first Monday of the month // Note: Assuming dayOfWeekEnum starts from 0 (Monday) to 6 (Sunday) const daysFromFirstMonday = (weekNr - 1) * 7 + dayOfWeekNr; // Calculate the new date let newStart = new Date(firstMonday); newStart.setDate(firstMonday.getDate() + daysFromFirstMonday); // Extract time from startTime and apply it to newStart const time = new Date(startTime); newStart.setHours(time.getHours(), time.getMinutes(), time.getSeconds()); return newStart; } exports.getMonthDatesInfo = function (date) { // cast to date if not daate date = new Date(date); // get first day of the month var firstDay = new Date(date.getFullYear(), date.getMonth(), 1); // get first day of next month var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 1); // lastDay = new Date(lastDay.getTime() - 1); lastDay = new Date(lastDay.getTime() - 86400000); // 86400000 ms = 1 day // // get the day of the week of the first day var firstDayOfWeek = firstDay.getDay(); // 0 = Sunday, 1 = Monday, etc. // calculate the date of the first Monday var firstMonday = new Date(firstDay); if (firstDayOfWeek !== 1) { // Check if the first day is not already a Monday firstMonday.setDate(firstDay.getDate() + ((7 - firstDayOfWeek) % 7) + 1); } // get last Monday var lastMonday = new Date(lastDay); if (lastDay.getDay() !== 0) { // Check if the last day is not already a Sunday lastMonday.setDate(lastDay.getDate() - ((lastDay.getDay() + 6) % 7)); } else { lastMonday.setDate(lastDay.getDate() - 6); } // get Sunday of the last week var lastSunday = new Date(lastMonday); lastSunday.setDate(lastMonday.getDate() + 6); var monthName = exports.getMonthName(date.getMonth()); // var firstDayNextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1); // // Calculate the last Sunday of the current month // // .getDay() returns 0 for Sunday, 1 for Monday, ..., 6 for Saturday // var lastSunday = new Date(firstDayNextMonth); // lastSunday.setDate(firstDayNextMonth.getDate() - firstDayNextMonth.getDay()); //logger.debug("Last Sunday: ", lastSunday); const diffInDays = (lastSunday - firstMonday) / (1000 * 60 * 60 * 24); // Calculate number of weeks, rounding up for partial weeks const nrOfWeeks = Math.ceil((diffInDays + 1) / 7); return { firstDay: firstDay, lastDay: lastDay, firstMonday: firstMonday, lastMonday: lastMonday, lastSunday: lastSunday, date: date, monthName: monthName, year: date.getFullYear(), nrOfWeeks: nrOfWeeks }; }; exports.getMonthInfo = exports.getMonthDatesInfo; exports.getMonthlyScheduleRange = function (date) { let info = exports.getMonthDatesInfo(date); // get first day of the month var firstDay = info.firstDay; // get last sunday of the month var lastSunday = info.lastSunday; return { firstDay: firstDay, lastSunday: lastSunday }; }; exports.getWeekNumber = function (date) { let info = exports.getMonthDatesInfo(date); // If the date is before the first full week, return 0 if (date < info.firstMonday) { return 0; } // Calculate the week number based on the first full week return Math.ceil((date.getDate() - info.firstMonday.getDate() + 1) / 7); }; exports.compareTimes = function (time1, time2) { const time1String = `${getHours(time1)}:${getMinutes(time1)}`; const time2String = `${getHours(time2)}:${getMinutes(time2)}`; return time1String.localeCompare(time2String); }; exports.normalizeTime = function (date, baseDate) { // return set(baseDate, { // hours: getHours(date), // minutes: getMinutes(date), // seconds: getSeconds(date), // milliseconds: 0 // }); //don't use date-fns let newDate = new Date(baseDate); newDate.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), 0); return newDate; } exports.getTimeRange = function (start, end) { start = new Date(start); end = new Date(end); const startTime = start.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Sofia' }); const endTime = end.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Sofia' }); return `${startTime}-${endTime}`; } //get date in format сряда 1 юли 2020 г. exports.getDateFormated = function (date) { const dayOfWeekName = exports.getDayOfWeekName(date); const day = date.getDate(); const monthName = exports.getMonthName(date.getMonth()); const year = date.getFullYear(); return `${dayOfWeekName} ${day} ${monthName} ${year} г.`; } exports.getDateFormatedShort = function (date) { const day = date.getDate(); const monthName = exports.getMonthName(date.getMonth()); return `${day} ${monthName}`; } exports.getTimeFomatted = function (date) { return date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Sofia' });//timeZone: 'local' } /*Todo: remove: toISOString slice(0, 10) getISODateFromDateTime > getISODateOnly */ exports.toLocalISO = function (date) { // Adjust for timezone and get ISO string without 'Z' const offset = date.getTimezoneOffset() * 60000; // offset in milliseconds return new Date(date.getTime() - offset).toISOString().slice(0, -1); }; exports.getISODateOnly = function (date) { // Use toLocalISO and get the date part only return this.toLocalISO(date).split('T')[0]; }; exports.getDateFromDateTime = function (day) { // Adjust for timezone and reset time to midnight var date = new Date(day); date.setHours(0, 0, 0, 0); const offset = date.getTimezoneOffset() * 60000; return new Date(date.getTime() - offset); }; // ########################################### // remove empty nodes from json // Function to check if an object is empty or contains only empty nodes exports.isEmpty = function (obj) { for (const key in obj) { if (typeof obj[key] === 'object') { if (!exports.isEmpty(obj[key])) { return false; } } else if (obj[key] !== '') { return false; } } return true; } // Function to remove empty nodes from a JSON object exports.jsonRemoveEmptyNodes = function (obj) { const newObj = {}; for (const key in obj) { if (typeof obj[key] === 'object') { const cleanedObj = exports.jsonRemoveEmptyNodes(obj[key]); if (!exports.isEmpty(cleanedObj)) { newObj[key] = cleanedObj; } } else if (obj[key] !== '') { newObj[key] = obj[key]; } } return newObj; } // #region string functions //input:Светослав и Сириел Георгиеви //output: Светослав Георгиев, Сириел Георгиев //input: Игор и Галина Москвини //output: Игор Москвин, Галина Москвин exports.separateFamilyMemberNames = function (namesArray) { logger.debug("separateFamilyMemberNames: " + namesArray); const result = []; // itteraate over all names for (let i = 0; i < namesArray.trim().split(' '); i++) { const name = namesArray[i]; // if name contains " и " then split it into two names // else just add it to the result if (name.includes(" и ")) { const namePairs = name.split(" и "); const firstNames = namePairs.map((pair) => pair.split(" ")[0]); // last name is only one. and it is shared for both firstnames const lastName = namePairs[1].split(" ")[1]; for (let i = 0; i < namePairs.length; i++) { let firstName = firstNames[i]; if (i === namePairs.length - 1 && namePairs.length % 2 !== 0) { result.push(`${firstName} ${lastName}`); } else { result.push(`${firstName} ${lastName}`); } } } else { result.push(name); } } //normalize names for (var i = 0; i < result.length; i++) { var name = result[i].trim(); //cut last letter of name if it is "a" or "и" (bulgarian feminine ending) if (name.endsWith("a") || name.endsWith("и")) { name = name.substring(0, name.length - 1); } result[i] = name; } return result; } exports.separateFamilyMemberNames2 = function (input) { const nameParts = input.trim().split(' '); let familyName = nameParts.pop(); const firstNames = nameParts.join(' ').split(' и '); const result = firstNames.map(firstName => `${firstName} ${familyName}`); return result; } exports.fuzzySearch = function (publishers, searchQuery, distanceThreshold = 0.9) { const lowerCaseSearchQuery = searchQuery?.toLowerCase(); let results = publishers .filter((p) => { try { const fullName = p.firstName.toLowerCase() + " " + p.lastName.toLowerCase(); const reversedFullName = p.lastName.toLowerCase() + " " + p.firstName.toLowerCase(); const distanceFullName = levenshtein.distance(fullName, lowerCaseSearchQuery); const distanceReversedFullName = levenshtein.distance(reversedFullName, lowerCaseSearchQuery); const similarityFullName = 1 - distanceFullName / Math.max(fullName.length, lowerCaseSearchQuery.length); const similarityReversedFullName = 1 - distanceReversedFullName / Math.max(reversedFullName.length, lowerCaseSearchQuery.length); // If total fullname length is less than 10 symbols, allow results that are 1 symbol difference if ((fullName.length < 10 || reversedFullName.length < 10) && (distanceFullName <= 1 || distanceReversedFullName <= 1)) { return true; } return similarityFullName >= distanceThreshold || similarityReversedFullName >= distanceThreshold } catch (e) { logger.error(e); console.log(e); return false; } }) .sort((a, b) => (a.similarity > b.similarity ? -1 : 1)); return (results && results.length > 0) ? results[0] : null; } exports.getCurrentNonthFormatted = function () { const getCurrentYearMonth = () => { const currentDate = new Date(); const year = currentDate.getFullYear(); const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Month is 0-indexed return `${year}-${month}`; } return getCurrentYearMonth(); } exports.getCurrentYearMonth = () => { const currentDate = new Date(); const year = currentDate.getFullYear(); const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Month is 0-indexed return `${year}-${month}`; } exports.getTimeFormated = function (date) { return this.formatTimeHHmm(date); } // format date to 'HH:mm' time string required by the time picker exports.formatTimeHHmm = function (input) { // Check if the input is a string or a Date object const date = (typeof input === 'string') ? new Date(input) : input; return date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Sofia' }).substring(0, 5); } //parse 'HH:mm' time string to date object exports.parseTimeHHmm = (timeString) => { // If timeString is already a date, return it as is if (timeString instanceof Date) { return timeString; } const [hours, minutes] = timeString.split(':'); const date = new Date(); date.setHours(hours); date.setMinutes(minutes); return date; } exports.setTimeHHmm = (date, timeStringOrHours) => { const newDate = new Date(date); if (typeof timeStringOrHours === 'string' && timeStringOrHours.includes(':')) { // If hours is a string in "HH:mm" format const [h, m] = timeStringOrHours.split(':'); newDate.setHours(parseInt(h, 10), parseInt(m, 10), 0, 0); } else { // If hours and minutes are provided separately newDate.setHours(parseInt(timeStringOrHours, 10), 0, 0, 0); } return newDate; }; exports.getTimeInMinutes = (dateOrTimestamp) => { const date = new Date(dateOrTimestamp); logger.debug("getTimeInMinutes: date = ", date); return date.getHours() * 60 + date.getMinutes(); }; exports.parseBool = function (value) { if (value === undefined) { return false; } value = value || "false"; const truthyValues = ['1', 'y', 'yes', 'true', 't']; return truthyValues.includes(String(value).toLowerCase()); } exports.getStartOfWeek = function (date) { const result = new Date(date); // create a copy of the input date // If the day is Sunday (0), we set it to -6, otherwise, subtract the current day of the week from 1 (for Monday) let daysToSubtract = result.getDay() === 0 ? 6 : result.getDay() - 1; result.setDate(result.getDate() - daysToSubtract); result.setHours(0, 0, 0, 0); // set time to midnight return result; } exports.getEndOfWeek = function (date) { const result = new Date(date); // If the day is Sunday (0 in `getDay()`), no addition is needed. Otherwise, add 7 minus the current day of the week. let daysToAdd = result.getDay() === 0 ? 0 : 7 - result.getDay(); result.setDate(result.getDate() + daysToAdd); result.setHours(23, 59, 59, 999); // set time to the last millisecond of the day return result; } exports.getStartOfMonth = function (date) { date = new Date(date); return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0); } exports.getEndOfMonth = function (date) { date = new Date(date); const result = new Date(date.getFullYear(), date.getMonth() + 1, 0); // 0th day of the next month result.setHours(23, 59, 59, 999); // set time to the last millisecond of the day return result; } exports.excelSerialDateToDate = function (serial) { // Set base date as 1899-12-31 (Excel's zero date, ignoring leap year bug) let baseDate = new Date(1899, 11, 31); // Extract date and time fractions let datePart = Math.floor(serial); let timePart = serial - datePart; // Compute the complete date let dateInMs = baseDate.getTime() + datePart * 24 * 60 * 60 * 1000; let timeInMs = timePart * 24 * 60 * 60 * 1000; // Construct and return the final date return new Date(dateInMs + timeInMs); } // Function to get a value from the session exports.getSessionValue = function (session, fieldName, defaultValue = null) { return session[fieldName] || defaultValue; }; // Function to update a value in the session exports.updateSessionValue = function (session, fieldName, value) { session[fieldName] = value; session.save(); // Save the session after updating }; exports.copyToClipboard = function (event, text) { let contentToCopy; if (event) { const spanElement = event.currentTarget; contentToCopy = spanElement.textContent; // Animation logic const originalText = spanElement.textContent; const originalColor = spanElement.style.backgroundColor; // Store the original color spanElement.textContent = "имейла копиран"; spanElement.style.backgroundColor = "blue"; // Change color to "info" (blue in this case) setTimeout(() => { spanElement.textContent = originalText; spanElement.style.backgroundColor = originalColor; // Reset the color back to original }, 3000); // Reset the text and color back to original after 3 seconds } else { // If event is null, use the provided text parameter if (!text) { console.error("No text provided to copy to clipboard"); return; // Exit the function if no text is provided } contentToCopy = text; } // Copy contentToCopy to clipboard const tempTextarea = document.createElement('textarea'); tempTextarea.value = contentToCopy; document.body.appendChild(tempTextarea); tempTextarea.select(); document.execCommand('copy'); document.body.removeChild(tempTextarea); } // exports.getUser = async function (req) { // // Use req if provided (server-side), otherwise call getSession without args (client-side) // const session = req ? await getSession({ req }) : await getSession(); // return session?.user; // } // exports.isUserInRole = function (req, allowedRoles = []) { // const user = exports.getUser(req); // // Check if the user is authenticated // if (!user) { // return false; // } // // If no specific roles are required, return true as the user is authenticated // if (allowedRoles.length === 0) { // return true; // } // // Check if the user's role is among the allowed roles // // Ensure role exists and is a valid UserRole // const userRole = user.role; // return allowedRoles.includes(userRole); // } // Utility functions for localStorage operations exports.setLocalStorage = function (key, value) { if (typeof window !== 'undefined') { window.localStorage.setItem(key, JSON.stringify(value)); } }; exports.getLocalStorage = function (key, defaultValue) { if (typeof window !== 'undefined') { const stored = window.localStorage.getItem(key); if (stored) { try { // Attempt to parse the stored JSON data return JSON.parse(stored); } catch (error) { // Handle parsing error console.error(`Error parsing JSON from localStorage for key '${key}':`, error); // Return defaultValue or perform other error handling as needed return defaultValue; } } } return defaultValue; }; exports.root = function (req) { return process.env.NEXT_PUBLIC_PUBLIC_URL; } exports.getInitials = function (names) { const parts = names.split(' '); return parts.map(part => part[0] + ".").join(''); } // ? import { addMinutes } from 'date-fns'; exports.addMinutes = function (date, minutes) { return new Date(date.getTime() + minutes * 60000); // 60000 milliseconds in a minute } /** * Recursively converts all Date objects in an object to ISO strings. * @param {Object} obj - The object to convert. * @returns {Object} - The new object with all Date objects converted to ISO strings. */ exports.convertDatesToISOStrings = function (obj) { if (obj === null || obj === undefined) { return obj; } if (obj instanceof Date) { return obj.toISOString(); } if (Array.isArray(obj)) { return obj.map(exports.convertDatesToISOStrings); } if (typeof obj === 'object') { const keys = Object.keys(obj); return keys.reduce((acc, key) => { acc[key] = exports.convertDatesToISOStrings(obj[key]); return acc; }, {}); } return obj; } // exports.getInitials = function (names) { // const parts = names.split(' '); // Split the full name into parts // if (parts.length === 0) { // return ''; // } // // Extract the first two letters of the first name // let initials = parts[0].substring(0, 2) + "."; // // If there is a last name, add the first letter of the last name // if (parts.length > 1) { // initials += parts[parts.length - 1][0] + "."; // } // return initials; // }