Spaces:
Runtime error
Runtime error
| /** | |
| * ChefCode Utility Functions - Condivise tra Web e Mobile | |
| */ | |
| // ===== STORAGE UTILS ===== | |
| const storage = { | |
| // Chiave principale per localStorage/AsyncStorage | |
| key: 'chefcode:v1', | |
| // Salva dati (Web: localStorage, Mobile: AsyncStorage) | |
| async save(data) { | |
| try { | |
| const serialized = JSON.stringify(data); | |
| if (typeof window !== 'undefined' && window.localStorage) { | |
| // Web | |
| localStorage.setItem(this.key, serialized); | |
| } else if (typeof AsyncStorage !== 'undefined') { | |
| // React Native | |
| await AsyncStorage.setItem(this.key, serialized); | |
| } | |
| return true; | |
| } catch (error) { | |
| console.error('Errore salvataggio:', error); | |
| return false; | |
| } | |
| }, | |
| // Carica dati | |
| async load() { | |
| try { | |
| let data = null; | |
| if (typeof window !== 'undefined' && window.localStorage) { | |
| // Web | |
| data = localStorage.getItem(this.key); | |
| } else if (typeof AsyncStorage !== 'undefined') { | |
| // React Native | |
| data = await AsyncStorage.getItem(this.key); | |
| } | |
| return data ? JSON.parse(data) : null; | |
| } catch (error) { | |
| console.error('Errore caricamento:', error); | |
| return null; | |
| } | |
| }, | |
| // Reset completo | |
| async reset() { | |
| try { | |
| if (typeof window !== 'undefined' && window.localStorage) { | |
| // Web | |
| localStorage.removeItem(this.key); | |
| } else if (typeof AsyncStorage !== 'undefined') { | |
| // React Native | |
| await AsyncStorage.removeItem(this.key); | |
| } | |
| return true; | |
| } catch (error) { | |
| console.error('Errore reset:', error); | |
| return false; | |
| } | |
| } | |
| }; | |
| // ===== PARSING UTILS ===== | |
| const parser = { | |
| // Parse comando vocale italiano (funziona per web e mobile) | |
| parseItalianCommand(text) { | |
| const lowerPrompt = text.toLowerCase(); | |
| console.log('🔍 Parsing comando:', lowerPrompt); | |
| // Rimuovi "aggiungi" dall'inizio | |
| const cleanPrompt = lowerPrompt.replace(/^aggiungi\s+/, '').trim(); | |
| // Trova prezzo con valute | |
| const currencyPattern = /(€|\$|euro|dollaro|usd|eur)\s*(\d+(?:[.,]\d+)?)|(\d+(?:[.,]\d+)?)\s*(€|\$|euro|dollaro|usd|eur)/g; | |
| let price = 0; | |
| let priceMatch; | |
| while ((priceMatch = currencyPattern.exec(cleanPrompt)) !== null) { | |
| const priceValue = priceMatch[2] || priceMatch[3]; | |
| if (priceValue) { | |
| price = parseFloat(priceValue.replace(',', '.')); | |
| break; | |
| } | |
| } | |
| // Rimuovi prezzo dal prompt | |
| let promptWithoutPrice = cleanPrompt.replace(currencyPattern, '').trim(); | |
| // Trova quantità + unità | |
| let quantity = 0; | |
| let unit = 'pz'; | |
| const qtyUnitMatch = promptWithoutPrice.match(/(\d+(?:[.,]\d+)?)\s*(kg|g|grammi|kilogrammi|lt|l|litri|ml|millilitri|pz|pezzi|pcs|confezioni|bottiglie|lattine)/); | |
| if (qtyUnitMatch) { | |
| quantity = parseFloat(qtyUnitMatch[1].replace(',', '.')); | |
| unit = qtyUnitMatch[2]; | |
| promptWithoutPrice = promptWithoutPrice.replace(qtyUnitMatch[0], '').trim(); | |
| } else { | |
| const qtyMatch = promptWithoutPrice.match(/^(\d+(?:[.,]\d+)?)\s+/); | |
| if (qtyMatch) { | |
| quantity = parseFloat(qtyMatch[1].replace(',', '.')); | |
| promptWithoutPrice = promptWithoutPrice.replace(qtyMatch[0], '').trim(); | |
| } | |
| } | |
| // Nome (rimuovi articoli) | |
| let name = promptWithoutPrice | |
| .replace(/^(di|del|della|dei|degli|delle|il|la|lo|gli|le|un|una|uno)\s+/g, '') | |
| .replace(/\s+(di|del|della|dei|degli|delle|il|la|lo|gli|le|un|una|uno)\s+/g, ' ') | |
| .trim(); | |
| return { quantity, unit, name, price }; | |
| }, | |
| // Normalizza unità di misura | |
| normalizeUnit(unit) { | |
| const unitMap = { | |
| 'chili': 'kg', 'chilo': 'kg', 'chilogrammi': 'kg', | |
| 'grammi': 'g', 'gr': 'g', | |
| 'litro': 'l', 'litri': 'l', 'lt': 'l', | |
| 'millilitri': 'ml', 'ml': 'ml', | |
| 'pezzi': 'pz', 'pezzo': 'pz', 'pcs': 'pz', | |
| 'bottiglie': 'bt', 'bottiglia': 'bt' | |
| }; | |
| return unitMap[unit.toLowerCase()] || unit; | |
| } | |
| }; | |
| // ===== VALIDATION UTILS ===== | |
| const validator = { | |
| // Valida ingrediente | |
| validateIngredient(item) { | |
| const errors = []; | |
| if (!item.name || item.name.trim().length === 0) { | |
| errors.push('Nome ingrediente richiesto'); | |
| } | |
| if (!item.quantity || item.quantity <= 0) { | |
| errors.push('Quantità deve essere maggiore di 0'); | |
| } | |
| if (!item.unit || item.unit.trim().length === 0) { | |
| errors.push('Unità di misura richiesta'); | |
| } | |
| if (item.price < 0) { | |
| errors.push('Prezzo non può essere negativo'); | |
| } | |
| return { | |
| isValid: errors.length === 0, | |
| errors | |
| }; | |
| }, | |
| // Valida ricetta | |
| validateRecipe(recipe) { | |
| const errors = []; | |
| if (!recipe.name || recipe.name.trim().length === 0) { | |
| errors.push('Nome ricetta richiesto'); | |
| } | |
| if (!recipe.items || !Array.isArray(recipe.items) || recipe.items.length === 0) { | |
| errors.push('Ricetta deve avere almeno un ingrediente'); | |
| } | |
| recipe.items?.forEach((item, index) => { | |
| const itemValidation = this.validateIngredient(item); | |
| if (!itemValidation.isValid) { | |
| errors.push(`Ingrediente ${index + 1}: ${itemValidation.errors.join(', ')}`); | |
| } | |
| }); | |
| return { | |
| isValid: errors.length === 0, | |
| errors | |
| }; | |
| } | |
| }; | |
| // ===== FORMAT UTILS ===== | |
| const formatter = { | |
| // Formatta prezzo | |
| currency(amount, currency = '€') { | |
| return `${currency}${parseFloat(amount || 0).toFixed(2)}`; | |
| }, | |
| // Formatta data | |
| date(date) { | |
| return new Intl.DateTimeFormat('it-IT', { | |
| weekday: 'long', | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }).format(new Date(date)); | |
| }, | |
| // Formatta quantità | |
| quantity(qty, unit) { | |
| return `${parseFloat(qty || 0)} ${unit || 'pz'}`; | |
| } | |
| }; | |
| // ===== DEVICE UTILS ===== | |
| const device = { | |
| // Rileva piattaforma | |
| isMobile() { | |
| return typeof window !== 'undefined' && /Mobi|Android/i.test(navigator.userAgent); | |
| }, | |
| isWeb() { | |
| return typeof window !== 'undefined' && typeof document !== 'undefined'; | |
| }, | |
| isReactNative() { | |
| return typeof navigator !== 'undefined' && navigator.product === 'ReactNative'; | |
| }, | |
| // Info piattaforma | |
| getPlatform() { | |
| if (this.isReactNative()) return 'react-native'; | |
| if (this.isMobile()) return 'mobile-web'; | |
| if (this.isWeb()) return 'web'; | |
| return 'unknown'; | |
| } | |
| }; | |
| // Export per browser | |
| if (typeof window !== 'undefined') { | |
| window.ChefCodeUtils = { storage, parser, validator, formatter, device }; | |
| // Export individuali per retrocompatibilità | |
| window.storage = storage; | |
| window.parser = parser; | |
| window.validator = validator; | |
| window.formatter = formatter; | |
| window.device = device; | |
| } |