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 { 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 = new Date(priorYear, date.getMonth(), date.getDate()); 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)}; } 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); } }