# @crescender/calendar

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

## 🚨 Version 0.3.0 Breaking Changes

**This version introduces client/server separation.** If you're upgrading from v0.2.x, please see the [Migration Guide](./MIGRATION.md) for detailed upgrade instructions.

## Features

- **Client/Server Architecture**: Clean separation between browser-safe client code and Node.js server operations
- **Full CRUD operations** for events and calendars
- **Musician-specific event types**: gigs, lessons, auditions, practices, rehearsals, recordings
- **Financial tracking**: Income and expense management per event
- **Venue management**: Store and associate venues with events
- **Contact management**: Track students, band members, promoters, etc.
- **ICS export** for calendar compatibility
- **Recurrence support** with RFC 5545 RRULE
- **React components** for rapid UI development
- **Enhanced client utilities** for event processing and validation
- **PostgreSQL backend** for robust data storage

## Installation

```bash
npm install @crescender/calendar
```

## Quick Start

### Server-Side (Node.js/API Routes)

```typescript
import { DataSource } from 'typeorm';
import { 
  initDb, 
  createEvent, 
  Event, 
  Calendar, 
  addEventIncome 
} from '@crescender/calendar/server';

// Initialize database
const dataSource = new DataSource({
  type: 'postgres',
  // ... your config
  entities: [Event, Calendar, /* other entities */],
});

await dataSource.initialize();
initDb(dataSource);

// Create an event
const event = await createEvent('calendar-id', {
  summary: 'Jazz Gig',
  start: new Date('2025-02-15T20:00:00+11:00'),
  end: new Date('2025-02-15T23:00:00+11:00'),
  type: 'gig'
});
```

### Client-Side (React/Browser)

```typescript
import { 
  EventCard, 
  validateEvent, 
  enhanceClientEvent,
  formatDateAustralian 
} from '@crescender/calendar/client';

function EventList({ events }) {
  const enhancedEvents = events.map(enhanceClientEvent);
  
  return (
    <div>
      {enhancedEvents.map(event => (
        <EventCard key={event.id} event={event} />
      ))}
    </div>
  );
}

// Form validation
const validation = validateEvent(formData);
if (validation.isValid) {
  // Submit form
}
```

### Shared Types & Constants

```typescript
import { EVENT_TYPES, PAYMENT_STATUS } from '@crescender/calendar';
// OR
import { EVENT_TYPES, PAYMENT_STATUS } from '@crescender/calendar/shared';
```

## Server-Side Usage (Node.js)

```typescript
import { DataSource } from 'typeorm';
import { 
  initDb, 
  createEvent, 
  Event, 
  Calendar, 
  Venue, 
  Contact,
  EventIncome,
  EventExpense,
  addEventIncome,
  addEventExpense,
  calculateEventProfit 
} from '@crescender/calendar/server';

// Initialize database connection
const dataSource = new DataSource({
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'your_username',
  password: 'your_password',
  database: 'your_database',
  entities: [Event, Calendar, Venue, Contact, EventIncome, EventExpense],
  synchronize: true, // Don't use in production
});

await dataSource.initialize();
initDb(dataSource);
```

## Musician-Specific Features

### Creating a Gig with Financial Tracking

```typescript
import { 
  createVenue, 
  createContact, 
  createEvent,
  addEventIncome,
  addEventExpense,
  calculateEventProfit 
} from '@crescender/calendar/server';

// Create a venue
const venue = await createVenue({
  name: 'The Jazz Corner',
  address: '123 Music St',
  city: 'Melbourne',
  state: 'VIC',
  country: 'Australia',
  contactName: 'Sarah Johnson',
  contactEmail: 'sarah@jazzcorner.com.au',
  contactPhone: '+61 3 9876 5432'
});

// Create a promoter contact
const promoter = await createContact({
  name: 'Mike Smith',
  email: 'mike@promotions.com.au',
  phone: '+61 4 1234 5678',
  role: 'promoter'
});

// Create a gig event
const gig = await createEvent('calendar-id', {
  summary: 'Jazz Quartet Performance',
  description: 'Evening jazz performance featuring original compositions',
  start: new Date('2025-02-15T20:00:00+11:00'),
  end: new Date('2025-02-15T23:00:00+11:00'),
  type: 'gig',
  genre: 'Jazz',
  instrument: 'Piano',
  difficulty: 'Professional',
  repertoire: 'Original compositions and jazz standards',
  setList: JSON.stringify([
    'Take Five',
    'Blue Rondo à la Turk',
    'Original Composition #1',
    'Autumn Leaves'
  ]),
  equipmentNeeded: JSON.stringify(['Piano', 'Microphone', 'Music stand']),
  dresscode: 'Smart casual',
  soundcheckTime: new Date('2025-02-15T19:00:00+11:00'),
  loadInTime: new Date('2025-02-15T18:30:00+11:00'),
  paymentStatus: 'Confirmed',
  status: 'Confirmed',
  venue,
  primaryContact: promoter
});

// Add income streams
await addEventIncome(gig.id, {
  description: 'Performance fee',
  amount: 800.00,
  currency: 'AUD',
  notes: 'Flat rate for 3-hour performance'
});

await addEventIncome(gig.id, {
  description: 'Merchandise sales',
  amount: 150.00,
  currency: 'AUD',
  notes: 'CDs and t-shirts sold during interval'
});

// Add expenses
await addEventExpense(gig.id, {
  description: 'Travel costs',
  amount: 45.00,
  currency: 'AUD',
  notes: 'Petrol and parking'
});

await addEventExpense(gig.id, {
  description: 'Equipment hire',
  amount: 120.00,
  currency: 'AUD',
  notes: 'Piano tuning and microphone rental'
});

// Calculate profit
const profit = await calculateEventProfit(gig.id);
console.log(`Net profit: $${profit.toFixed(2)}`); // Net profit: $785.00
```

### Creating Music Lessons

```typescript
import { createContact, createEvent, addEventIncome } from '@crescender/calendar/server';

// Create a student contact
const student = await createContact({
  name: 'Emma Wilson',
  email: 'emma.wilson@email.com',
  phone: '+61 4 9876 5432',
  role: 'student',
  notes: 'Grade 6 piano, preparing for AMEB exam'
});

// Create recurring weekly lessons
const lesson = await createEvent('calendar-id', {
  summary: 'Piano Lesson - Emma Wilson',
  description: 'Grade 6 piano lesson focusing on exam preparation',
  start: new Date('2025-02-10T16:00:00+11:00'),
  end: new Date('2025-02-10T17:00:00+11:00'),
  type: 'lesson',
  recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO;COUNT=12', // 12 weekly lessons
  instrument: 'Piano',
  studentLevel: 'Grade 6',
  lessonFocus: 'AMEB exam preparation',
  repertoire: 'Bach Invention No. 4, Chopin Waltz in A minor',
  paymentStatus: 'Paid',
  status: 'Confirmed',
  primaryContact: student
});

// Add lesson income
await addEventIncome(lesson.id, {
  description: 'Lesson fee',
  amount: 65.00,
  currency: 'AUD',
  notes: '1-hour private lesson'
});
```

### Financial Reporting

```typescript
import { getEventsByType, getFinancialSummary, getUpcomingGigs } from '@crescender/calendar/server';

// Get all gigs for the month
const gigs = await getEventsByType('calendar-id', 'gig');
const gigIds = gigs.map(g => g.id);

// Generate financial summary
const summary = await getFinancialSummary(gigIds);
console.log(`
Monthly Gig Summary:
- Total Income: $${summary.totalIncome.toFixed(2)}
- Total Expenses: $${summary.totalExpenses.toFixed(2)}
- Net Profit: $${summary.netProfit.toFixed(2)}
- Number of Gigs: ${summary.eventCount}
- Average Profit per Gig: $${summary.averageProfitPerEvent.toFixed(2)}
`);

// Get upcoming gigs
const upcomingGigs = await getUpcomingGigs('calendar-id', 5);
upcomingGigs.forEach(gig => {
  console.log(`${gig.summary} - ${gig.start.toLocaleDateString()} at ${gig.venue?.name}`);
});
```

## Client-Side Usage (React/Browser)

### Event Processing and Validation

```typescript
import { 
  validateEvent, 
  validateIncome, 
  validateExpense,
  enhanceClientEvent,
  formatDateAustralian,
  formatCurrency,
  calculateFinancials 
} from '@crescender/calendar/client';

// Form validation
const eventValidation = validateEvent({
  title: 'Jazz Gig',
  startDate: '15/Feb/2025',
  startTime: '20:00',
  endDate: '15/Feb/2025', 
  endTime: '23:00',
  eventType: 'gig'
});

if (eventValidation.isValid) {
  // Form is valid, submit to server
  console.log('Event data is valid');
} else {
  // Show validation errors
  console.log('Validation errors:', eventValidation.errors);
}

// Enhance events with computed properties
const rawEvent = await fetch('/api/events/123').then(r => r.json());
const enhancedEvent = enhanceClientEvent(rawEvent);

console.log(enhancedEvent.duration); // "3 hours"
console.log(enhancedEvent.profit); // 650.00
console.log(enhancedEvent.formattedDate); // "15/Feb/2025"
console.log(enhancedEvent.formattedTime); // "8:00 PM - 11:00 PM"
```

### React Components

```typescript
import { EventCard, CalendarView } from '@crescender/calendar/client';
import type { IEvent } from '@crescender/calendar/shared';

interface EventListProps {
  events: IEvent[];
  onEdit: (event: IEvent) => void;
  onDelete: (eventId: string) => void;
}

function EventList({ events, onEdit, onDelete }: EventListProps) {
  return (
    <div className="event-list">
      {events.map(event => (
        <EventCard
          key={event.id}
          event={event}
          onEdit={() => onEdit(event)}
          onDelete={() => onDelete(event.id)}
          showFinancials={event.type === 'gig'}
        />
      ))}
    </div>
  );
}

// Calendar view component
function MyCalendar({ events }: { events: IEvent[] }) {
  return (
    <CalendarView
      events={events}
      onEventClick={handleEventClick}
      onDateClick={handleDateClick}
      view="month"
    />
  );
}
```

### Advanced Event Processing

```typescript
import { 
  filterEvents, 
  sortEvents, 
  groupEventsByDate,
  expandRecurrence 
} from '@crescender/calendar/client';

// Filter events
const upcomingGigs = filterEvents(events, { 
  type: 'gig', 
  status: 'confirmed',
  dateRange: { start: new Date(), end: addDays(new Date(), 30) }
});

// Sort events
const sortedEvents = sortEvents(events, 'start', 'asc');

// Group events by date for calendar display
const groupedEvents = groupEventsByDate(events);
console.log(groupedEvents['2025-02-15']); // Array of events on that date

// Expand recurring events
const recurringEvent = events.find(e => e.recurrenceRule);
if (recurringEvent) {
  const occurrences = expandRecurrence(
    recurringEvent, 
    new Date('2025-01-01'), 
    new Date('2025-12-31')
  );
  console.log(`${occurrences.length} occurrences this year`);
}
```

## Event Types

The library supports various musician-specific event types:

- **`gig`**: Performances, concerts, shows
- **`lesson`**: Music teaching sessions
- **`audition`**: Auditions for bands, orchestras, etc.
- **`practice`**: Personal practice sessions
- **`rehearsal`**: Band or ensemble rehearsals
- **`recording`**: Studio recording sessions
- **`meeting`**: Business meetings, planning sessions

## Custom Fields

Each event can include musician-specific fields:

- **`genre`**: Jazz, Classical, Rock, Pop, etc.
- **`instrument`**: Primary instrument for the event
- **`difficulty`**: Beginner, Intermediate, Advanced, Professional
- **`repertoire`**: Songs or pieces to be performed/practiced
- **`setList`**: JSON array of songs in performance order
- **`equipmentNeeded`**: JSON array of required equipment
- **`dresscode`**: Performance attire requirements
- **`soundcheckTime`** & **`loadInTime`**: For gigs
- **`paymentStatus`** & **`paymentDueDate`**: Financial tracking
- **`studentLevel`** & **`lessonFocus`**: For teaching
- **`auditionPiece`** & **`auditionRequirements`**: For auditions
- **`practiceGoals`** & **`rehearsalNotes`**: For practice/rehearsal sessions

## API Reference

### Core Functions
- `initDb(dataSource)` - Initialize database connection
- `createEvent(calendarId, eventData)` - Create new event
- `updateEvent(eventId, updates)` - Update existing event
- `deleteEvent(eventId)` - Delete event
- `getEventsByCalendar(calendarId)` - Get all events for calendar
- `getEventsByType(calendarId, type)` - Get events by type

### Financial Functions
- `addEventIncome(eventId, income)` - Add income to event
- `addEventExpense(eventId, expense)` - Add expense to event
- `calculateEventProfit(eventId)` - Calculate net profit
- `getFinancialSummary(eventIds)` - Generate financial report

### Venue & Contact Functions
- `createVenue(venueData)` - Create venue
- `createContact(contactData)` - Create contact
- `getUpcomingGigs(calendarId, limit)` - Get upcoming performances
- `getStudentLessons(calendarId, studentId)` - Get lessons for student

### Export Functions
- `generateIcs(calendar, calendarId)` - Generate ICS calendar feed

## License

MIT
