UNPKG

@crescender/calendar

Version:

A comprehensive TypeScript calendar library with musician-specific capabilities, architected for client/server separation.

1,366 lines (1,357 loc) 40.5 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/client/utils/date-helpers.ts var formatTimeAustralian = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; return d.toLocaleTimeString("en-AU", { hour: "2-digit", minute: "2-digit", hour12: true }); }, "formatTimeAustralian"); var formatDateTimeAustralian = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; return d.toLocaleString("en-AU", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", hour12: true }); }, "formatDateTimeAustralian"); var daysUntil = /* @__PURE__ */ __name((date) => { const targetDate = typeof date === "string" ? new Date(date) : date; const now = /* @__PURE__ */ new Date(); const diffTime = targetDate.getTime() - now.getTime(); return Math.ceil(diffTime / (1e3 * 60 * 60 * 24)); }, "daysUntil"); var isToday = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; const today = /* @__PURE__ */ new Date(); return d.toDateString() === today.toDateString(); }, "isToday"); var isPast = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; return d < /* @__PURE__ */ new Date(); }, "isPast"); var isFuture = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; return d > /* @__PURE__ */ new Date(); }, "isFuture"); var isThisWeek = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; const now = /* @__PURE__ */ new Date(); const startOfWeek = new Date(now.setDate(now.getDate() - now.getDay())); const endOfWeek = new Date(now.setDate(now.getDate() - now.getDay() + 6)); return d >= startOfWeek && d <= endOfWeek; }, "isThisWeek"); var isThisMonth = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; const now = /* @__PURE__ */ new Date(); return d.getMonth() === now.getMonth() && d.getFullYear() === now.getFullYear(); }, "isThisMonth"); var startOfDay = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : new Date(date); d.setHours(0, 0, 0, 0); return d; }, "startOfDay"); var endOfDay = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : new Date(date); d.setHours(23, 59, 59, 999); return d; }, "endOfDay"); var addDays = /* @__PURE__ */ __name((date, days) => { const d = typeof date === "string" ? new Date(date) : new Date(date); d.setDate(d.getDate() + days); return d; }, "addDays"); var addMonths = /* @__PURE__ */ __name((date, months) => { const d = typeof date === "string" ? new Date(date) : new Date(date); d.setMonth(d.getMonth() + months); return d; }, "addMonths"); var getMonthName = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; return d.toLocaleDateString("en-AU", { month: "long" }); }, "getMonthName"); var getDayName = /* @__PURE__ */ __name((date) => { const d = typeof date === "string" ? new Date(date) : date; return d.toLocaleDateString("en-AU", { weekday: "long" }); }, "getDayName"); // src/client/utils/formatting.ts var formatCurrencyAUD = /* @__PURE__ */ __name((amount) => { return new Intl.NumberFormat("en-AU", { style: "currency", currency: "AUD", minimumFractionDigits: 2 }).format(amount); }, "formatCurrencyAUD"); var formatPhoneAustralian = /* @__PURE__ */ __name((phone) => { const cleaned = phone.replace(/\D/g, ""); if (cleaned.length === 10) { if (cleaned.startsWith("04")) { return cleaned.replace(/(\d{4})(\d{3})(\d{3})/, "$1 $2 $3"); } else if (cleaned.startsWith("02") || cleaned.startsWith("03") || cleaned.startsWith("07") || cleaned.startsWith("08")) { return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, "($1) $2 $3"); } } return phone; }, "formatPhoneAustralian"); var formatAddressAustralian = /* @__PURE__ */ __name((address) => { const parts = []; if (address.street) parts.push(address.street); if (address.suburb) parts.push(address.suburb); if (address.state && address.postcode) { parts.push(`${address.state} ${address.postcode}`); } else if (address.state) { parts.push(address.state); } else if (address.postcode) { parts.push(address.postcode); } if (address.country && address.country !== "Australia") { parts.push(address.country); } return parts.join(", "); }, "formatAddressAustralian"); var formatPercentage = /* @__PURE__ */ __name((value, decimals = 1) => { return `${value.toFixed(decimals)}%`; }, "formatPercentage"); var formatNumber = /* @__PURE__ */ __name((value, decimals = 0) => { return new Intl.NumberFormat("en-AU", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(value); }, "formatNumber"); var formatDuration = /* @__PURE__ */ __name((minutes) => { const hours = Math.floor(minutes / 60); const mins = minutes % 60; if (hours === 0) { return `${mins} min`; } else if (mins === 0) { return hours === 1 ? `${hours} hr` : `${hours} hrs`; } else { const hourText = hours === 1 ? "hr" : "hrs"; return `${hours} ${hourText} ${mins} min`; } }, "formatDuration"); var formatFileSize = /* @__PURE__ */ __name((bytes) => { const sizes = [ "Bytes", "KB", "MB", "GB" ]; if (bytes === 0) return "0 Bytes"; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${Math.round(bytes / Math.pow(1024, i) * 100) / 100} ${sizes[i]}`; }, "formatFileSize"); var capitalizeWords = /* @__PURE__ */ __name((text) => { return text.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); }, "capitalizeWords"); var truncateText = /* @__PURE__ */ __name((text, maxLength) => { if (text.length <= maxLength) return text; return text.substr(0, maxLength - 3) + "..."; }, "truncateText"); var getInitials = /* @__PURE__ */ __name((name) => { return name.split(" ").map((word) => word.charAt(0).toUpperCase()).join("").substr(0, 2); }, "getInitials"); // src/shared/utils.ts function formatDateAustralian(date) { const months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; const day = date.getUTCDate().toString().padStart(2, "0"); const month = months[date.getUTCMonth()]; const year = date.getUTCFullYear(); return `${day}/${month}/${year}`; } __name(formatDateAustralian, "formatDateAustralian"); function getDurationMinutes(start, end) { return Math.round((end.getTime() - start.getTime()) / (1e3 * 60)); } __name(getDurationMinutes, "getDurationMinutes"); function isSameDay(date1, date2) { return date1.toDateString() === date2.toDateString(); } __name(isSameDay, "isSameDay"); function getStartOfWeek(date) { const result = new Date(date); const day = result.getDay(); const diff = result.getDate() - day + (day === 0 ? -6 : 1); result.setDate(diff); result.setHours(0, 0, 0, 0); return result; } __name(getStartOfWeek, "getStartOfWeek"); function getEndOfWeek(date) { const result = new Date(date); const day = result.getDay(); const diff = result.getDate() - day + (day === 0 ? 0 : 7); result.setDate(diff); result.setHours(23, 59, 59, 999); return result; } __name(getEndOfWeek, "getEndOfWeek"); function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } __name(isValidEmail, "isValidEmail"); function isValidPhone(phone) { const phoneRegex = /^(\+61|0)[2-9]\d{8}$/; return phoneRegex.test(phone.replace(/\s/g, "")); } __name(isValidPhone, "isValidPhone"); function generateTempId() { return `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } __name(generateTempId, "generateTempId"); // src/shared/enums.ts var EVENT_TYPES = { GIG: "gig", LESSON: "lesson", AUDITION: "audition", PRACTICE: "practice", REHEARSAL: "rehearsal", RECORDING: "recording", MEETING: "meeting" }; var PAYMENT_STATUS = { PENDING: "Pending", PAID: "Paid", OVERDUE: "Overdue", CANCELLED: "Cancelled" }; var EVENT_STATUS = { CONFIRMED: "Confirmed", TENTATIVE: "Tentative", CANCELLED: "Cancelled", COMPLETED: "Completed" }; var CALENDAR_TYPES = { INDIVIDUAL: "individual", GROUP: "group", SHARED: "shared" }; var STUDENT_LEVELS = { BEGINNER: "Beginner", INTERMEDIATE: "Intermediate", ADVANCED: "Advanced", PROFESSIONAL: "Professional" }; var DIFFICULTY_LEVELS = { EASY: "Easy", MEDIUM: "Medium", HARD: "Hard", EXPERT: "Expert" }; var GENRES = { CLASSICAL: "Classical", JAZZ: "Jazz", ROCK: "Rock", POP: "Pop", BLUES: "Blues", COUNTRY: "Country", FOLK: "Folk", ELECTRONIC: "Electronic", WORLD: "World Music", OTHER: "Other" }; var INSTRUMENTS = { PIANO: "Piano", GUITAR: "Guitar", VIOLIN: "Violin", DRUMS: "Drums", BASS: "Bass", SAXOPHONE: "Saxophone", TRUMPET: "Trumpet", FLUTE: "Flute", CELLO: "Cello", VOICE: "Voice", OTHER: "Other" }; // src/client/utils/validation.ts function validateEvent(data) { const errors = {}; if (!data.title || data.title.trim().length === 0) { errors.title = [ "Event title is required" ]; } else if (data.title.length > 200) { errors.title = [ "Event title must not exceed 200 characters" ]; } if (!data.startDate) { errors.startDate = [ "Start date is required" ]; } if (!data.startTime) { errors.startTime = [ "Start time is required" ]; } if (data.startDate && data.endDate && data.startTime && data.endTime) { const start = /* @__PURE__ */ new Date(`${data.startDate}T${data.startTime}`); const end = /* @__PURE__ */ new Date(`${data.endDate || data.startDate}T${data.endTime || data.startTime}`); if (start >= end) { errors.endTime = [ "End time must be after start time" ]; } const durationHours = (end.getTime() - start.getTime()) / (1e3 * 60 * 60); if (durationHours > 24) { errors.endTime = [ "Event duration cannot exceed 24 hours" ]; } } if (!data.eventType) { errors.eventType = [ "Event type is required" ]; } else if (!Object.values(EVENT_TYPES).includes(data.eventType)) { errors.eventType = [ "Invalid event type" ]; } if (!data.calendarId) { errors.calendarId = [ "Calendar selection is required" ]; } if (data.description && data.description.length > 1e3) { errors.description = [ "Description must not exceed 1000 characters" ]; } if (data.status && !Object.values(EVENT_STATUS).includes(data.status)) { errors.status = [ "Invalid event status" ]; } if (data.notes && data.notes.length > 1e3) { errors.notes = [ "Notes must not exceed 1000 characters" ]; } if (data.isRecurring && !data.recurrenceRule) { errors.recurrenceRule = [ "Recurrence rule is required for recurring events" ]; } if (data.recurrenceRule && !data.isRecurring) { errors.isRecurring = [ "Recurring flag must be set when recurrence rule is provided" ]; } if (data.recurrenceEndDate && data.startDate) { const recurrenceEnd = new Date(data.recurrenceEndDate); const eventStart = new Date(data.startDate); if (recurrenceEnd <= eventStart) { errors.recurrenceEndDate = [ "Recurrence end date must be after event start date" ]; } } if (data.maxOccurrences !== void 0 && data.maxOccurrences < 1) { errors.maxOccurrences = [ "Maximum occurrences must be at least 1" ]; } return { isValid: Object.keys(errors).length === 0, errors }; } __name(validateEvent, "validateEvent"); function validateIncome(data) { const errors = {}; if (!data.description || data.description.trim().length === 0) { errors.description = [ "Income description is required" ]; } else if (data.description.length > 200) { errors.description = [ "Description must not exceed 200 characters" ]; } if (data.amount === void 0 || data.amount === null) { errors.amount = [ "Amount is required" ]; } else if (data.amount < 0) { errors.amount = [ "Amount must be positive" ]; } else if (data.amount > 999999.99) { errors.amount = [ "Amount must not exceed $999,999.99" ]; } if (!data.currency || data.currency.trim().length === 0) { errors.currency = [ "Currency is required" ]; } else if (!/^[A-Z]{3}$/.test(data.currency)) { errors.currency = [ "Currency must be a valid 3-letter code (e.g., AUD, USD)" ]; } if (data.notes && data.notes.length > 500) { errors.notes = [ "Notes must not exceed 500 characters" ]; } return { isValid: Object.keys(errors).length === 0, errors }; } __name(validateIncome, "validateIncome"); function validateExpense(data) { const errors = {}; if (!data.description || data.description.trim().length === 0) { errors.description = [ "Expense description is required" ]; } else if (data.description.length > 200) { errors.description = [ "Description must not exceed 200 characters" ]; } if (data.amount === void 0 || data.amount === null) { errors.amount = [ "Amount is required" ]; } else if (data.amount < 0) { errors.amount = [ "Amount must be positive" ]; } else if (data.amount > 999999.99) { errors.amount = [ "Amount must not exceed $999,999.99" ]; } if (!data.currency || data.currency.trim().length === 0) { errors.currency = [ "Currency is required" ]; } else if (!/^[A-Z]{3}$/.test(data.currency)) { errors.currency = [ "Currency must be a valid 3-letter code (e.g., AUD, USD)" ]; } if (data.notes && data.notes.length > 500) { errors.notes = [ "Notes must not exceed 500 characters" ]; } if (data.receipt && data.receipt.length > 500) { errors.receipt = [ "Receipt path must not exceed 500 characters" ]; } return { isValid: Object.keys(errors).length === 0, errors }; } __name(validateExpense, "validateExpense"); function validateCalendar(data) { const errors = {}; if (!data.name || data.name.trim().length === 0) { errors.name = [ "Calendar name is required" ]; } else if (data.name.length > 100) { errors.name = [ "Calendar name must not exceed 100 characters" ]; } if (data.description && data.description.length > 500) { errors.description = [ "Description must not exceed 500 characters" ]; } if (data.color && !/^#[0-9A-F]{6}$/i.test(data.color)) { errors.color = [ "Color must be a valid hex color code" ]; } if (data.timeZone && data.timeZone.length > 50) { errors.timeZone = [ "Time zone must not exceed 50 characters" ]; } return { isValid: Object.keys(errors).length === 0, errors }; } __name(validateCalendar, "validateCalendar"); function validateVenue(data) { const errors = {}; if (!data.name || data.name.trim().length === 0) { errors.name = [ "Venue name is required" ]; } else if (data.name.length > 200) { errors.name = [ "Venue name must not exceed 200 characters" ]; } if (data.address && data.address.length > 300) { errors.address = [ "Address must not exceed 300 characters" ]; } if (data.city && data.city.length > 100) { errors.city = [ "City must not exceed 100 characters" ]; } if (data.state && data.state.length > 100) { errors.state = [ "State must not exceed 100 characters" ]; } if (data.country && data.country.length > 100) { errors.country = [ "Country must not exceed 100 characters" ]; } if (data.website && !isValidWebsite(data.website)) { errors.website = [ "Please enter a valid website URL" ]; } if (data.contactEmail && !isValidEmail(data.contactEmail)) { errors.contactEmail = [ "Please enter a valid email address" ]; } if (data.contactPhone && !isValidPhone(data.contactPhone)) { errors.contactPhone = [ "Please enter a valid phone number" ]; } if (data.notes && data.notes.length > 1e3) { errors.notes = [ "Notes must not exceed 1000 characters" ]; } return { isValid: Object.keys(errors).length === 0, errors }; } __name(validateVenue, "validateVenue"); function validateContact(data) { const errors = {}; if (!data.name || data.name.trim().length === 0) { errors.name = [ "Contact name is required" ]; } else if (data.name.length > 200) { errors.name = [ "Contact name must not exceed 200 characters" ]; } if (data.email && !isValidEmail(data.email)) { errors.email = [ "Please enter a valid email address" ]; } if (data.phone && !isValidPhone(data.phone)) { errors.phone = [ "Please enter a valid phone number" ]; } if (data.role && data.role.length > 100) { errors.role = [ "Role must not exceed 100 characters" ]; } if (data.notes && data.notes.length > 1e3) { errors.notes = [ "Notes must not exceed 1000 characters" ]; } return { isValid: Object.keys(errors).length === 0, errors }; } __name(validateContact, "validateContact"); function validateRecurrenceRule(rule) { const errors = {}; if (!rule || rule.trim().length === 0) { errors.rule = [ "Recurrence rule is required" ]; return { isValid: false, errors }; } if (!rule.startsWith("RRULE:")) { errors.rule = [ 'Recurrence rule must start with "RRULE:"' ]; } if (!rule.includes("FREQ=")) { errors.rule = [ "Recurrence rule must include FREQ parameter" ]; } return { isValid: Object.keys(errors).length === 0, errors }; } __name(validateRecurrenceRule, "validateRecurrenceRule"); function isValidWebsite(url) { try { const urlObj = new URL(url); return urlObj.protocol === "http:" || urlObj.protocol === "https:"; } catch { return false; } } __name(isValidWebsite, "isValidWebsite"); // src/client/utils/event-processing.ts import { RRule } from "rrule"; function expandRecurrence(event, startRange, endRange, maxOccurrences = 100) { if (!event.recurrenceRule) { if (event.start >= startRange && event.start <= endRange) { return [ event ]; } return []; } try { const rule = new RRule({ ...RRule.parseString(event.recurrenceRule), dtstart: event.start }); const occurrences = rule.between(startRange, endRange, true); const limitedOccurrences = occurrences.slice(0, maxOccurrences); return limitedOccurrences.map((occurrenceStart, index) => { const duration = event.end.getTime() - event.start.getTime(); const occurrenceEnd = new Date(occurrenceStart.getTime() + duration); return { ...event, id: `${event.id}_${index}`, start: occurrenceStart, end: occurrenceEnd }; }); } catch (error) { console.warn("Failed to parse recurrence rule:", event.recurrenceRule, error); return [ event ]; } } __name(expandRecurrence, "expandRecurrence"); function enhanceClientEvent(event) { const enhanced = { ...event }; enhanced.duration = Math.round((event.end.getTime() - event.start.getTime()) / (1e3 * 60)); if (event.income) { enhanced.totalIncome = event.income.reduce((sum, income) => sum + income.amount, 0); } if (event.expenses) { enhanced.totalExpenses = event.expenses.reduce((sum, expense) => sum + expense.amount, 0); } const totalIncome = enhanced.totalIncome || 0; const totalExpenses = enhanced.totalExpenses || 0; enhanced.profit = totalIncome - totalExpenses; enhanced.netProfit = enhanced.profit; enhanced.displayDate = formatDateAustralian(event.start); enhanced.formattedDate = formatDateAustralian(event.start); enhanced.formattedTime = formatTimeAustralian(event.start); const hours = Math.floor(enhanced.duration / 60); const minutes = enhanced.duration % 60; if (hours === 0) { enhanced.formattedDuration = `${minutes} min${minutes !== 1 ? "s" : ""}`; } else if (minutes === 0) { enhanced.formattedDuration = `${hours} hr${hours !== 1 ? "s" : ""}`; } else { enhanced.formattedDuration = `${hours} hr${hours !== 1 ? "s" : ""} ${minutes} min`; } enhanced.isPast = isPast(event.start); enhanced.isUpcoming = isFuture(event.start); enhanced.daysUntil = daysUntil(event.start); return enhanced; } __name(enhanceClientEvent, "enhanceClientEvent"); function filterEvents(events, filters) { return events.filter((event) => { if (filters.eventType && filters.eventType.length > 0) { if (!filters.eventType.includes(event.type)) { return false; } } if (filters.status && filters.status.length > 0) { if (!event.status || !filters.status.includes(event.status)) { return false; } } if (filters.calendarId && filters.calendarId.length > 0) { if (!filters.calendarId.includes(event.calendarId)) { return false; } } if (filters.venueId && filters.venueId.length > 0) { if (!event.venueId || !filters.venueId.includes(event.venueId)) { return false; } } if (filters.dateRange) { if (event.start < filters.dateRange.start || event.start > filters.dateRange.end) { return false; } } if (filters.searchQuery) { const query = filters.searchQuery.toLowerCase(); const searchableText = [ event.summary, event.description, event.genre, event.instrument ].filter(Boolean).join(" ").toLowerCase(); if (!searchableText.includes(query)) { return false; } } if (filters.isUpcoming !== void 0) { const isUpcoming = isFuture(event.start); if (filters.isUpcoming !== isUpcoming) { return false; } } if (filters.isPast !== void 0) { const isPastEvent = isPast(event.start); if (filters.isPast !== isPastEvent) { return false; } } return true; }); } __name(filterEvents, "filterEvents"); function sortEvents(events, sortOptions) { return [ ...events ].sort((a, b) => { let aValue; let bValue; switch (sortOptions.field) { case "start": aValue = a.start.getTime(); bValue = b.start.getTime(); break; case "end": aValue = a.end.getTime(); bValue = b.end.getTime(); break; case "summary": aValue = a.summary.toLowerCase(); bValue = b.summary.toLowerCase(); break; case "type": aValue = a.type.toLowerCase(); bValue = b.type.toLowerCase(); break; case "createdAt": aValue = a.createdAt.getTime(); bValue = b.createdAt.getTime(); break; default: return 0; } if (aValue < bValue) { return sortOptions.direction === "asc" ? -1 : 1; } if (aValue > bValue) { return sortOptions.direction === "asc" ? 1 : -1; } return 0; }); } __name(sortEvents, "sortEvents"); function groupEventsByDate(events) { return events.reduce((groups, event) => { const dateKey = formatDateAustralian(event.start); if (!groups[dateKey]) { groups[dateKey] = []; } groups[dateKey].push(event); return groups; }, {}); } __name(groupEventsByDate, "groupEventsByDate"); function getUpcomingEvents(events) { const now = /* @__PURE__ */ new Date(); return events.filter((event) => event.start > now).sort((a, b) => a.start.getTime() - b.start.getTime()); } __name(getUpcomingEvents, "getUpcomingEvents"); function getTodaysEvents(events) { const today = /* @__PURE__ */ new Date(); const startOfDay2 = new Date(today.getFullYear(), today.getMonth(), today.getDate()); const endOfDay2 = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 999); return events.filter((event) => event.start >= startOfDay2 && event.start <= endOfDay2); } __name(getTodaysEvents, "getTodaysEvents"); function calculateFinancialSummary(events) { let totalIncome = 0; let totalExpenses = 0; let eventCount = 0; events.forEach((event) => { if (event.income) { totalIncome += event.income.reduce((sum, income) => sum + income.amount, 0); } if (event.expenses) { totalExpenses += event.expenses.reduce((sum, expense) => sum + expense.amount, 0); } eventCount++; }); const netProfit = totalIncome - totalExpenses; const averageProfit = eventCount > 0 ? netProfit / eventCount : 0; return { totalIncome, totalExpenses, netProfit, eventCount, averageProfit }; } __name(calculateFinancialSummary, "calculateFinancialSummary"); // src/client/components/EventCard.tsx import React from "react"; var EventCard = /* @__PURE__ */ __name(({ event, onClick, onEdit, onDelete, showFinancials = false, className = "" }) => { const handleClick = /* @__PURE__ */ __name(() => { onClick?.(event); }, "handleClick"); const handleEdit = /* @__PURE__ */ __name((e) => { e.stopPropagation(); onEdit?.(event); }, "handleEdit"); const handleDelete = /* @__PURE__ */ __name((e) => { e.stopPropagation(); onDelete?.(event); }, "handleDelete"); return /* @__PURE__ */ React.createElement("div", { className: `event-card ${className}`, onClick: handleClick, style: { border: "1px solid #e0e0e0", borderRadius: "8px", padding: "16px", margin: "8px 0", backgroundColor: "#fff", cursor: onClick ? "pointer" : "default", boxShadow: "0 2px 4px rgba(0,0,0,0.1)", transition: "box-shadow 0.2s ease" }, onMouseEnter: /* @__PURE__ */ __name((e) => { if (onClick) { e.currentTarget.style.boxShadow = "0 4px 8px rgba(0,0,0,0.15)"; } }, "onMouseEnter"), onMouseLeave: /* @__PURE__ */ __name((e) => { e.currentTarget.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)"; }, "onMouseLeave") }, /* @__PURE__ */ React.createElement("div", { className: "event-card-header", style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start" } }, /* @__PURE__ */ React.createElement("div", { className: "event-info", style: { flex: 1 } }, /* @__PURE__ */ React.createElement("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600" } }, event.summary), /* @__PURE__ */ React.createElement("div", { className: "event-meta", style: { fontSize: "14px", color: "#666", marginBottom: "8px" } }, /* @__PURE__ */ React.createElement("div", null, event.formattedDate || formatDateAustralian(event.start), event.formattedTime && ` at ${event.formattedTime}`), /* @__PURE__ */ React.createElement("div", { style: { marginTop: "4px" } }, /* @__PURE__ */ React.createElement("span", { className: "event-type", style: { backgroundColor: "#f0f0f0", padding: "2px 6px", borderRadius: "4px", fontSize: "12px" } }, event.type), event.status && /* @__PURE__ */ React.createElement("span", { className: "event-status", style: { backgroundColor: getStatusColor(event.status), color: "#fff", padding: "2px 6px", borderRadius: "4px", fontSize: "12px", marginLeft: "8px" } }, event.status))), event.description && /* @__PURE__ */ React.createElement("p", { style: { margin: "8px 0", fontSize: "14px", color: "#555" } }, event.description.length > 100 ? `${event.description.substring(0, 100)}...` : event.description), event.venueDetails && /* @__PURE__ */ React.createElement("div", { style: { fontSize: "14px", color: "#666", marginTop: "8px" } }, "\u{1F4CD} ", event.venueDetails.name)), (onEdit || onDelete) && /* @__PURE__ */ React.createElement("div", { className: "event-actions", style: { display: "flex", gap: "8px" } }, onEdit && /* @__PURE__ */ React.createElement("button", { onClick: handleEdit, style: { background: "none", border: "1px solid #ddd", borderRadius: "4px", padding: "4px 8px", cursor: "pointer", fontSize: "12px" } }, "Edit"), onDelete && /* @__PURE__ */ React.createElement("button", { onClick: handleDelete, style: { background: "none", border: "1px solid #ff4444", borderRadius: "4px", padding: "4px 8px", cursor: "pointer", fontSize: "12px", color: "#ff4444" } }, "Delete"))), showFinancials && (event.totalIncome || event.totalExpenses) && /* @__PURE__ */ React.createElement("div", { className: "event-financials", style: { marginTop: "12px", padding: "12px", backgroundColor: "#f8f9fa", borderRadius: "4px", fontSize: "14px" } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "space-between" } }, event.totalIncome && /* @__PURE__ */ React.createElement("span", { style: { color: "#28a745" } }, "Income: ", formatCurrencyAUD(event.totalIncome)), event.totalExpenses && /* @__PURE__ */ React.createElement("span", { style: { color: "#dc3545" } }, "Expenses: ", formatCurrencyAUD(event.totalExpenses))), event.netProfit !== void 0 && /* @__PURE__ */ React.createElement("div", { style: { marginTop: "4px", fontWeight: "600", color: event.netProfit >= 0 ? "#28a745" : "#dc3545" } }, "Net: ", formatCurrencyAUD(event.netProfit))), event.isUpcoming && event.daysUntil !== void 0 && /* @__PURE__ */ React.createElement("div", { style: { marginTop: "8px", fontSize: "12px", color: "#007bff", fontWeight: "500" } }, event.daysUntil === 0 ? "Today" : event.daysUntil === 1 ? "Tomorrow" : `In ${event.daysUntil} days`)); }, "EventCard"); function getStatusColor(status) { if (!status) return "#6c757d"; switch (status.toLowerCase()) { case "confirmed": return "#28a745"; case "pending": return "#ffc107"; case "cancelled": return "#dc3545"; case "completed": return "#17a2b8"; default: return "#6c757d"; } } __name(getStatusColor, "getStatusColor"); // src/client/components/CalendarView.tsx import React2, { useState } from "react"; var CalendarView = /* @__PURE__ */ __name(({ calendar, events, view = "list", onEventClick, onEventEdit, onEventDelete, onViewChange, showFinancials = false, className = "" }) => { const [currentDate, setCurrentDate] = useState(/* @__PURE__ */ new Date()); const getFilteredEvents = /* @__PURE__ */ __name(() => { const now = /* @__PURE__ */ new Date(); switch (view) { case "day": return events.filter((event) => isToday(event.start)); case "week": return events.filter((event) => isThisWeek(event.start)); case "month": return events.filter((event) => isThisMonth(event.start)); case "list": default: return events.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()); } }, "getFilteredEvents"); const filteredEvents = getFilteredEvents(); const renderViewSelector = /* @__PURE__ */ __name(() => /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: "8px", marginBottom: "16px" } }, [ "day", "week", "month", "list" ].map((viewOption) => /* @__PURE__ */ React2.createElement("button", { key: viewOption, onClick: /* @__PURE__ */ __name(() => onViewChange?.(viewOption), "onClick"), style: { padding: "8px 16px", border: "1px solid #ddd", borderRadius: "4px", backgroundColor: view === viewOption ? "#007bff" : "#fff", color: view === viewOption ? "#fff" : "#333", cursor: "pointer", textTransform: "capitalize" } }, viewOption))), "renderViewSelector"); const renderCalendarHeader = /* @__PURE__ */ __name(() => /* @__PURE__ */ React2.createElement("div", { style: { marginBottom: "20px", padding: "16px", backgroundColor: "#f8f9fa", borderRadius: "8px" } }, /* @__PURE__ */ React2.createElement("h2", { style: { margin: "0 0 8px 0", fontSize: "24px", fontWeight: "600" } }, calendar.name), calendar.description && /* @__PURE__ */ React2.createElement("p", { style: { margin: "0 0 12px 0", color: "#666" } }, calendar.description), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: "20px", fontSize: "14px", color: "#555" } }, /* @__PURE__ */ React2.createElement("span", null, "\u{1F4C5} ", filteredEvents.length, " events"), calendar.upcomingEventCount !== void 0 && /* @__PURE__ */ React2.createElement("span", null, "\u23F0 ", calendar.upcomingEventCount, " upcoming"), showFinancials && calendar.netProfit !== void 0 && /* @__PURE__ */ React2.createElement("span", { style: { color: calendar.netProfit >= 0 ? "#28a745" : "#dc3545", fontWeight: "600" } }, "\u{1F4B0} Net: $", calendar.netProfit.toFixed(2)))), "renderCalendarHeader"); const renderListView = /* @__PURE__ */ __name(() => /* @__PURE__ */ React2.createElement("div", { className: "calendar-list-view" }, filteredEvents.length === 0 ? /* @__PURE__ */ React2.createElement("div", { style: { textAlign: "center", padding: "40px", color: "#666", backgroundColor: "#f8f9fa", borderRadius: "8px" } }, /* @__PURE__ */ React2.createElement("p", null, "No events found for this view.")) : /* @__PURE__ */ React2.createElement("div", null, filteredEvents.map((event) => /* @__PURE__ */ React2.createElement(EventCard, { key: event.id, event, onClick: onEventClick, onEdit: onEventEdit, onDelete: onEventDelete, showFinancials })))), "renderListView"); const renderGridView = /* @__PURE__ */ __name(() => { const groupedEvents = filteredEvents.reduce((groups, event) => { const dateKey = formatDateAustralian(event.start); if (!groups[dateKey]) { groups[dateKey] = []; } groups[dateKey].push(event); return groups; }, {}); return /* @__PURE__ */ React2.createElement("div", { className: "calendar-grid-view" }, Object.keys(groupedEvents).length === 0 ? /* @__PURE__ */ React2.createElement("div", { style: { textAlign: "center", padding: "40px", color: "#666", backgroundColor: "#f8f9fa", borderRadius: "8px" } }, /* @__PURE__ */ React2.createElement("p", null, "No events found for this ", view, ".")) : /* @__PURE__ */ React2.createElement("div", { style: { display: "grid", gap: "16px" } }, Object.entries(groupedEvents).map(([date, dayEvents]) => /* @__PURE__ */ React2.createElement("div", { key: date, style: { border: "1px solid #e0e0e0", borderRadius: "8px", padding: "16px", backgroundColor: "#fff" } }, /* @__PURE__ */ React2.createElement("h3", { style: { margin: "0 0 12px 0", fontSize: "16px", fontWeight: "600", color: "#333", borderBottom: "1px solid #eee", paddingBottom: "8px" } }, date), /* @__PURE__ */ React2.createElement("div", { style: { display: "grid", gap: "8px" } }, dayEvents.map((event) => /* @__PURE__ */ React2.createElement("div", { key: event.id, onClick: /* @__PURE__ */ __name(() => onEventClick?.(event), "onClick"), style: { padding: "8px 12px", backgroundColor: "#f8f9fa", borderRadius: "4px", cursor: onEventClick ? "pointer" : "default", fontSize: "14px", border: "1px solid transparent" }, onMouseEnter: /* @__PURE__ */ __name((e) => { if (onEventClick) { e.currentTarget.style.backgroundColor = "#e9ecef"; e.currentTarget.style.borderColor = "#dee2e6"; } }, "onMouseEnter"), onMouseLeave: /* @__PURE__ */ __name((e) => { e.currentTarget.style.backgroundColor = "#f8f9fa"; e.currentTarget.style.borderColor = "transparent"; }, "onMouseLeave") }, /* @__PURE__ */ React2.createElement("div", { style: { fontWeight: "500" } }, event.summary), event.formattedTime && /* @__PURE__ */ React2.createElement("div", { style: { fontSize: "12px", color: "#666" } }, event.formattedTime)))))))); }, "renderGridView"); return /* @__PURE__ */ React2.createElement("div", { className: `calendar-view ${className}` }, renderCalendarHeader(), onViewChange && renderViewSelector(), /* @__PURE__ */ React2.createElement("div", { className: "calendar-content" }, view === "list" ? renderListView() : renderGridView())); }, "CalendarView"); // src/shared/constants.ts var DEFAULT_CURRENCY = "AUD"; var MAX_EVENT_DURATION_HOURS = 24; var MAX_RECURRENCE_OCCURRENCES = 365; var DEFAULT_EVENT_DURATION_MINUTES = 60; var MAX_LENGTHS = { EVENT_TITLE: 200, EVENT_DESCRIPTION: 1e3, CALENDAR_NAME: 100, CALENDAR_DESCRIPTION: 500, VENUE_NAME: 200, VENUE_ADDRESS: 300, VENUE_CITY: 100, CONTACT_NAME: 200, CONTACT_ROLE: 100, NOTES: 1e3, INCOME_DESCRIPTION: 200, EXPENSE_DESCRIPTION: 200, RECEIPT_PATH: 500 }; var DATE_FORMATS = { AUSTRALIAN: "DD/MM/YYYY", AMERICAN: "MM/DD/YYYY", ISO: "YYYY-MM-DD" }; var TIME_FORMATS = { TWELVE_HOUR: "12h", TWENTY_FOUR_HOUR: "24h" }; var CALENDAR_VIEWS = { DAY: "day", WEEK: "week", MONTH: "month", LIST: "list" }; var DEFAULT_START_OF_WEEK = 1; var AUSTRALIAN_STATES = { NSW: "New South Wales", VIC: "Victoria", QLD: "Queensland", WA: "Western Australia", SA: "South Australia", TAS: "Tasmania", ACT: "Australian Capital Territory", NT: "Northern Territory" }; var CURRENCIES = { AUD: "Australian Dollar", USD: "US Dollar", EUR: "Euro", GBP: "British Pound", CAD: "Canadian Dollar", NZD: "New Zealand Dollar" }; var VALIDATION_PATTERNS = { EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, PHONE_AU: /^(\+61|0)[2-478](?:[ -]?[0-9]){8}$/, CURRENCY_CODE: /^[A-Z]{3}$/, HEX_COLOR: /^#[0-9A-F]{6}$/i, URL: /^https?:\/\/.+/ }; var API_CONFIG = { DEFAULT_PAGE_SIZE: 20, MAX_PAGE_SIZE: 100, REQUEST_TIMEOUT: 3e4, RETRY_ATTEMPTS: 3 }; var FILE_UPLOAD = { MAX_SIZE_MB: 10, ALLOWED_TYPES: [ "image/jpeg", "image/png", "image/gif", "application/pdf" ], ALLOWED_EXTENSIONS: [ ".jpg", ".jpeg", ".png", ".gif", ".pdf" ] }; export { API_CONFIG, AUSTRALIAN_STATES, CALENDAR_TYPES, CALENDAR_VIEWS, CURRENCIES, CalendarView, DATE_FORMATS, DEFAULT_CURRENCY, DEFAULT_EVENT_DURATION_MINUTES, DEFAULT_START_OF_WEEK, DIFFICULTY_LEVELS, EVENT_STATUS, EVENT_TYPES, EventCard, FILE_UPLOAD, GENRES, INSTRUMENTS, MAX_EVENT_DURATION_HOURS, MAX_LENGTHS, MAX_RECURRENCE_OCCURRENCES, PAYMENT_STATUS, STUDENT_LEVELS, TIME_FORMATS, VALIDATION_PATTERNS, addDays, addMonths, calculateFinancialSummary, capitalizeWords, daysUntil, endOfDay, enhanceClientEvent, expandRecurrence, filterEvents, formatAddressAustralian, formatCurrencyAUD, formatDateAustralian, formatDateTimeAustralian, formatDuration, formatFileSize, formatNumber, formatPercentage, formatPhoneAustralian, formatTimeAustralian, generateTempId, getDayName, getDurationMinutes, getEndOfWeek, getInitials, getMonthName, getStartOfWeek, getTodaysEvents, getUpcomingEvents, groupEventsByDate, isFuture, isPast, isSameDay, isThisMonth, isThisWeek, isToday, isValidEmail, isValidPhone, sortEvents, startOfDay, truncateText, validateCalendar, validateContact, validateEvent, validateExpense, validateIncome, validateRecurrenceRule, validateVenue }; //# sourceMappingURL=index.mjs.map