340 lines
12 KiB
JavaScript
340 lines
12 KiB
JavaScript
htmx.defineExtension('disable-submit', {
|
|
onEvent: function (name, evt, data) {
|
|
/*
|
|
let elt = evt.detail.elt;
|
|
let result = elt.querySelectorAll('.hx-disable');
|
|
|
|
if (name === 'htmx:beforeRequest') {
|
|
result.forEach(element => element.disabled = true);
|
|
if (elt.classList.contains('hx-disable')) {
|
|
elt.disabled = true;}
|
|
|
|
} else if(name == 'htmx:afterRequest') {
|
|
result.forEach(element => element.disabled = false);
|
|
if (elt.classList.contains('hx-disable')) elt.disabled = false;
|
|
}
|
|
*/
|
|
}
|
|
})
|
|
|
|
htmx.defineExtension('rename-params', {
|
|
onEvent: function(name , evt) {
|
|
if (name === "htmx:configRequest") {
|
|
var normal = evt.detail.elt.getAttribute("hx-rename-params");
|
|
if (normal) {
|
|
normal = JSON.parse(normal);
|
|
for (const [key, value] of Object.entries(normal)) {
|
|
console.log(evt.detail.parameters)
|
|
evt.detail.parameters[value] = evt.detail.parameters[key];
|
|
delete evt.detail.parameters[key];
|
|
}
|
|
}
|
|
var exclusive = evt.detail.elt.getAttribute("hx-rename-params-ex");
|
|
if (exclusive) {
|
|
exclusive = JSON.parse(exclusive);
|
|
var params = {};
|
|
for (const [key, value] of Object.entries(exclusive)) {
|
|
var v = evt.detail.parameters[key]
|
|
if (v) {
|
|
params[value] = v;
|
|
}
|
|
}
|
|
evt.detail.parameters = params;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
htmx.defineExtension('reactive-trigger', {
|
|
|
|
onEvent: function(name , evt) {
|
|
if (name === "htmx:beforeProcessNode") {
|
|
var element = evt.detail.elt;
|
|
var reactiveTrigger = element.getAttribute("hx-reactive-trigger");
|
|
if (reactiveTrigger) {
|
|
reactiveTrigger = JSON.parse(reactiveTrigger);
|
|
var reactiveSource = element.getAttribute("hx-reactive-source");
|
|
reactiveSource = reactiveSource ? document.querySelector(reactiveSource) : element.closest("form");
|
|
|
|
// var triggerString = Object.keys(reactiveTrigger).map(k => "change from:#" + reactiveSource.getAttribute("id") + " [name=\"" + k + "\"]").join(", ");
|
|
var triggerString = reactiveTrigger.map(k => "change from:" + "[name=\"" + k + "\"]").join(", ");
|
|
element.setAttribute("hx-trigger", triggerString);
|
|
}
|
|
} else if (name=="htmx:configRequest") {
|
|
var element = evt.detail.elt;
|
|
var reactiveTrigger = element.getAttribute("hx-reactive-trigger");
|
|
if (reactiveTrigger) {
|
|
reactiveTrigger = JSON.parse(reactiveTrigger);
|
|
var reactiveSource = element.getAttribute("hx-reactive-source");
|
|
reactiveSource = reactiveSource ? document.querySelector(reactiveSource) : element.closest("form");
|
|
for (key of reactiveTrigger) {
|
|
console.log( reactiveSource.querySelector(`[name="${key}"]`).value)
|
|
evt.detail.parameters[key] = reactiveSource.querySelector(`[name="${key}"]`).value || "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function deepEqual(obj1, obj2) {
|
|
if (obj1 === obj2) {
|
|
return true; // Check for equality
|
|
}
|
|
|
|
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) {
|
|
return false; // Check if both are objects
|
|
}
|
|
|
|
const keys1 = Object.keys(obj1);
|
|
const keys2 = Object.keys(obj2);
|
|
|
|
if (keys1.length !== keys2.length) {
|
|
return false; // Check if they have the same number of properties
|
|
}
|
|
|
|
for (const key of keys1) {
|
|
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
|
|
return false; // Recursively compare properties
|
|
}
|
|
}
|
|
|
|
return true; // Objects are deeply equal
|
|
}
|
|
|
|
htmx.defineExtension('trigger-filter', {
|
|
|
|
onEvent: function(name , evt) {
|
|
if (name=="htmx:beforeRequest") {
|
|
var element = evt.detail.elt;
|
|
if (!deepEqual(element.lastParams, evt.detail.requestConfig.parameters)) {
|
|
element.lastParams = evt.detail.requestConfig.parameters;
|
|
} else {
|
|
console.log("unchanged")
|
|
evt.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
initDatepicker = function(elem) {
|
|
const modalParent = elem.closest('#modal-content');
|
|
if (modalParent) {
|
|
console.log('datepicker in modal')
|
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: true, container: "#modal-content .modal-card"});
|
|
} else {
|
|
console.log('datepicker normal')
|
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: true});
|
|
}
|
|
|
|
}
|
|
|
|
function formatDateMMDDYYYY(date) {
|
|
const month = String(date.getMonth() + 1).padStart(2, '0'); // Get month (1-12), pad with leading zero if necessary
|
|
const day = String(date.getDate()).padStart(2, '0'); // Get day (1-31), pad with leading zero if necessary
|
|
const year = date.getFullYear(); // Get full year
|
|
|
|
return `${month}/${day}/${year}`;
|
|
}
|
|
|
|
function parseMMDDYYYY(dateString) {
|
|
const parts = dateString.split('/');
|
|
if (parts.length !== 3) {
|
|
throw new Error('Invalid date format. Expected mm/dd/yyyy');
|
|
}
|
|
const month = parseInt(parts[0], 10) - 1; // Months are zero-based in JavaScript
|
|
const day = parseInt(parts[1], 10);
|
|
const year = parseInt(parts[2], 10);
|
|
|
|
return new Date(year, month, day);
|
|
}
|
|
|
|
const getFourWeekPeriods = endDate => {
|
|
if (!endDate) {
|
|
endDate= formatDateMMDDYYYY(new Date())
|
|
}
|
|
let periods = [endDate];
|
|
for (let i = 0; i < 13; i++) {
|
|
const currentDate = new Date(parseMMDDYYYY(endDate).getTime());
|
|
currentDate.setDate(currentDate.getDate() - 28 * (i + 1));
|
|
periods.push(formatDateMMDDYYYY(new Date(currentDate)));
|
|
}
|
|
return periods;
|
|
};
|
|
|
|
function getFourWeekPeriodsPeriods(endDate) {
|
|
// Determine today's date based on data or current local date
|
|
const today = endDate ? parseMMDDYYYY(endDate) : new Date();
|
|
|
|
// Calculate the total period
|
|
const totalStart = dateFns.addDays(dateFns.subWeeks(today, 13 * 4), 1);
|
|
|
|
// Construct the array of periods
|
|
return [
|
|
{
|
|
start: dateFns.format(totalStart, 'MM/dd/yyyy'),
|
|
end: dateFns.format(today, 'MM/dd/yyyy'),
|
|
title: "Total"
|
|
},
|
|
...Array.from({ length: 13 }, (_, i) => ({
|
|
start: dateFns.format(dateFns.addDays(dateFns.subWeeks(today, (i + 1) * 4), 1), 'MM/dd/yyyy'),
|
|
end: dateFns.format(dateFns.subWeeks(today, i * 4), 'MM/dd/yyyy')
|
|
}))
|
|
];
|
|
}
|
|
|
|
const withLastYear = (date) => {
|
|
|
|
if (!date) {
|
|
date= new Date()
|
|
} else {
|
|
date = parseMMDDYYYY(date)
|
|
}
|
|
const originalYear = date.getFullYear();
|
|
const priorYear = originalYear - 1;
|
|
|
|
// Create new Date objects for both years
|
|
const originalDate = new Date(originalYear, date.getMonth(), date.getDate());
|
|
const priorYearDate = new Date(priorYear, date.getMonth(), date.getDate());
|
|
|
|
return [formatDateMMDDYYYY(originalDate), formatDateMMDDYYYY(priorYearDate)];
|
|
}
|
|
|
|
const lastYearPeriod = (date) => {
|
|
if (!date) {
|
|
date= new Date()
|
|
} else {
|
|
date = parseMMDDYYYY(date)
|
|
}
|
|
const originalYear = date.getFullYear();
|
|
const priorYear = originalYear - 1;
|
|
|
|
// Create new Date objects for both years
|
|
const originalDate = new Date(originalYear, date.getMonth(), date.getDate());
|
|
const priorYearDate = dateFns.addDays(new Date(priorYear, date.getMonth(), date.getDate()), 1);
|
|
|
|
return {end: formatDateMMDDYYYY(originalDate), start: formatDateMMDDYYYY(priorYearDate)};
|
|
}
|
|
|
|
const calendarYearPeriod = (date) => {
|
|
if (!date) {
|
|
date= new Date()
|
|
} else {
|
|
date = parseMMDDYYYY(date)
|
|
}
|
|
const originalYear = date.getFullYear();
|
|
const priorYear = originalYear - 1;
|
|
|
|
// Create new Date objects for both years
|
|
const start = new Date(originalYear, 0, 1);
|
|
const end = new Date(originalYear, 11, 31);
|
|
|
|
return {end: formatDateMMDDYYYY(end), start: formatDateMMDDYYYY(start)};
|
|
}
|
|
|
|
const getLastMonthPeriods = (date) => {
|
|
if (!date) {
|
|
date = new Date();
|
|
} else {
|
|
date = parseMMDDYYYY(date);
|
|
}
|
|
|
|
// Get the first day of the current month
|
|
const firstDayOfCurrentMonth = new Date(date.getFullYear(), date.getMonth(), 1);
|
|
|
|
// Get the last day of the previous month
|
|
const lastDayOfPreviousMonth = dateFns.subDays(firstDayOfCurrentMonth, 1);
|
|
|
|
// Get the first day of the previous month
|
|
const firstDayOfPreviousMonth = new Date(lastDayOfPreviousMonth.getFullYear(), lastDayOfPreviousMonth.getMonth(), 1);
|
|
|
|
// Get the same period for the previous year
|
|
const firstDayOfPreviousYearMonth = new Date(firstDayOfPreviousMonth.getFullYear() - 1, firstDayOfPreviousMonth.getMonth(), 1);
|
|
const lastDayOfPreviousYearMonth = dateFns.lastDayOfMonth(firstDayOfPreviousYearMonth);
|
|
|
|
// Create period objects
|
|
const currentPeriod = {
|
|
start: formatDateMMDDYYYY(firstDayOfPreviousMonth),
|
|
end: formatDateMMDDYYYY(lastDayOfPreviousMonth)
|
|
};
|
|
|
|
const previousYearPeriod = {
|
|
start: formatDateMMDDYYYY(firstDayOfPreviousYearMonth),
|
|
end: formatDateMMDDYYYY(lastDayOfPreviousYearMonth)
|
|
};
|
|
|
|
return [currentPeriod, previousYearPeriod];
|
|
};
|
|
|
|
initMultiDatepicker = function(elem, startingValue) {
|
|
const modalParent = elem.closest('#modal-content');
|
|
if (modalParent) {
|
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, container: "#modal-content .modal-card", maxNumberOfDates: 12, dateDelimiter: ", "});
|
|
} else {
|
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, maxNumberOfDates: 12, dateDelimiter: ", "});
|
|
}
|
|
}
|
|
|
|
initCalendar = function(elem, startingValue) {
|
|
const modalParent = elem.closest('#modal-content');
|
|
if (modalParent) {
|
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, container: "#modal-content .modal-card"});
|
|
} else {
|
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, });
|
|
}
|
|
}
|
|
|
|
|
|
destroyDatepicker = function(dp) {
|
|
try {
|
|
dp.destroy()
|
|
} catch {
|
|
|
|
}
|
|
}
|
|
|
|
countRows = function(id) {
|
|
var table = document.querySelector(id);
|
|
var rows = table.querySelectorAll("tbody tr");
|
|
return rows.length;
|
|
}
|
|
|
|
htmx.onLoad(function(content) {
|
|
var sortables = content.querySelectorAll(".sortable");
|
|
for (var i = 0; i < sortables.length; i++) {
|
|
var sortable = sortables[i];
|
|
var sortableInstance = new Sortable(sortable, {
|
|
animation: 150,
|
|
ghostClass: 'bg-blue-100',
|
|
|
|
// Make the `.htmx-indicator` unsortable
|
|
filter: ".htmx-indicator",
|
|
onMove: function (evt) {
|
|
return evt.related.className.indexOf('htmx-indicator') === -1;
|
|
},
|
|
|
|
// Disable sorting on the `end` event
|
|
onEnd: function (evt) {
|
|
this.option("disabled", true);
|
|
}
|
|
});
|
|
|
|
// Re-enable sorting on the `htmx:afterSwap` event
|
|
sortable.addEventListener("htmx:afterSwap", function() {
|
|
sortableInstance.option("disabled", false);
|
|
});
|
|
}
|
|
})
|
|
|
|
async function copyToClipboard(text) {
|
|
try {
|
|
// Write the text to the clipboard
|
|
await navigator.clipboard.writeText(text);
|
|
console.log('Text copied to clipboard:', text);
|
|
} catch (err) {
|
|
console.error('Failed to copy text to clipboard:', err);
|
|
}
|
|
}
|
|
|