Skip to content

Latest commit

 

History

History
123 lines (104 loc) · 3.79 KB

File metadata and controls

123 lines (104 loc) · 3.79 KB

import { Plant } from '../types/Plant';

interface WateringEvent { plantName: string; dueDate: Date; description: string; }

export function generateWateringCalendarICS(plants: Plant[]): string { const events: WateringEvent[] = []; const now = new Date();

// Calculate watering events for the next 90 days plants.forEach(plant => { if (!plant.wateringFrequencyDays || plant.wateringFrequencyDays <= 0) { return; }

const lastWatered = plant.lastWatered ? new Date(plant.lastWatered) : new Date();
let nextWatering = new Date(lastWatered);
nextWatering.setDate(nextWatering.getDate() + plant.wateringFrequencyDays);

// Generate events for next 90 days
const endDate = new Date(now);
endDate.setDate(endDate.getDate() + 90);

while (nextWatering <= endDate) {
  if (nextWatering >= now) {
    events.push({
      plantName: plant.name,
      dueDate: new Date(nextWatering),
      description: plant.notes || `Water ${plant.name}`
    });
  }
  nextWatering = new Date(nextWatering);
  nextWatering.setDate(nextWatering.getDate() + plant.wateringFrequencyDays);
}

});

// Sort events by date events.sort((a, b) => a.dueDate.getTime() - b.dueDate.getTime());

return buildICS(events); }

function buildICS(events: WateringEvent[]): string { const lines: string[] = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//PlantGuide//Watering Calendar//EN', 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'X-WR-CALNAME:Plant Watering Schedule', 'X-WR-TIMEZONE:UTC' ];

events.forEach((event, index) => { const uid = watering-${Date.now()}-${index}@plantguide.local; const dtstamp = formatICSDate(new Date()); const dtstart = formatICSDate(event.dueDate);

// Create all-day event
const dtstartDate = formatICSDateOnly(event.dueDate);

lines.push('BEGIN:VEVENT');
lines.push(`UID:${uid}`);
lines.push(`DTSTAMP:${dtstamp}`);
lines.push(`DTSTART;VALUE=DATE:${dtstartDate}`);
lines.push(`SUMMARY:Water ${event.plantName}`);
lines.push(`DESCRIPTION:${escapeICSText(event.description)}`);
lines.push('STATUS:CONFIRMED');
lines.push('TRANSP:TRANSPARENT');
lines.push('BEGIN:VALARM');
lines.push('TRIGGER:-PT2H');
lines.push('ACTION:DISPLAY');
lines.push(`DESCRIPTION:Time to water ${event.plantName}`);
lines.push('END:VALARM');
lines.push('END:VEVENT');

});

lines.push('END:VCALENDAR');

return lines.join('\r\n'); }

function formatICSDate(date: Date): string { const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, '0'); const day = String(date.getUTCDate()).padStart(2, '0'); const hours = String(date.getUTCHours()).padStart(2, '0'); const minutes = String(date.getUTCMinutes()).padStart(2, '0'); const seconds = String(date.getUTCSeconds()).padStart(2, '0'); return ${year}${month}${day}T${hours}${minutes}${seconds}Z; }

function formatICSDateOnly(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return ${year}${month}${day}; }

function escapeICSText(text: string): string { return text .replace(/\/g, '\\') .replace(/;/g, '\;') .replace(/,/g, '\,') .replace(/\n/g, '\n'); }

export function downloadICS(plants: Plant[], filename: string = 'watering-schedule.ics'): void { const icsContent = generateWateringCalendarICS(plants); const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' }); const url = URL.createObjectURL(blob);

const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }