File: //home/arjun/projects/good-life-be/api/Event/utils.js
/* eslint-disable function-paren-newline */
/* eslint-disable implicit-arrow-linebreak */
import moment from 'moment';
import { Op } from 'sequelize';
import Category from '../../models/Category.js';
import EventModel from '../../models/Event.js';
export const assignColorToEvents = async (events) => {
const categories = await Category.findAll({
attributes: ['name', 'color', 'lightColor'],
raw: true,
});
// Create a map from category name to color
const categoryColorMap = categories.reduce(
(acc, { name, color, lightColor }) => {
acc[name] = { color, lightColor };
return acc;
},
{}
);
return events.map((event) => {
const categoryColors = categoryColorMap[event.category] || {}; // Get color set, default to empty object
return {
...event,
color: categoryColors.color || '#FFFFFF', // Default color
lightColor: categoryColors.lightColor || '#F0F0F0', // Default lightColor
};
});
};
export const groupEventsByWeek = async (eventsData) => {
const grouped = {};
eventsData.forEach((event) => {
// Determine the week start and end dates
const eventDate = moment(event.date);
const weekStart = moment(eventDate).startOf('week'); // Default: Sunday
const weekEnd = moment(eventDate).endOf('week');
// Check if the week spans two months
const isSameMonth = weekStart.month() === weekEnd.month();
// Format the week range accordingly
const weekRange = isSameMonth
? `${weekStart.format('MMMM YYYY')}`
: `${weekStart.format('MMMM YYYY')} - ${weekEnd.format('MMMM YYYY')}`;
// Initialize the group if not already present
if (!grouped[weekRange]) {
grouped[weekRange] = { events: [], weekStart, weekEnd };
}
// Convert start_time and end_time from "HH:mm:ss" to "hh:mm A"
const formattedEvent = {
...event,
start_time: moment(event.start_time, 'HH:mm:ss').format('hh:mm A'),
end_time: moment(event.end_time, 'HH:mm:ss').format('hh:mm A'),
};
// Add the event to the corresponding week range
grouped[weekRange].events.push(formattedEvent);
});
// Transform grouped object into an array format
return Object.keys(grouped).map((weekRange) => {
const { weekStart, weekEnd, events } = grouped[weekRange];
// Generate an array of week dates
const weekDates = [];
const currentDate = weekStart.clone();
while (currentDate.isSameOrBefore(weekEnd)) {
weekDates.push(currentDate.format('YYYY-MM-DD'));
currentDate.add(1, 'day');
}
const orderedWeekDates = weekDates.sort((a, b) =>
moment(a).isBefore(moment(b)) ? -1 : 1
);
return {
weekRange,
hours: [
'12:00 AM',
'01:00 AM',
'02:00 AM',
'03:00 AM',
'04:00 AM',
'05:00 AM',
'06:00 AM',
'07:00 AM',
'08:00 AM',
'09:00 AM',
'10:00 AM',
'11:00 AM',
'12:00 PM',
'01:00 PM',
'02:00 PM',
'03:00 PM',
'04:00 PM',
'05:00 PM',
'06:00 PM',
'07:00 PM',
'08:00 PM',
'09:00 PM',
'10:00 PM',
'11:00 PM',
],
weekDates: orderedWeekDates, // Add weekDates array here
events,
};
});
};
const hours = [
'12:00 AM',
'01:00 AM',
'02:00 AM',
'03:00 AM',
'04:00 AM',
'05:00 AM',
'06:00 AM',
'07:00 AM',
'08:00 AM',
'09:00 AM',
'10:00 AM',
'11:00 AM',
'12:00 PM',
'01:00 PM',
'02:00 PM',
'03:00 PM',
'04:00 PM',
'05:00 PM',
'06:00 PM',
'07:00 PM',
'08:00 PM',
'09:00 PM',
'10:00 PM',
'11:00 PM',
];
export const groupEventsByDay = async (eventsData) => {
const rowHeight = 60; // Each row height in pixels
const grouped = {};
function truncateText(text, maxLength) {
if (text.length <= maxLength) return text;
const trimmedText = text.substring(0, maxLength).trim();
const lastSpaceIndex = trimmedText.lastIndexOf(' ');
// If no space found, return full trimmed text
if (lastSpaceIndex === -1) return trimmedText;
return `${trimmedText.substring(0, lastSpaceIndex)}...`;
}
eventsData.forEach((event) => {
const eventDate = moment(event.date).format('YYYY-MM-DD');
if (!grouped[eventDate]) {
grouped[eventDate] = {
date: eventDate,
dayFormat: moment(event.date).format('MMMM-DD-YYYY'),
events: [],
hours,
};
}
const startMoment = moment(event.start_time, 'hh:mm A');
const endMoment = moment(event.end_time, 'hh:mm A');
const startMinutes = startMoment.minutes();
const endMinutes = endMoment.minutes();
const startHour = startMoment.hours();
const endHour = endMoment.hours();
// Calculate percentage within the hour
const startPercentage = Math.floor((startMinutes / 60) * 100);
const endPercentage = Math.floor(
startPercentage +
(endHour - startHour) * 100 +
((endMinutes - startMinutes) / 60) * 100
);
// Convert percentage to pixels
let height = Math.floor(
((endPercentage - startPercentage) / 100) * rowHeight
);
height = height <= 15 ? 17 : height;
// Calculate top position
const top = Math.floor((startPercentage / 100) * rowHeight);
// top = height <= 15 ? top - 15 : top;
const formattedEvent = {
...event,
event_title: truncateText(event.event_title, 40),
start_time: startMoment.format('hh:mm A'),
end_time: endMoment.format('hh:mm A'),
heightPercentage: [startPercentage, endPercentage],
eventHour: startMoment.format('HH'),
height: height - 2, // Height in pixels
top, // Position from the top in pixels
};
grouped[eventDate].events.push(formattedEvent);
});
const returnss = Object.values(grouped).sort((a, b) =>
moment(a.date).diff(moment(b.date))
);
// const groupEventsByHour = (data) =>
// data.map((day) => {
// let same = [];
// let different = [];
// const groupeds = {};
// // Group events by hour
// day.events.forEach((event) => {
// const eventHour = `${event.start_time.split(':')[0]}:00 ${
// event.start_time.split(' ')[1]
// }`;
// if (!groupeds[eventHour]) {
// groupeds[eventHour] = [];
// }
// groupeds[eventHour].push(event);
// });
// // Separate into "same" and "different"
// Object.values(groupeds).forEach((eventGroup) => {
// if (eventGroup.length > 1) {
// same = [...same, ...eventGroup];
// } else {
// different = [...different, ...eventGroup];
// }
// });
// return {
// ...day,
// events: { same, different },
// };
// });
// const groupedEventsData = groupEventsByHour(returnss);
const parseTime = (timeStr) => {
const [time, modifier] = timeStr.split(' ');
let [hours, minutes] = time.split(':').map(Number);
if (modifier === 'PM' && hours !== 12) hours += 12;
if (modifier === 'AM' && hours === 12) hours = 0;
return { hours, minutes, raw: timeStr };
};
const isAnyTimeWithinBounds = (startTime, endTime, boundStart, boundEnd) => {
const start = parseTime(startTime);
const end = parseTime(endTime);
const boundS = parseTime(boundStart);
const boundE = parseTime(boundEnd);
return (
(start.hours < boundE.hours && end.hours > boundS.hours) ||
(start.hours === boundS.hours && start.minutes >= boundS.minutes) ||
(end.hours === boundE.hours && end.minutes <= boundE.minutes) ||
(start.hours < boundE.hours &&
end.hours === boundE.hours &&
end.minutes > boundS.minutes) ||
(start.hours < boundS.hours && end.hours >= boundS.hours) // Ensures overlap into next hour
);
};
const groupEventsByHours = (data) =>
data.map((day) => {
const formattedHours = day.hours
.slice(0, -1)
.map((hour, i) => [hour, day.hours[i + 1]]);
const groupedEvents = {};
formattedHours.forEach(([startHour, endHour], index) => {
day.events.forEach((event) => {
if (
isAnyTimeWithinBounds(
event.start_time,
event.end_time,
startHour,
endHour
)
) {
const key = startHour;
const hasAlreadyBeenAdded = Object.values(groupedEvents ?? {}).find(
(e) =>
e?.some(
(each) =>
each?.event_title === event?.event_title && !each?.isHidden
)
);
const findIndexs = hasAlreadyBeenAdded?.findIndex(
(e) => e?.event_title === event?.event_title
);
const wasAlreadyAddedToEarlierInterval = Object.entries(
groupedEvents
).some(([hourKey, events]) =>
events.some(
(e) =>
e.event_title === event.event_title &&
e.start_time === event.start_time &&
e.end_time === key // Prevent if end time matches the start of this interval
)
);
if (wasAlreadyAddedToEarlierInterval) {
return; // Skip adding this event to avoid duplication in a later interval
}
if (findIndexs !== 1) {
groupedEvents[key] = [
...(groupedEvents[key] || []),
hasAlreadyBeenAdded ? { ...event, isHidden: true } : event,
];
groupedEvents[key] = groupedEvents[key].sort((a, b) =>
a?.isHidden === b?.isHidden ? 0 : a?.isHidden ? -1 : 1
);
}
}
});
});
return { ...day, groupedEvents };
});
const groupedEventsData = groupEventsByHours(returnss);
return groupedEventsData;
};
export const eventUpdate = async (ids, data) => {
await EventModel.update(data, { where: { id: ids } });
};
export const MatchingEvents = async (event, userid) => {
const matchingEvents = await EventModel.findAll({
where: {
category: event.category,
subCategory: event.subCategory,
event_title: event.event_title,
user_id: userid,
status: 'active',
[Op.and]: [
{ start_time: event.start_time }, // Direct comparison with start_time
{ end_time: event.end_time }, // Direct comparison with end_time
],
},
attributes: ['id', 'recurring'], // Fetch only the IDs for performance
raw: true,
});
return matchingEvents;
};
const updateConflict = async (ids) => {
if (ids?.length) {
await EventModel.update({ conflict: false }, { where: { id: ids } });
}
};
export const findAndEditOverlapEvents = async (body, type, user) => {
if (type === 'selected') {
for (const eachData of body.overlapping_events) {
await updateConflict(eachData?.id);
}
}
if (type === 'all') {
for (const eachData of body.overlapping_events) {
const events = await EventModel.findAll({
where: {
user_id: user.id,
event_title: eachData.title,
start_time: eachData.start_time,
end_time: eachData.end_time,
},
});
const recurringEventIds = events
.filter((e) => !e.recurring)
.map((e) => e.id);
await updateConflict(recurringEventIds);
}
}
};
const incrementDate = (dateStr) =>
moment(dateStr, 'YYYY-MM-DD').add(1, 'days').format('YYYY-MM-DD');
export const processEventsByday = (eventsData) => {
const groupedEvents = [];
eventsData.forEach((event) => {
const eventDate = event.date;
const startTime = moment(event.start_time, 'HH:mm:ss');
const endTime = moment(event.end_time, 'HH:mm:ss');
// If the event spans across midnight, split it into two
if (endTime.isBefore(startTime)) {
// First part: Ends at "23:59" on the same day
const eventPart1 = {
...event,
end_time: '23:59:00',
};
// Second part: Starts at "00:00" the next day
const nextDate = incrementDate(eventDate);
const eventPart2 = {
...event,
date: nextDate,
start_time: '00:00:00',
};
groupedEvents.push(eventPart1, eventPart2);
} else {
// If the event does not cross midnight, just add it normally
groupedEvents.push(event);
}
});
// Return the final sorted result inside an "events" object
return {
events: groupedEvents.sort((a, b) => moment(a.date).diff(moment(b.date))),
};
};