Event Manager
This demo showcases an integrated event management system combining the Calendar and Timeline plugins. Schedule events, add milestones, and see your timeline come to life with real-time updates.
<html>
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/calendar/dist/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/calendar/dist/style.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/style.min.css" />
<div id="root"></div>
<script>
function EventManager() {
const self = this;
// Sample events
const initialEvents = [
{ date: new Date(2025, 0, 15), title: 'Project Kickoff', type: 'meeting', description: 'Initial planning and team assembly' },
{ date: new Date(2025, 1, 1), title: 'Design Review', type: 'review', description: 'UI/UX design presentation' },
{ date: new Date(2025, 1, 15), title: 'Sprint 1 Complete', type: 'milestone', description: 'Core features implemented' },
{ date: new Date(2025, 2, 1), title: 'QA Testing', type: 'testing', description: 'Comprehensive testing phase' },
{ date: new Date(2025, 2, 15), title: 'Launch Date', type: 'milestone', description: 'Product goes live!' }
];
self.events = initialEvents;
self.selectedDate = new Date().toISOString().split('T')[0];
self.newEventTitle = '';
self.newEventType = 'event';
self.newEventDescription = '';
// Stats as reactive properties
self.totalEvents = initialEvents.length;
self.milestonesCount = initialEvents.filter(e => e.type === 'milestone').length;
self.meetingsCount = initialEvents.filter(e => e.type === 'meeting').length;
// Reactive properties for calendar and timeline
self.calendarEvents = initialEvents.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
}));
// Timeline with date, title, subtitle (type) and description
self.timelineData = [...initialEvents].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}));
self.onDateChange = function(el, date) {
if (date && date.length === 10 && self.selectedDate !== date) {
self.selectedDate = date;
}
};
self.updateViews = function() {
// Update stats
self.totalEvents = self.events.length;
self.milestonesCount = self.events.filter(e => e.type === 'milestone').length;
self.meetingsCount = self.events.filter(e => e.type === 'meeting').length;
// Update calendar events
self.calendarEvents = self.events.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
}));
// Update timeline data
self.timelineData = [...self.events].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}));
};
self.addEvent = function() {
if (self.newEventTitle && self.selectedDate) {
// Parse date correctly to avoid timezone issues
const [year, month, day] = self.selectedDate.split('-').map(Number);
self.events = [...self.events, {
date: new Date(year, month - 1, day),
title: self.newEventTitle,
type: self.newEventType,
description: self.newEventDescription || 'No description'
}];
self.newEventTitle = '';
self.newEventType = 'event';
self.newEventDescription = '';
self.updateViews();
}
};
return `<div style="padding: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2 style="margin: 0;">Event Manager</h2>
<div style="display: flex; gap: 30px;">
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #3498db;">{{self.totalEvents}}</div>
<div style="font-size: 12px; color: #64748b;">Total Events</div>
</div>
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #e74c3c;">{{self.milestonesCount}}</div>
<div style="font-size: 12px; color: #64748b;">Milestones</div>
</div>
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #2ecc71;">{{self.meetingsCount}}</div>
<div style="font-size: 12px; color: #64748b;">Meetings</div>
</div>
</div>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Calendar View</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0;">
<Calendar :events="self.calendarEvents" :onchange="self.onDateChange" type="inline" footer="false" />
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Add New Event</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0;">
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Selected Date:</label>
<input type="date" :value="self.selectedDate" oninput="self.selectedDate = this.value" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;" />
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Event Title:</label>
<input type="text" :value="self.newEventTitle" oninput="self.newEventTitle = this.value" placeholder="Enter event name..." style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;" />
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Event Type:</label>
<select :value="self.newEventType" onchange="self.newEventType = this.value" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;">
<option value="event">Event</option>
<option value="meeting">Meeting</option>
<option value="review">Review</option>
<option value="milestone">Milestone</option>
<option value="testing">Testing</option>
</select>
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Description:</label>
<textarea :value="self.newEventDescription" oninput="self.newEventDescription = this.value" placeholder="Enter description..." style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px; resize: none; height: 80px;"></textarea>
</div>
<button onclick="self.addEvent()" class="button primary" style="width: 100%;">Add Event</button>
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Timeline View</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0; max-height: 450px; overflow-y: auto;">
<Timeline :data="self.timelineData" align="left" order="desc" />
</div>
</div>
</div>
</div>`;
}
lemonade.render(EventManager, document.getElementById('root'));
</script>
</html>
import lemonade from 'lemonadejs';
import Calendar from '@lemonadejs/calendar';
import Timeline from '@lemonadejs/timeline';
export default function EventManager() {
const self = this;
// Sample events
const initialEvents = [
{ date: new Date(2025, 0, 15), title: 'Project Kickoff', type: 'meeting', description: 'Initial planning and team assembly' },
{ date: new Date(2025, 1, 1), title: 'Design Review', type: 'review', description: 'UI/UX design presentation' },
{ date: new Date(2025, 1, 15), title: 'Sprint 1 Complete', type: 'milestone', description: 'Core features implemented' },
{ date: new Date(2025, 2, 1), title: 'QA Testing', type: 'testing', description: 'Comprehensive testing phase' },
{ date: new Date(2025, 2, 15), title: 'Launch Date', type: 'milestone', description: 'Product goes live!' }
];
self.events = initialEvents;
self.selectedDate = new Date().toISOString().split('T')[0];
self.newEventTitle = '';
self.newEventType = 'event';
self.newEventDescription = '';
// Stats as reactive properties
self.totalEvents = initialEvents.length;
self.milestonesCount = initialEvents.filter(e => e.type === 'milestone').length;
self.meetingsCount = initialEvents.filter(e => e.type === 'meeting').length;
// Reactive properties for calendar and timeline
self.calendarEvents = initialEvents.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
}));
self.timelineData = [...initialEvents].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}));
self.onDateChange = function(el, date) {
if (date && date.length === 10 && self.selectedDate !== date) {
self.selectedDate = date;
}
};
self.updateViews = function() {
self.totalEvents = self.events.length;
self.milestonesCount = self.events.filter(e => e.type === 'milestone').length;
self.meetingsCount = self.events.filter(e => e.type === 'meeting').length;
self.calendarEvents = self.events.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
}));
self.timelineData = [...self.events].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}));
};
self.addEvent = function() {
if (self.newEventTitle && self.selectedDate) {
const [year, month, day] = self.selectedDate.split('-').map(Number);
self.events = [...self.events, {
date: new Date(year, month - 1, day),
title: self.newEventTitle,
type: self.newEventType,
description: self.newEventDescription || 'No description'
}];
self.newEventTitle = '';
self.newEventType = 'event';
self.newEventDescription = '';
self.updateViews();
}
};
return `<div style="padding: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2 style="margin: 0;">Event Manager</h2>
<div style="display: flex; gap: 30px;">
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #3498db;">{{self.totalEvents}}</div>
<div style="font-size: 12px; color: #64748b;">Total Events</div>
</div>
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #e74c3c;">{{self.milestonesCount}}</div>
<div style="font-size: 12px; color: #64748b;">Milestones</div>
</div>
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #2ecc71;">{{self.meetingsCount}}</div>
<div style="font-size: 12px; color: #64748b;">Meetings</div>
</div>
</div>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Calendar View</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0;">
<Calendar :events="self.calendarEvents" :onchange="self.onDateChange" type="inline" footer="false" />
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Add New Event</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0;">
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Selected Date:</label>
<input type="date" :value="self.selectedDate" oninput="self.selectedDate = this.value" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;" />
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Event Title:</label>
<input type="text" :value="self.newEventTitle" oninput="self.newEventTitle = this.value" placeholder="Enter event name..." style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;" />
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Event Type:</label>
<select :value="self.newEventType" onchange="self.newEventType = this.value" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;">
<option value="event">Event</option>
<option value="meeting">Meeting</option>
<option value="review">Review</option>
<option value="milestone">Milestone</option>
<option value="testing">Testing</option>
</select>
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Description:</label>
<textarea :value="self.newEventDescription" oninput="self.newEventDescription = this.value" placeholder="Enter description..." style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px; resize: none; height: 80px;"></textarea>
</div>
<button onclick="self.addEvent()" class="button primary" style="width: 100%;">Add Event</button>
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Timeline View</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0; max-height: 450px; overflow-y: auto;">
<Timeline :data="self.timelineData" align="left" order="desc" />
</div>
</div>
</div>
</div>`;
}
import React, { useState } from 'react';
import { Calendar } from '@lemonadejs/calendar/react';
import { Timeline } from '@lemonadejs/timeline/react';
import '@lemonadejs/calendar/dist/style.min.css';
import '@lemonadejs/timeline/dist/style.min.css';
export default function EventManager() {
const initialEvents = [
{ date: new Date(2025, 0, 15), title: 'Project Kickoff', type: 'meeting', description: 'Initial planning and team assembly' },
{ date: new Date(2025, 1, 1), title: 'Design Review', type: 'review', description: 'UI/UX design presentation' },
{ date: new Date(2025, 1, 15), title: 'Sprint 1 Complete', type: 'milestone', description: 'Core features implemented' },
{ date: new Date(2025, 2, 1), title: 'QA Testing', type: 'testing', description: 'Comprehensive testing phase' },
{ date: new Date(2025, 2, 15), title: 'Launch Date', type: 'milestone', description: 'Product goes live!' }
];
const [events, setEvents] = useState(initialEvents);
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
const [newEventTitle, setNewEventTitle] = useState('');
const [newEventType, setNewEventType] = useState('event');
const [newEventDescription, setNewEventDescription] = useState('');
const [totalEvents, setTotalEvents] = useState(initialEvents.length);
const [milestonesCount, setMilestonesCount] = useState(initialEvents.filter(e => e.type === 'milestone').length);
const [meetingsCount, setMeetingsCount] = useState(initialEvents.filter(e => e.type === 'meeting').length);
const [calendarEvents, setCalendarEvents] = useState(
initialEvents.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
}))
);
const [timelineData, setTimelineData] = useState(
[...initialEvents].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}))
);
const handleDateChange = (el, date) => {
if (date && date.length === 10 && selectedDate !== date) {
setSelectedDate(date);
}
};
const updateViews = (newEvents) => {
setTotalEvents(newEvents.length);
setMilestonesCount(newEvents.filter(e => e.type === 'milestone').length);
setMeetingsCount(newEvents.filter(e => e.type === 'meeting').length);
setCalendarEvents(
newEvents.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
}))
);
setTimelineData(
[...newEvents].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}))
);
};
const addEvent = () => {
if (newEventTitle && selectedDate) {
const [year, month, day] = selectedDate.split('-').map(Number);
const newEvents = [...events, {
date: new Date(year, month - 1, day),
title: newEventTitle,
type: newEventType,
description: newEventDescription || 'No description'
}];
setEvents(newEvents);
setNewEventTitle('');
setNewEventType('event');
setNewEventDescription('');
updateViews(newEvents);
}
};
return (
<div style={{ padding: '20px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
<h2 style={{ margin: 0 }}>Event Manager</h2>
<div style={{ display: 'flex', gap: '30px' }}>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '24px', fontWeight: 700, color: '#3498db' }}>{totalEvents}</div>
<div style={{ fontSize: '12px', color: '#64748b' }}>Total Events</div>
</div>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '24px', fontWeight: 700, color: '#e74c3c' }}>{milestonesCount}</div>
<div style={{ fontSize: '12px', color: '#64748b' }}>Milestones</div>
</div>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '24px', fontWeight: 700, color: '#2ecc71' }}>{meetingsCount}</div>
<div style={{ fontSize: '12px', color: '#64748b' }}>Meetings</div>
</div>
</div>
</div>
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
<h3 style={{ margin: '0 0 16px 0', fontSize: '18px' }}>Calendar View</h3>
<div style={{ background: 'white', padding: '20px', borderRadius: '10px', border: '1px solid #e2e8f0' }}>
<Calendar events={calendarEvents} onchange={handleDateChange} type="inline" footer={false} />
</div>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
<h3 style={{ margin: '0 0 16px 0', fontSize: '18px' }}>Add New Event</h3>
<div style={{ background: 'white', padding: '20px', borderRadius: '10px', border: '1px solid #e2e8f0' }}>
<div style={{ marginBottom: '12px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>Selected Date:</label>
<input type="date" value={selectedDate} onChange={(e) => setSelectedDate(e.target.value)} style={{ width: '100%', padding: '8px', border: '1px solid #cbd5e1', borderRadius: '6px' }} />
</div>
<div style={{ marginBottom: '12px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>Event Title:</label>
<input type="text" value={newEventTitle} onChange={(e) => setNewEventTitle(e.target.value)} placeholder="Enter event name..." style={{ width: '100%', padding: '8px', border: '1px solid #cbd5e1', borderRadius: '6px' }} />
</div>
<div style={{ marginBottom: '12px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>Event Type:</label>
<select value={newEventType} onChange={(e) => setNewEventType(e.target.value)} style={{ width: '100%', padding: '8px', border: '1px solid #cbd5e1', borderRadius: '6px' }}>
<option value="event">Event</option>
<option value="meeting">Meeting</option>
<option value="review">Review</option>
<option value="milestone">Milestone</option>
<option value="testing">Testing</option>
</select>
</div>
<div style={{ marginBottom: '12px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>Description:</label>
<textarea value={newEventDescription} onChange={(e) => setNewEventDescription(e.target.value)} placeholder="Enter description..." style={{ width: '100%', padding: '8px', border: '1px solid #cbd5e1', borderRadius: '6px', resize: 'none', height: '80px' }}></textarea>
</div>
<button onClick={addEvent} className="button primary" style={{ width: '100%' }}>Add Event</button>
</div>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
<h3 style={{ margin: '0 0 16px 0', fontSize: '18px' }}>Timeline View</h3>
<div style={{ background: 'white', padding: '20px', borderRadius: '10px', border: '1px solid #e2e8f0', maxHeight: '450px', overflowY: 'auto' }}>
<Timeline data={timelineData} align="left" order="desc" />
</div>
</div>
</div>
</div>
);
}
<template>
<div style="padding: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2 style="margin: 0;">Event Manager</h2>
<div style="display: flex; gap: 30px;">
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #3498db;">{{ totalEvents }}</div>
<div style="font-size: 12px; color: #64748b;">Total Events</div>
</div>
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #e74c3c;">{{ milestonesCount }}</div>
<div style="font-size: 12px; color: #64748b;">Milestones</div>
</div>
<div style="text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #2ecc71;">{{ meetingsCount }}</div>
<div style="font-size: 12px; color: #64748b;">Meetings</div>
</div>
</div>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Calendar View</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0;">
<Calendar :events="calendarEvents" @change="handleDateChange" type="inline" :footer="false" />
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Add New Event</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0;">
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Selected Date:</label>
<input type="date" v-model="selectedDate" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;" />
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Event Title:</label>
<input type="text" v-model="newEventTitle" placeholder="Enter event name..." style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;" />
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Event Type:</label>
<select v-model="newEventType" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px;">
<option value="event">Event</option>
<option value="meeting">Meeting</option>
<option value="review">Review</option>
<option value="milestone">Milestone</option>
<option value="testing">Testing</option>
</select>
</div>
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500;">Description:</label>
<textarea v-model="newEventDescription" placeholder="Enter description..." style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px; resize: none; height: 80px;"></textarea>
</div>
<button @click="addEvent" class="button primary" style="width: 100%;">Add Event</button>
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 16px 0; font-size: 18px;">Timeline View</h3>
<div style="background: white; padding: 20px; border-radius: 10px; border: 1px solid #e2e8f0; max-height: 450px; overflow-y: auto;">
<Timeline :data="timelineData" align="left" order="desc" />
</div>
</div>
</div>
</div>
</template>
<script>
import { Calendar } from '@lemonadejs/calendar/vue';
import { Timeline } from '@lemonadejs/timeline/vue';
import '@lemonadejs/calendar/dist/style.min.css';
import '@lemonadejs/timeline/dist/style.min.css';
export default {
name: 'EventManager',
components: {
Calendar,
Timeline
},
data() {
const initialEvents = [
{ date: new Date(2025, 0, 15), title: 'Project Kickoff', type: 'meeting', description: 'Initial planning and team assembly' },
{ date: new Date(2025, 1, 1), title: 'Design Review', type: 'review', description: 'UI/UX design presentation' },
{ date: new Date(2025, 1, 15), title: 'Sprint 1 Complete', type: 'milestone', description: 'Core features implemented' },
{ date: new Date(2025, 2, 1), title: 'QA Testing', type: 'testing', description: 'Comprehensive testing phase' },
{ date: new Date(2025, 2, 15), title: 'Launch Date', type: 'milestone', description: 'Product goes live!' }
];
return {
events: initialEvents,
selectedDate: new Date().toISOString().split('T')[0],
newEventTitle: '',
newEventType: 'event',
newEventDescription: '',
totalEvents: initialEvents.length,
milestonesCount: initialEvents.filter(e => e.type === 'milestone').length,
meetingsCount: initialEvents.filter(e => e.type === 'meeting').length,
calendarEvents: initialEvents.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
})),
timelineData: [...initialEvents].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}))
};
},
methods: {
handleDateChange(el, date) {
if (date && date.length === 10 && this.selectedDate !== date) {
this.selectedDate = date;
}
},
updateViews() {
this.totalEvents = this.events.length;
this.milestonesCount = this.events.filter(e => e.type === 'milestone').length;
this.meetingsCount = this.events.filter(e => e.type === 'meeting').length;
this.calendarEvents = this.events.map(e => ({
date: e.date.toISOString().split('T')[0],
title: e.title,
color: e.type === 'milestone' ? '#e74c3c' : e.type === 'meeting' ? '#3498db' : e.type === 'review' ? '#f39c12' : '#2ecc71'
}));
this.timelineData = [...this.events].sort((a, b) => a.date - b.date).map(e => ({
date: e.date,
title: e.title,
subtitle: e.type.charAt(0).toUpperCase() + e.type.slice(1),
description: e.description
}));
},
addEvent() {
if (this.newEventTitle && this.selectedDate) {
const [year, month, day] = this.selectedDate.split('-').map(Number);
this.events = [...this.events, {
date: new Date(year, month - 1, day),
title: this.newEventTitle,
type: this.newEventType,
description: this.newEventDescription || 'No description'
}];
this.newEventTitle = '';
this.newEventType = 'event';
this.newEventDescription = '';
this.updateViews();
}
}
}
}
</script>