npm package: @lemonadejs/schedule
Description: A reactive schedule and calendar component for managing events and appointments with drag-and-drop, time ranges, and weekly views
Author: Contact
Latest version: 5.0.5

# LemonadeJS Schedule

A reactive schedule and calendar component for managing events and appointments with drag-and-drop support, time range validation, and multiple view modes.

**Compatible with Vanilla JavaScript, LemonadeJS, React, Vue, and Angular.**

## Features

- 📅 **Multiple View Modes**: Day, week, and weekdays views
- 🔄 **Drag & Drop**: Move and resize events with mouse
- ⏰ **Time Validation**: Define valid working hours and read-only time ranges
- 📊 **Weekly Schedule**: Create recurring weekly schedules without specific dates
- ⌨️ **Keyboard Support**: Arrow keys, delete, copy/paste (Ctrl+C/V), undo/redo (Ctrl+Z/Y)
- 🎨 **Customizable**: Event colors, grid intervals, and overlap control
- ♿ **Accessibility**: Full keyboard navigation support
- 📱 **Responsive**: Adapts to different screen sizes

## Installation

### NPM

```bash
npm install @lemonadejs/schedule
```

### CDN

```html
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/schedule/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/schedule/dist/style.min.css" />
```

## Basic Usage

### Simple Schedule

```javascript
import Schedule from '@lemonadejs/schedule';

const root = document.getElementById('schedule');

const schedule = Schedule(root, {
    type: 'week',
    value: '2025-01-15',
    data: [
        {
            title: 'Team Meeting',
            start: '09:00',
            end: '10:00',
            date: '2025-01-15',
            color: '#3f51b5'
        },
        {
            title: 'Lunch Break',
            start: '12:00',
            end: '13:00',
            date: '2025-01-15',
            color: '#4caf50'
        }
    ]
});
```

### Weekly Recurring Schedule

Perfect for class schedules, gym timetables, or any recurring weekly events:

```javascript
const schedule = Schedule(root, {
    type: 'weekdays',
    weekly: true,
    grid: 30, // 30-minute intervals
    data: [
        {
            title: 'Math Class',
            start: '09:00',
            end: '10:30',
            weekday: 1, // Monday (0=Sunday, 1=Monday, ..., 6=Saturday)
            color: '#2196f3'
        },
        {
            title: 'Physics Lab',
            start: '14:00',
            end: '16:00',
            weekday: 3, // Wednesday
            color: '#ff9800'
        }
    ]
});
```

### School Schedule with Working Hours

```javascript
const schedule = Schedule(root, {
    type: 'weekdays',
    weekly: true,
    grid: 30,
    validRange: ['08:00', '18:00'], // Only show 8 AM to 6 PM
    readOnlyRange: [
        ['08:00', '09:00'], // Before school
        ['12:00', '13:00'], // Lunch break
        ['17:00', '18:00']  // After school
    ],
    overlap: false, // Prevent overlapping classes
    data: []
});
```

## Framework Integration

### React

The Schedule component includes a React wrapper for seamless integration.

#### Installation

```bash
npm install @lemonadejs/schedule react
```

#### Basic React Example

```jsx
import React, { useRef } from 'react';
import Schedule from '@lemonadejs/schedule/dist/react';
import '@lemonadejs/schedule/dist/style.css';

export default function App() {
    const scheduleRef = useRef();

    const events = [
        {
            title: 'Team Meeting',
            start: '09:00',
            end: '10:00',
            date: '2025-01-15',
            color: '#3f51b5'
        },
        {
            title: 'Lunch',
            start: '12:00',
            end: '13:00',
            date: '2025-01-15',
            color: '#4caf50'
        }
    ];

    return (
        <div style={{ width: '100%', height: '600px' }}>
            <Schedule
                ref={scheduleRef}
                type="week"
                value="2025-01-15"
                data={events}
                grid={30}
                oncreate={(instance, events) => {
                    console.log('Event created:', events);
                }}
            />
        </div>
    );
}
```

#### React with State Management

```jsx
import React, { useRef, useState } from 'react';
import Schedule from '@lemonadejs/schedule/dist/react';
import '@lemonadejs/schedule/dist/style.css';

export default function ScheduleComponent() {
    const scheduleRef = useRef();
    const [events, setEvents] = useState([
        {
            title: 'Morning Standup',
            start: '09:00',
            end: '09:30',
            date: '2025-01-15',
            color: '#2196f3'
        }
    ]);

    const handleCreateEvent = (instance, newEvents) => {
        console.log('New event created:', newEvents);
        // Update your state or backend
        setEvents([...instance.data]);
    };

    const handleDeleteEvent = (instance, event) => {
        console.log('Event deleted:', event);
        setEvents([...instance.data]);
    };

    const addNewEvent = () => {
        if (scheduleRef.current) {
            scheduleRef.current.addEvents({
                title: 'New Event',
                start: '14:00',
                end: '15:00',
                date: '2025-01-15',
                color: '#ff9800'
            });
        }
    };

    return (
        <div>
            <button onClick={addNewEvent}>Add Event</button>
            <div style={{ width: '100%', height: '600px' }}>
                <Schedule
                    ref={scheduleRef}
                    type="week"
                    value="2025-01-15"
                    data={events}
                    oncreate={handleCreateEvent}
                    ondelete={handleDeleteEvent}
                />
            </div>
        </div>
    );
}
```

#### React Weekly Schedule (Gym/School)

```jsx
import React, { useRef } from 'react';
import Schedule from '@lemonadejs/schedule/dist/react';
import '@lemonadejs/schedule/dist/style.css';

export default function GymSchedule() {
    const scheduleRef = useRef();

    const classes = [
        {
            title: 'Yoga',
            start: '07:00',
            end: '08:00',
            weekday: 1, // Monday
            color: '#9c27b0'
        },
        {
            title: 'Spinning',
            start: '18:00',
            end: '19:00',
            weekday: 2, // Tuesday
            color: '#f44336'
        },
        {
            title: 'Yoga',
            start: '07:00',
            end: '08:00',
            weekday: 3, // Wednesday
            color: '#9c27b0'
        },
        {
            title: 'CrossFit',
            start: '18:00',
            end: '19:30',
            weekday: 4, // Thursday
            color: '#ff5722'
        }
    ];

    return (
        <div style={{ width: '100%', height: '600px' }}>
            <Schedule
                ref={scheduleRef}
                type="weekdays"
                weekly={true}
                grid={30}
                data={classes}
                validRange={['06:00', '21:00']}
                overlap={false}
            />
        </div>
    );
}
```

### Vue

The Schedule component also includes a Vue 3 wrapper.

#### Installation

```bash
npm install @lemonadejs/schedule vue
```

#### Basic Vue Example

```vue
<template>
    <div style="width: 100%; height: 600px">
        <Schedule
            type="week"
            :value="currentDate"
            :data="events"
            :grid="30"
            @oncreate="handleCreate"
        />
    </div>
</template>

<script>
import { ref } from 'vue';
import Schedule from '@lemonadejs/schedule/dist/vue';
import '@lemonadejs/schedule/dist/style.css';

export default {
    name: 'App',
    components: {
        Schedule
    },
    setup() {
        const currentDate = ref('2025-01-15');
        const events = ref([
            {
                title: 'Team Meeting',
                start: '09:00',
                end: '10:00',
                date: '2025-01-15',
                color: '#3f51b5'
            },
            {
                title: 'Lunch',
                start: '12:00',
                end: '13:00',
                date: '2025-01-15',
                color: '#4caf50'
            }
        ]);

        const handleCreate = (instance, newEvents) => {
            console.log('Event created:', newEvents);
        };

        return {
            currentDate,
            events,
            handleCreate
        };
    }
};
</script>
```

#### Vue with Reactive State

```vue
<template>
    <div>
        <button @click="addEvent">Add Event</button>
        <button @click="goToNextWeek">Next Week</button>
        <button @click="goToPrevWeek">Previous Week</button>

        <div style="width: 100%; height: 600px">
            <Schedule
                ref="scheduleRef"
                type="week"
                :value="currentDate"
                :data="events"
                :validRange="['08:00', '18:00']"
                @oncreate="handleCreate"
                @ondelete="handleDelete"
            />
        </div>
    </div>
</template>

<script>
import { ref } from 'vue';
import Schedule from '@lemonadejs/schedule/dist/vue';
import '@lemonadejs/schedule/dist/style.css';

export default {
    name: 'ScheduleComponent',
    components: {
        Schedule
    },
    setup() {
        const scheduleRef = ref(null);
        const currentDate = ref('2025-01-15');
        const events = ref([
            {
                title: 'Morning Standup',
                start: '09:00',
                end: '09:30',
                date: '2025-01-15',
                color: '#2196f3'
            }
        ]);

        const handleCreate = (instance, newEvents) => {
            console.log('Event created:', newEvents);
            events.value = [...instance.data];
        };

        const handleDelete = (instance, event) => {
            console.log('Event deleted:', event);
            events.value = [...instance.data];
        };

        const addEvent = () => {
            scheduleRef.value?.current?.addEvents({
                title: 'New Event',
                start: '14:00',
                end: '15:00',
                date: currentDate.value,
                color: '#ff9800'
            });
        };

        const goToNextWeek = () => {
            scheduleRef.value?.current?.next();
        };

        const goToPrevWeek = () => {
            scheduleRef.value?.current?.prev();
        };

        return {
            scheduleRef,
            currentDate,
            events,
            handleCreate,
            handleDelete,
            addEvent,
            goToNextWeek,
            goToPrevWeek
        };
    }
};
</script>
```

#### Vue Weekly Schedule

```vue
<template>
    <div style="width: 100%; height: 600px">
        <Schedule
            type="weekdays"
            :weekly="true"
            :grid="30"
            :data="classes"
            :validRange="['06:00', '21:00']"
            :overlap="false"
        />
    </div>
</template>

<script>
import { ref } from 'vue';
import Schedule from '@lemonadejs/schedule/dist/vue';
import '@lemonadejs/schedule/dist/style.css';

export default {
    name: 'GymSchedule',
    components: {
        Schedule
    },
    setup() {
        const classes = ref([
            {
                title: 'Yoga',
                start: '07:00',
                end: '08:00',
                weekday: 1, // Monday
                color: '#9c27b0'
            },
            {
                title: 'Spinning',
                start: '18:00',
                end: '19:00',
                weekday: 2, // Tuesday
                color: '#f44336'
            },
            {
                title: 'Yoga',
                start: '07:00',
                end: '08:00',
                weekday: 3, // Wednesday
                color: '#9c27b0'
            },
            {
                title: 'CrossFit',
                start: '18:00',
                end: '19:30',
                weekday: 4, // Thursday
                color: '#ff5722'
            }
        ]);

        return {
            classes
        };
    }
};
</script>
```

## API Reference

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `type` | `'week' \| 'day' \| 'weekdays'` | `'week'` | View type for the schedule |
| `weekly` | `boolean` | `false` | Enable weekly mode (recurring events without dates) |
| `value` | `string` | Current date | Initial date in ISO format (YYYY-MM-DD) |
| `data` | `Event[]` | `[]` | Array of events |
| `grid` | `number` | `15` | Time grid interval in minutes |
| `overlap` | `boolean` | `false` | Allow overlapping events |
| `validRange` | `string[]` | `[]` | Valid time range `['HH:MM', 'HH:MM']` (hides other hours) |
| `readOnlyRange` | `string[] \| string[][]` | `[]` | Read-only time ranges (single or multiple) |
| `onbeforecreate` | `function` | - | Callback before creating events |
| `oncreate` | `function` | - | Callback after creating events |
| `onchangeevent` | `function` | - | Callback when event properties change |
| `ondelete` | `function` | - | Callback when event is deleted |
| `onerror` | `function` | - | Callback for error handling |

### Event Object

```typescript
{
    title: string;           // Event title
    start: string;          // Start time in HH:MM format
    end: string;            // End time in HH:MM format
    date?: string;          // Date in ISO format (YYYY-MM-DD) - for normal mode
    weekday?: number;       // Day of week (0-6, Sunday is 0) - for weekly mode
    color?: string;         // Hex color code
    description?: string;   // Event description
    location?: string;      // Event location
    guests?: string;        // Guest list
    readonly?: boolean;     // Make event read-only
    guid?: string;         // Unique identifier (auto-generated)
}
```

### Methods

```javascript
// Add events
schedule.addEvents(event | events[]);

// Update event
schedule.updateEvent(guid, { title: 'New Title', start: '10:00' });

// Delete events
schedule.deleteEvents(guid | guid[] | event | event[]);

// Navigation
schedule.next();  // Next week/day
schedule.prev();  // Previous week/day

// History
schedule.undo();  // Undo last change
schedule.redo();  // Redo last change

// Time ranges
schedule.setRange(['08:00', '18:00']);
schedule.setReadOnly([['00:00', '08:00'], ['18:00', '23:59']]);

// Data management
schedule.getData();           // Get all events
schedule.setData(events);     // Set events data
schedule.getEvent(guid);      // Get event DOM element

// Render
schedule.render();  // Re-render schedule
```

## Examples

### Example 1: Office Hours Schedule

```javascript
const officeSchedule = Schedule(root, {
    type: 'week',
    value: '2025-01-20',
    grid: 30,
    validRange: ['08:00', '18:00'],
    readOnlyRange: ['12:00', '13:00'], // Lunch break
    oncreate: (instance, events) => {
        console.log('New appointment created:', events);
    },
    onerror: (instance, message) => {
        alert(message);
    }
});
```

### Example 2: Gym Class Schedule

```javascript
const gymSchedule = Schedule(root, {
    type: 'weekdays',
    weekly: true,
    grid: 60,
    data: [
        {
            title: 'Yoga',
            start: '07:00',
            end: '08:00',
            weekday: 1, // Monday
            color: '#9c27b0'
        },
        {
            title: 'Spinning',
            start: '18:00',
            end: '19:00',
            weekday: 2, // Tuesday
            color: '#f44336'
        },
        {
            title: 'Yoga',
            start: '07:00',
            end: '08:00',
            weekday: 3, // Wednesday
            color: '#9c27b0'
        }
    ]
});
```

### Example 3: Interactive Event Creation

```javascript
const schedule = Schedule(root, {
    type: 'week',
    value: '2025-01-20',
    onbeforecreate: (instance, events) => {
        // Validate event before creation
        const event = Array.isArray(events) ? events[0] : events;

        if (event.start < '09:00') {
            alert('Cannot create events before 9 AM');
            return false; // Cancel creation
        }

        return true; // Allow creation
    },
    oncreate: (instance, events) => {
        // Event was created successfully
        console.log('Event created:', events);
    }
});
```

### Example 4: Multiple Read-Only Time Ranges

```javascript
const schedule = Schedule(root, {
    type: 'week',
    value: '2025-01-20',
    validRange: ['00:00', '23:59'], // Show all hours
    readOnlyRange: [
        ['00:00', '08:00'], // Early morning
        ['12:00', '13:00'], // Lunch
        ['18:00', '23:59']  // Evening
    ]
});
```

## Keyboard Shortcuts

| Shortcut | Action |
|----------|--------|
| `Ctrl+Z` | Undo last change |
| `Ctrl+Y` | Redo last change |
| `Ctrl+C` | Copy selected events |
| `Ctrl+V` | Paste copied events |
| `Delete` | Delete selected events |
| `Arrow Keys` | Navigate between events |

## Styling

The schedule uses CSS custom properties for easy customization:

```css
.lm-schedule {
    --lm-schedule-border: #e0e0e0;
    --lm-schedule-bg: #ffffff;
    --lm-schedule-text: #000000;
}

/* Customize event colors */
.lm-schedule-item {
    border-radius: 4px;
    padding: 4px 8px;
}

/* Disabled time slots */
.lm-schedule-disabled {
    background-color: #f5f5f5;
    cursor: not-allowed;
}
```

## Browser Support

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)

## License

This component is part of the LemonadeJS plugins library.

## Links

- [LemonadeJS Website](https://lemonadejs.com)
- [Documentation](https://lemonadejs.net/docs/plugins/schedule)
- [GitHub Repository](https://github.com/lemonadejs/lemonadejs)
- [Issue Tracker](https://github.com/lemonadejs/lemonadejs/issues)

## Contributing

We welcome contributions! Please see the main repository for contribution guidelines.