// Client-side render functions window.clientRender = { renderStorageSpot(position, bottle, readOnly = false) { const bottleClass = bottle ? (bottle.type === 'red' ? 'bg-red-200' : bottle.type === 'white' ? 'bg-yellow-100' : bottle.type === 'champagne' ? 'bg-amber-100' : bottle.type === 'sparkling' ? 'bg-amber-100' : 'bg-pink-200') : ''; return `
${bottle ? `
${bottle.full_wine_name}
${bottle.vintage ? `
${bottle.vintage}
` : ''}
${bottle.average_retail_price_usd > 0 ? '
$'+bottle.average_retail_price_usd+'
' : ''} ${getCountryFlag(bottle.country, 'w-4 h-4')}
` : !readOnly ? ` ` : ''}
`.trim(); }, renderStandardRack(bottles, config, readOnly = false) { return `

Standard Storage

${Array.from({ length: config.slots }).map((_, index) => { const position = `standard-${config.slots - index}`; // Count down instead of up return this.renderStorageSpot(position, bottles.find(b => b.position === position), readOnly); }).join('')}
`.trim(); }, renderDiamondRack(bottles, config, readOnly = false) { return `

Secondary Storage

${Array.from({ length: config.slots }).map((_, index) => { const position = `diamond-${config.slots - index}`; // Count down instead of up return this.renderStorageSpot(position, bottles.find(b => b.position === position), readOnly); }).join('')}
`.trim(); }, renderBulkStorage(bottles, config, readOnly = false) { return `

Bulk Storage

${Array.from({ length: config.slots }).map((_, index) => { const position = `bulk-${config.slots - index}`; // Count down instead of up return this.renderStorageSpot(position, bottles.find(b => b.position === position), readOnly); }).join('')}
`.trim(); }, sortAndAggregateStats(type, bottles){ const objStats = bottles .filter(b => b[type] != null && b[type] !== '') .reduce((acc, bottle) => { const obj = bottle[type].toString(); acc[obj] = (acc[obj] || 0) + 1; return acc; }, {}); const sortedObjStats = Object.entries(objStats) .map(([value, count]) => ({ value, count })) .sort((a, b) => b.count - a.count); return sortedObjStats; }, renderSidebar(bottles, originalBottles = window.winecellar.bottles) { const ITEMS_TO_SHOW = 5; // Number of items to show before "Show more" const statsArray = [ { type:'Total', count: originalBottles.length }, { type:'Red', count: originalBottles.filter(b => b.type.toLowerCase() === 'red').length }, { type:'White', count: originalBottles.filter(b => b.type.toLowerCase() === 'white').length}, { type:'Champagne', count: originalBottles.filter(b => b.type.toLowerCase() === 'champagne').length}, { type:'Sparkling', count: originalBottles.filter(b => b.type.toLowerCase() === 'sparkling').length}, { type:'Rose', count: originalBottles.filter(b => b.type.toLowerCase() === 'rose').length} ].sort((a, b) => b.count - a.count); const sortedRegionStats = this.sortAndAggregateStats('region', originalBottles); const sortedCountryStats = this.sortAndAggregateStats('country', originalBottles); const sortedVintageStats = this.sortAndAggregateStats('vintage', originalBottles); //const sortedGrapeStats = this.sortAndAggregateStats('grape_variety', originalBottles); // grape variety requires different handling due to mix blend const grapeStats = originalBottles.reduce((acc, bottle) => { if (!bottle.grape_variety) return acc; // First split only on commas const grapes = bottle.grape_variety .split(',') .map(g => g.trim().toLowerCase()) // Split on specific connectors while preserving complete variety names .map(g => g.replace(/s+(and|with|&)s+/g, ',')) .join(',') .split(',') .map(g => g.trim()) .filter(g => g && g.length > 1) // Filter out empty strings and single characters // Filter out common joining words if they appear alone .filter(g => !['and', 'with', '&'].includes(g)); // Count each variety separately grapes.forEach(grape => { acc[grape] = (acc[grape] || 0) + 1; }); return acc; }, {}); const sortedGrapeStats = Object.entries(grapeStats) .map(([grape, count]) => ({ grape, count })) .sort((a, b) => b.count - a.count); //render more button const renderExpandableSection = (items, sectionType, itemRenderer) => { const isExpanded = window.sidebarState[`${sectionType}Expanded`]; const visibleItems = isExpanded ? items : items.slice(0, ITEMS_TO_SHOW); const hasMore = items.length > ITEMS_TO_SHOW; return `
${visibleItems.map(itemRenderer).join('')} ${hasMore ? ` ` : ''}
`; }; return `

Categories


${statsArray[0].count > 0 ? statsArray.map(({ type, count }) => count > 0 ? ` ` : '').join('') : '
Cellar is empty
'}

Variety


${sortedGrapeStats.length > 0 ? renderExpandableSection( sortedGrapeStats, 'varieties', item => ` `) : '
Variety data not available
' }

Region


${sortedRegionStats.length > 0 ? renderExpandableSection( sortedRegionStats, 'regions', item => ` `) : '
Region data not available
' }

Country


${sortedCountryStats.length > 0 ? renderExpandableSection( sortedCountryStats, 'countries', item => ` `) : '
Country data not available
' }

Vintages


${sortedVintageStats.length > 0 ? renderExpandableSection( sortedVintageStats, 'vintages', item => ` `) : '
No vintage wines
' }
`.trim(); }, renderWineCellar(bottles, config, settings, readOnly = false) { return `
Winekitty is a wine cellar app. Upload your wine list or label and our AI model will figure out the rest. No manual entry. No sign ups. No social. Just wine.

${settings.title}

${share < 1 ? ` ` : ''}
${this.renderStandardRack(bottles, config.standard, readOnly)} ${this.renderDiamondRack(bottles, config.diamond, readOnly)} ${this.renderBulkStorage(bottles, config.bulk, readOnly)}
`.trim(); } };