@crescender/calendar
Version:
A comprehensive TypeScript calendar library with musician-specific capabilities, architected for client/server separation.
1,471 lines (1,461 loc) • 45.7 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/client/index.ts
var client_exports = {};
__export(client_exports, {
API_CONFIG: () => API_CONFIG,
AUSTRALIAN_STATES: () => AUSTRALIAN_STATES,
CALENDAR_TYPES: () => CALENDAR_TYPES,
CALENDAR_VIEWS: () => CALENDAR_VIEWS,
CURRENCIES: () => CURRENCIES,
CalendarView: () => CalendarView,
DATE_FORMATS: () => DATE_FORMATS,
DEFAULT_CURRENCY: () => DEFAULT_CURRENCY,
DEFAULT_EVENT_DURATION_MINUTES: () => DEFAULT_EVENT_DURATION_MINUTES,
DEFAULT_START_OF_WEEK: () => DEFAULT_START_OF_WEEK,
DIFFICULTY_LEVELS: () => DIFFICULTY_LEVELS,
EVENT_STATUS: () => EVENT_STATUS,
EVENT_TYPES: () => EVENT_TYPES,
EventCard: () => EventCard,
FILE_UPLOAD: () => FILE_UPLOAD,
GENRES: () => GENRES,
INSTRUMENTS: () => INSTRUMENTS,
MAX_EVENT_DURATION_HOURS: () => MAX_EVENT_DURATION_HOURS,
MAX_LENGTHS: () => MAX_LENGTHS,
MAX_RECURRENCE_OCCURRENCES: () => MAX_RECURRENCE_OCCURRENCES,
PAYMENT_STATUS: () => PAYMENT_STATUS,
STUDENT_LEVELS: () => STUDENT_LEVELS,
TIME_FORMATS: () => TIME_FORMATS,
VALIDATION_PATTERNS: () => VALIDATION_PATTERNS,
addDays: () => addDays,
addMonths: () => addMonths,
calculateFinancialSummary: () => calculateFinancialSummary,
capitalizeWords: () => capitalizeWords,
daysUntil: () => daysUntil,
endOfDay: () => endOfDay,
enhanceClientEvent: () => enhanceClientEvent,
expandRecurrence: () => expandRecurrence,
filterEvents: () => filterEvents,
formatAddressAustralian: () => formatAddressAustralian,
formatCurrencyAUD: () => formatCurrencyAUD,
formatDateAustralian: () => formatDateAustralian,
formatDateTimeAustralian: () => formatDateTimeAustralian,
formatDuration: () => formatDuration,
formatFileSize: () => formatFileSize,
formatNumber: () => formatNumber,
formatPercentage: () => formatPercentage,
formatPhoneAustralian: () => formatPhoneAustralian,
formatTimeAustralian: () => formatTimeAustralian,
generateTempId: () => generateTempId,
getDayName: () => getDayName,
getDurationMinutes: () => getDurationMinutes,
getEndOfWeek: () => getEndOfWeek,
getInitials: () => getInitials,
getMonthName: () => getMonthName,
getStartOfWeek: () => getStartOfWeek,
getTodaysEvents: () => getTodaysEvents,
getUpcomingEvents: () => getUpcomingEvents,
groupEventsByDate: () => groupEventsByDate,
isFuture: () => isFuture,
isPast: () => isPast,
isSameDay: () => isSameDay,
isThisMonth: () => isThisMonth,
isThisWeek: () => isThisWeek,
isToday: () => isToday,
isValidEmail: () => isValidEmail,
isValidPhone: () => isValidPhone,
sortEvents: () => sortEvents,
startOfDay: () => startOfDay,
truncateText: () => truncateText,
validateCalendar: () => validateCalendar,
validateContact: () => validateContact,
validateEvent: () => validateEvent,
validateExpense: () => validateExpense,
validateIncome: () => validateIncome,
validateRecurrenceRule: () => validateRecurrenceRule,
validateVenue: () => validateVenue
});
module.exports = __toCommonJS(client_exports);
// 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
var import_rrule = require("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 import_rrule.RRule({
...import_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
var import_react = __toESM(require("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__ */ import_react.default.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__ */ import_react.default.createElement("div", {
className: "event-card-header",
style: {
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start"
}
}, /* @__PURE__ */ import_react.default.createElement("div", {
className: "event-info",
style: {
flex: 1
}
}, /* @__PURE__ */ import_react.default.createElement("h3", {
style: {
margin: "0 0 8px 0",
fontSize: "18px",
fontWeight: "600"
}
}, event.summary), /* @__PURE__ */ import_react.default.createElement("div", {
className: "event-meta",
style: {
fontSize: "14px",
color: "#666",
marginBottom: "8px"
}
}, /* @__PURE__ */ import_react.default.createElement("div", null, event.formattedDate || formatDateAustralian(event.start), event.formattedTime && ` at ${event.formattedTime}`), /* @__PURE__ */ import_react.default.createElement("div", {
style: {
marginTop: "4px"
}
}, /* @__PURE__ */ import_react.default.createElement("span", {
className: "event-type",
style: {
backgroundColor: "#f0f0f0",
padding: "2px 6px",
borderRadius: "4px",
fontSize: "12px"
}
}, event.type), event.status && /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("p", {
style: {
margin: "8px 0",
fontSize: "14px",
color: "#555"
}
}, event.description.length > 100 ? `${event.description.substring(0, 100)}...` : event.description), event.venueDetails && /* @__PURE__ */ import_react.default.createElement("div", {
style: {
fontSize: "14px",
color: "#666",
marginTop: "8px"
}
}, "\u{1F4CD} ", event.venueDetails.name)), (onEdit || onDelete) && /* @__PURE__ */ import_react.default.createElement("div", {
className: "event-actions",
style: {
display: "flex",
gap: "8px"
}
}, onEdit && /* @__PURE__ */ import_react.default.createElement("button", {
onClick: handleEdit,
style: {
background: "none",
border: "1px solid #ddd",
borderRadius: "4px",
padding: "4px 8px",
cursor: "pointer",
fontSize: "12px"
}
}, "Edit"), onDelete && /* @__PURE__ */ import_react.default.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__ */ import_react.default.createElement("div", {
className: "event-financials",
style: {
marginTop: "12px",
padding: "12px",
backgroundColor: "#f8f9fa",
borderRadius: "4px",
fontSize: "14px"
}
}, /* @__PURE__ */ import_react.default.createElement("div", {
style: {
display: "flex",
justifyContent: "space-between"
}
}, event.totalIncome && /* @__PURE__ */ import_react.default.createElement("span", {
style: {
color: "#28a745"
}
}, "Income: ", formatCurrencyAUD(event.totalIncome)), event.totalExpenses && /* @__PURE__ */ import_react.default.createElement("span", {
style: {
color: "#dc3545"
}
}, "Expenses: ", formatCurrencyAUD(event.totalExpenses))), event.netProfit !== void 0 && /* @__PURE__ */ import_react.default.createElement("div", {
style: {
marginTop: "4px",
fontWeight: "600",
color: event.netProfit >= 0 ? "#28a745" : "#dc3545"
}
}, "Net: ", formatCurrencyAUD(event.netProfit))), event.isUpcoming && event.daysUntil !== void 0 && /* @__PURE__ */ import_react.default.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
var import_react2 = __toESM(require("react"));
var CalendarView = /* @__PURE__ */ __name(({ calendar, events, view = "list", onEventClick, onEventEdit, onEventDelete, onViewChange, showFinancials = false, className = "" }) => {
const [currentDate, setCurrentDate] = (0, import_react2.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__ */ import_react2.default.createElement("div", {
style: {
display: "flex",
gap: "8px",
marginBottom: "16px"
}
}, [
"day",
"week",
"month",
"list"
].map((viewOption) => /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.createElement("div", {
style: {
marginBottom: "20px",
padding: "16px",
backgroundColor: "#f8f9fa",
borderRadius: "8px"
}
}, /* @__PURE__ */ import_react2.default.createElement("h2", {
style: {
margin: "0 0 8px 0",
fontSize: "24px",
fontWeight: "600"
}
}, calendar.name), calendar.description && /* @__PURE__ */ import_react2.default.createElement("p", {
style: {
margin: "0 0 12px 0",
color: "#666"
}
}, calendar.description), /* @__PURE__ */ import_react2.default.createElement("div", {
style: {
display: "flex",
gap: "20px",
fontSize: "14px",
color: "#555"
}
}, /* @__PURE__ */ import_react2.default.createElement("span", null, "\u{1F4C5} ", filteredEvents.length, " events"), calendar.upcomingEventCount !== void 0 && /* @__PURE__ */ import_react2.default.createElement("span", null, "\u23F0 ", calendar.upcomingEventCount, " upcoming"), showFinancials && calendar.netProfit !== void 0 && /* @__PURE__ */ import_react2.default.createElement("span", {
style: {
color: calendar.netProfit >= 0 ? "#28a745" : "#dc3545",
fontWeight: "600"
}
}, "\u{1F4B0} Net: $", calendar.netProfit.toFixed(2)))), "renderCalendarHeader");
const renderListView = /* @__PURE__ */ __name(() => /* @__PURE__ */ import_react2.default.createElement("div", {
className: "calendar-list-view"
}, filteredEvents.length === 0 ? /* @__PURE__ */ import_react2.default.createElement("div", {
style: {
textAlign: "center",
padding: "40px",
color: "#666",
backgroundColor: "#f8f9fa",
borderRadius: "8px"
}
}, /* @__PURE__ */ import_react2.default.createElement("p", null, "No events found for this view.")) : /* @__PURE__ */ import_react2.default.createElement("div", null, filteredEvents.map((event) => /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.createElement("div", {
className: "calendar-grid-view"
}, Object.keys(groupedEvents).length === 0 ? /* @__PURE__ */ import_react2.default.createElement("div", {
style: {
textAlign: "center",
padding: "40px",
color: "#666",
backgroundColor: "#f8f9fa",
borderRadius: "8px"
}
}, /* @__PURE__ */ import_react2.default.createElement("p", null, "No events found for this ", view, ".")) : /* @__PURE__ */ import_react2.default.createElement("div", {
style: {
display: "grid",
gap: "16px"
}
}, Object.entries(groupedEvents).map(([date, dayEvents]) => /* @__PURE__ */ import_react2.default.createElement("div", {
key: date,
style: {
border: "1px solid #e0e0e0",
borderRadius: "8px",
padding: "16px",
backgroundColor: "#fff"
}
}, /* @__PURE__ */ import_react2.default.createElement("h3", {
style: {
margin: "0 0 12px 0",
fontSize: "16px",
fontWeight: "600",
color: "#333",
borderBottom: "1px solid #eee",
paddingBottom: "8px"
}
}, date), /* @__PURE__ */ import_react2.default.createElement("div", {
style: {
display: "grid",
gap: "8px"
}
}, dayEvents.map((event) => /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.createElement("div", {
style: {
fontWeight: "500"
}
}, event.summary), event.formattedTime && /* @__PURE__ */ import_react2.default.createElement("div", {
style: {
fontSize: "12px",
color: "#666"
}
}, event.formattedTime))))))));
}, "renderGridView");
return /* @__PURE__ */ import_react2.default.createElement("div", {
className: `calendar-view ${className}`
}, renderCalendarHeader(), onViewChange && renderViewSelector(), /* @__PURE__ */ import_react2.default.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"
]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
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.js.map