diff --git a/frontend/src/components/AddProductModal.tsx b/frontend/src/components/AddProductModal.tsx index 927e030..61d285a 100644 --- a/frontend/src/components/AddProductModal.tsx +++ b/frontend/src/components/AddProductModal.tsx @@ -156,20 +156,33 @@ const AddProductModal: React.FC = ({ isOpen, onClose, onPr } // Validate date constraints - const validFromDate = new Date(formData.valid_from); - const today = new Date(currentDate); - - if (validFromDate > today) { - setError('Valid from date cannot be in the future'); - return; - } - - if (editProduct) { - const minDate = new Date(minValidFromDate); - if (validFromDate <= minDate) { - setError(`Valid from date must be after the current product's valid from date (${minValidFromDate})`); + try { + const validFromDate = new Date(formData.valid_from); + if (isNaN(validFromDate.getTime())) { + setError('Please enter a valid date'); return; } + + if (currentDate) { + const today = new Date(currentDate); + if (!isNaN(today.getTime()) && validFromDate > today) { + setError('Valid from date cannot be in the future'); + return; + } + } + + if (editProduct && minValidFromDate) { + // Only validate if minValidFromDate is set and valid + const minDate = new Date(minValidFromDate); + if (!isNaN(minDate.getTime()) && validFromDate <= minDate) { + setError(`Valid from date must be after the current product's valid from date (${minValidFromDate})`); + return; + } + } + } catch (dateError) { + console.error('Date validation error:', dateError); + setError('Please enter a valid date'); + return; } try { @@ -315,10 +328,17 @@ const AddProductModal: React.FC = ({ isOpen, onClose, onPr value={formData.valid_from} onChange={handleChange} required - min={editProduct ? (() => { - const nextDay = new Date(minValidFromDate); - nextDay.setDate(nextDay.getDate() + 1); - return nextDay.toISOString().split('T')[0]; + min={editProduct && minValidFromDate ? (() => { + try { + const nextDay = new Date(minValidFromDate); + if (!isNaN(nextDay.getTime())) { + nextDay.setDate(nextDay.getDate() + 1); + return nextDay.toISOString().split('T')[0]; + } + } catch (error) { + console.error('Error calculating min date:', error); + } + return undefined; })() : undefined} max={currentDate} className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx index 6076feb..2e5dec1 100644 --- a/frontend/src/components/Dashboard.tsx +++ b/frontend/src/components/Dashboard.tsx @@ -6,7 +6,21 @@ import { shoppingEventApi } from '../services/api'; const Dashboard: React.FC = () => { const navigate = useNavigate(); const [recentEvents, setRecentEvents] = useState([]); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); + + // Safe date formatting function + const formatDate = (dateString: string): string => { + try { + const date = new Date(dateString); + if (isNaN(date.getTime())) { + return 'Invalid Date'; + } + return date.toLocaleDateString(); + } catch (error) { + console.error('Error formatting date:', error); + return 'Invalid Date'; + } + }; useEffect(() => { fetchRecentEvents(); @@ -18,7 +32,19 @@ const Dashboard: React.FC = () => { const response = await shoppingEventApi.getAll(); // Get the 3 most recent events const recent = response.data - .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + .sort((a, b) => { + try { + const dateA = new Date(b.created_at); + const dateB = new Date(a.created_at); + // Check if dates are valid + if (isNaN(dateA.getTime())) return 1; + if (isNaN(dateB.getTime())) return -1; + return dateA.getTime() - dateB.getTime(); + } catch (error) { + console.error('Error sorting events by date:', error); + return 0; + } + }) .slice(0, 3); setRecentEvents(recent); } catch (error) { @@ -179,7 +205,7 @@ const Dashboard: React.FC = () => { {event.shop.city}

- {new Date(event.date).toLocaleDateString()} + {formatDate(event.date)}

{event.products.length > 0 && (

diff --git a/frontend/src/components/ShoppingEventList.tsx b/frontend/src/components/ShoppingEventList.tsx index f1e6f45..54fbcb2 100644 --- a/frontend/src/components/ShoppingEventList.tsx +++ b/frontend/src/components/ShoppingEventList.tsx @@ -176,8 +176,18 @@ const ShoppingEventList: React.FC = () => { bValue = b.shop.name; break; case 'date': - aValue = new Date(a.date); - bValue = new Date(b.date); + // Safely handle date parsing with validation + try { + aValue = new Date(a.date); + bValue = new Date(b.date); + // Check if dates are valid + if (isNaN(aValue.getTime())) aValue = new Date(0); // fallback to epoch + if (isNaN(bValue.getTime())) bValue = new Date(0); // fallback to epoch + } catch (error) { + console.error('Error parsing dates for sorting:', error); + aValue = new Date(0); + bValue = new Date(0); + } break; case 'items': aValue = a.products.length; @@ -246,6 +256,20 @@ const ShoppingEventList: React.FC = () => { } }; + // Safe date formatting function + const formatDate = (dateString: string): string => { + try { + const date = new Date(dateString); + if (isNaN(date.getTime())) { + return 'Invalid Date'; + } + return date.toLocaleDateString(); + } catch (error) { + console.error('Error formatting date:', error); + return 'Invalid Date'; + } + }; + if (loading) { return (

@@ -346,7 +370,7 @@ const ShoppingEventList: React.FC = () => {
{event.shop.city}
- {new Date(event.date).toLocaleDateString()} + {formatDate(event.date)} {

{event.shop.city}

-

{new Date(event.date).toLocaleDateString()}

+

{formatDate(event.date)}

{event.total_amount && (

${event.total_amount.toFixed(2)} diff --git a/frontend/src/utils/dateUtils.ts b/frontend/src/utils/dateUtils.ts new file mode 100644 index 0000000..f761298 --- /dev/null +++ b/frontend/src/utils/dateUtils.ts @@ -0,0 +1,102 @@ +/** + * Date utility functions for safe date handling throughout the application + */ + +/** + * Safely formats a date string to a localized date string + * @param dateString - The date string to format + * @param fallback - The fallback value if the date is invalid (default: 'Invalid Date') + * @returns Formatted date string or fallback value + */ +export const formatDate = (dateString: string | null | undefined, fallback: string = 'Invalid Date'): string => { + if (!dateString) return fallback; + + try { + const date = new Date(dateString); + if (isNaN(date.getTime())) { + return fallback; + } + return date.toLocaleDateString(); + } catch (error) { + console.error('Error formatting date:', error); + return fallback; + } +}; + +/** + * Safely creates a Date object from a string + * @param dateString - The date string to parse + * @returns Date object or null if invalid + */ +export const safeParseDate = (dateString: string | null | undefined): Date | null => { + if (!dateString) return null; + + try { + const date = new Date(dateString); + if (isNaN(date.getTime())) { + return null; + } + return date; + } catch (error) { + console.error('Error parsing date:', error); + return null; + } +}; + +/** + * Safely compares two dates for sorting + * @param dateA - First date string + * @param dateB - Second date string + * @param direction - Sort direction ('asc' or 'desc') + * @returns Comparison result (-1, 0, 1) + */ +export const compareDates = ( + dateA: string | null | undefined, + dateB: string | null | undefined, + direction: 'asc' | 'desc' = 'asc' +): number => { + const parsedA = safeParseDate(dateA); + const parsedB = safeParseDate(dateB); + + // Handle null/invalid dates + if (!parsedA && !parsedB) return 0; + if (!parsedA) return direction === 'asc' ? 1 : -1; + if (!parsedB) return direction === 'asc' ? -1 : 1; + + const result = parsedA.getTime() - parsedB.getTime(); + return direction === 'asc' ? result : -result; +}; + +/** + * Gets the current date in YYYY-MM-DD format + * @returns Current date string + */ +export const getCurrentDateString = (): string => { + return new Date().toISOString().split('T')[0]; +}; + +/** + * Validates if a date string is valid and not in the future + * @param dateString - The date string to validate + * @param allowFuture - Whether to allow future dates (default: false) + * @returns Object with validation result and error message + */ +export const validateDate = ( + dateString: string | null | undefined, + allowFuture: boolean = false +): { isValid: boolean; error?: string } => { + if (!dateString) { + return { isValid: false, error: 'Date is required' }; + } + + const date = safeParseDate(dateString); + if (!date) { + return { isValid: false, error: 'Invalid date format' }; + } + + if (!allowFuture && date > new Date()) { + return { isValid: false, error: 'Date cannot be in the future' }; + } + + return { isValid: true }; +}; \ No newline at end of file