From a063d5a2c932c42eba91941aea3c8bfe6e9b5bdf Mon Sep 17 00:00:00 2001 From: Deco Vander Date: Fri, 4 Jul 2025 13:22:17 -0400 Subject: [PATCH] Create shared utility module to eliminate function duplication - Create public/utils.js with shared frontend utility functions - Extract parseUTCDate, getTimeAgo, getTimeRemaining, getRemainingClass to utils.js - Remove duplicate functions from admin.js, app-mapbox.js, app-google.js, and app.js - Add utils.js script import to index.html and admin.html - Add comprehensive JSDoc documentation for all utility functions - Ensure consistent UTC timestamp parsing across all frontend scripts This addresses Copilot AI feedback about function duplication across multiple frontend scripts. Now all timestamp and time calculation logic is centralized in one maintainable module. Benefits: - Single source of truth for time-related utilities - Easier maintenance and updates - Consistent behavior across all frontend components - Better code organization and documentation - Reduced bundle size through deduplication --- public/admin.html | 1 + public/admin.js | 23 +---------- public/app-google.js | 20 +-------- public/app-mapbox.js | 62 +--------------------------- public/app.js | 20 +-------- public/index.html | 1 + public/utils.js | 96 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 103 insertions(+), 120 deletions(-) create mode 100644 public/utils.js diff --git a/public/admin.html b/public/admin.html index 73bf0f9..9f3b853 100644 --- a/public/admin.html +++ b/public/admin.html @@ -558,6 +558,7 @@ + diff --git a/public/admin.js b/public/admin.js index 5a20dce..a616e0d 100644 --- a/public/admin.js +++ b/public/admin.js @@ -492,28 +492,7 @@ document.addEventListener('DOMContentLoaded', () => { } }; - function getTimeAgo(timestamp) { - const now = new Date(); - // Ensure timestamp is treated as UTC if it doesn't have timezone info - const reportTime = parseUTCDate(timestamp); - const diffInMinutes = Math.floor((now - reportTime) / (1000 * 60)); - - if (diffInMinutes < 1) return 'just now'; - if (diffInMinutes < 60) return `${diffInMinutes} minute${diffInMinutes !== 1 ? 's' : ''} ago`; - - const diffInHours = Math.floor(diffInMinutes / 60); - if (diffInHours < 24) return `${diffInHours} hour${diffInHours !== 1 ? 's' : ''} ago`; - - const diffInDays = Math.floor(diffInHours / 24); - if (diffInDays < 7) return `${diffInDays} day${diffInDays !== 1 ? 's' : ''} ago`; - - return reportTime.toLocaleDateString(); - } - - // Helper function to parse UTC date - function parseUTCDate(timestamp) { - return new Date(timestamp.includes('T') ? timestamp : timestamp + 'Z'); - } + // getTimeAgo and parseUTCDate functions are now available from utils.js // Profanity management functions async function loadProfanityWords() { diff --git a/public/app-google.js b/public/app-google.js index 1db2981..411efe8 100644 --- a/public/app-google.js +++ b/public/app-google.js @@ -82,25 +82,7 @@ document.addEventListener('DOMContentLoaded', async () => { }); }; - // Helper function to parse UTC date - const parseUTCDate = (timestamp) => { - return new Date(timestamp.includes('T') ? timestamp : timestamp + 'Z'); - }; - - const getTimeAgo = (timestamp) => { - const now = new Date(); - // Ensure timestamp is treated as UTC if it doesn't have timezone info - const reportTime = parseUTCDate(timestamp); - const diffInMinutes = Math.floor((now - reportTime) / (1000 * 60)); - - if (diffInMinutes < 1) return 'just now'; - if (diffInMinutes < 60) return `${diffInMinutes} minute${diffInMinutes !== 1 ? 's' : ''} ago`; - - const diffInHours = Math.floor(diffInMinutes / 60); - if (diffInHours < 24) return `${diffInHours} hour${diffInHours !== 1 ? 's' : ''} ago`; - - return 'over a day ago'; - }; + // getTimeAgo function is now available from utils.js const refreshLocations = () => { fetch('/api/locations') diff --git a/public/app-mapbox.js b/public/app-mapbox.js index dcff11f..55d74d6 100644 --- a/public/app-mapbox.js +++ b/public/app-mapbox.js @@ -93,25 +93,7 @@ document.addEventListener('DOMContentLoaded', async () => { }); }; - // Helper function to parse UTC date - const parseUTCDate = (timestamp) => { - return new Date(timestamp.includes('T') ? timestamp : timestamp + 'Z'); - }; - - const getTimeAgo = (timestamp) => { - const now = new Date(); - // Ensure timestamp is treated as UTC if it doesn't have timezone info - const reportTime = parseUTCDate(timestamp); - const diffInMinutes = Math.floor((now - reportTime) / (1000 * 60)); - - if (diffInMinutes < 1) return 'just now'; - if (diffInMinutes < 60) return `${diffInMinutes} minute${diffInMinutes !== 1 ? 's' : ''} ago`; - - const diffInHours = Math.floor(diffInMinutes / 60); - if (diffInHours < 24) return `${diffInHours} hour${diffInHours !== 1 ? 's' : ''} ago`; - - return 'over a day ago'; - }; + // getTimeAgo function is now available from utils.js // Toggle between map and table view const switchView = (viewType) => { @@ -171,47 +153,7 @@ document.addEventListener('DOMContentLoaded', async () => { tableLocationCount.textContent = `${locations.length} active report${locations.length !== 1 ? 's' : ''}`; }; - // Calculate time remaining until 48-hour expiration - const getTimeRemaining = (timestamp, persistent = false) => { - if (persistent) { - return 'Persistent'; - } - - const now = new Date(); - // Ensure timestamp is treated as UTC if it doesn't have timezone info - const reportTime = parseUTCDate(timestamp); - const expirationTime = new Date(reportTime.getTime() + 48 * 60 * 60 * 1000); - const remaining = expirationTime - now; - - if (remaining <= 0) return 'Expired'; - - const hours = Math.floor(remaining / (1000 * 60 * 60)); - const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60)); - - if (hours > 0) { - return `${hours}h ${minutes}m`; - } else { - return `${minutes}m`; - } - }; - - // Get CSS class for time remaining - const getRemainingClass = (timestamp, persistent = false) => { - if (persistent) { - return 'normal'; // Use normal styling for persistent reports - } - - const now = new Date(); - // Ensure timestamp is treated as UTC if it doesn't have timezone info - const reportTime = parseUTCDate(timestamp); - const expirationTime = new Date(reportTime.getTime() + 48 * 60 * 60 * 1000); - const remaining = expirationTime - now; - const hoursRemaining = remaining / (1000 * 60 * 60); - - if (hoursRemaining <= 1) return 'urgent'; - if (hoursRemaining <= 6) return 'warning'; - return 'normal'; - }; + // getTimeRemaining and getRemainingClass functions are now available from utils.js const refreshLocations = () => { fetch('/api/locations') diff --git a/public/app.js b/public/app.js index 84d0933..23d1c07 100644 --- a/public/app.js +++ b/public/app.js @@ -74,25 +74,7 @@ document.addEventListener('DOMContentLoaded', () => { }); }; - // Helper function to parse UTC date - const parseUTCDate = (timestamp) => { - return new Date(timestamp.includes('T') ? timestamp : timestamp + 'Z'); - }; - - const getTimeAgo = (timestamp) => { - const now = new Date(); - // Ensure timestamp is treated as UTC if it doesn't have timezone info - const reportTime = parseUTCDate(timestamp); - const diffInMinutes = Math.floor((now - reportTime) / (1000 * 60)); - - if (diffInMinutes < 1) return 'just now'; - if (diffInMinutes < 60) return `${diffInMinutes} minute${diffInMinutes !== 1 ? 's' : ''} ago`; - - const diffInHours = Math.floor(diffInMinutes / 60); - if (diffInHours < 24) return `${diffInHours} hour${diffInHours !== 1 ? 's' : ''} ago`; - - return 'over a day ago'; - }; + // getTimeAgo function is now available from utils.js const refreshLocations = () => { fetch('/api/locations') diff --git a/public/index.html b/public/index.html index eeb5baf..61bbf3c 100644 --- a/public/index.html +++ b/public/index.html @@ -109,6 +109,7 @@ placeholder="Enter address, intersection (e.g., Main St & Second St, City), or l + diff --git a/public/utils.js b/public/utils.js new file mode 100644 index 0000000..3a0427b --- /dev/null +++ b/public/utils.js @@ -0,0 +1,96 @@ +/** + * Shared utility functions for the Great Lakes Ice Report frontend + */ + +/** + * Helper function to parse UTC date consistently across all frontend scripts + * Ensures timestamp is treated as UTC if it doesn't have timezone info + * @param {string} timestamp - The timestamp string to parse + * @returns {Date} - Parsed Date object with proper UTC interpretation + */ +const parseUTCDate = (timestamp) => { + return new Date(timestamp.includes('T') ? timestamp : timestamp + 'Z'); +}; + +/** + * Calculate human-readable time ago string + * @param {string} timestamp - The timestamp to calculate from + * @returns {string} - Human-readable time difference + */ +const getTimeAgo = (timestamp) => { + const now = new Date(); + const reportTime = parseUTCDate(timestamp); + const diffInMinutes = Math.floor((now - reportTime) / (1000 * 60)); + + if (diffInMinutes < 1) return 'just now'; + if (diffInMinutes < 60) return `${diffInMinutes} minute${diffInMinutes !== 1 ? 's' : ''} ago`; + + const diffInHours = Math.floor(diffInMinutes / 60); + if (diffInHours < 24) return `${diffInHours} hour${diffInHours !== 1 ? 's' : ''} ago`; + + const diffInDays = Math.floor(diffInHours / 24); + if (diffInDays < 7) return `${diffInDays} day${diffInDays !== 1 ? 's' : ''} ago`; + + return reportTime.toLocaleDateString(); +}; + +/** + * Calculate time remaining until 48-hour expiration + * @param {string} timestamp - The creation timestamp + * @param {boolean} persistent - Whether the report is persistent + * @returns {string} - Time remaining string + */ +const getTimeRemaining = (timestamp, persistent = false) => { + if (persistent) { + return 'Persistent'; + } + + const now = new Date(); + const reportTime = parseUTCDate(timestamp); + const expirationTime = new Date(reportTime.getTime() + 48 * 60 * 60 * 1000); + const remaining = expirationTime - now; + + if (remaining <= 0) return 'Expired'; + + const hours = Math.floor(remaining / (1000 * 60 * 60)); + const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60)); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } else { + return `${minutes}m`; + } +}; + +/** + * Get CSS class for time remaining styling + * @param {string} timestamp - The creation timestamp + * @param {boolean} persistent - Whether the report is persistent + * @returns {string} - CSS class name + */ +const getRemainingClass = (timestamp, persistent = false) => { + if (persistent) { + return 'normal'; // Use normal styling for persistent reports + } + + const now = new Date(); + const reportTime = parseUTCDate(timestamp); + const expirationTime = new Date(reportTime.getTime() + 48 * 60 * 60 * 1000); + const remaining = expirationTime - now; + const hoursRemaining = remaining / (1000 * 60 * 60); + + if (hoursRemaining <= 1) return 'urgent'; + if (hoursRemaining <= 6) return 'warning'; + return 'normal'; +}; + +// Export functions for module usage (if using ES6 modules in the future) +// For now, functions are available globally when script is included +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + parseUTCDate, + getTimeAgo, + getTimeRemaining, + getRemainingClass + }; +}