diff --git a/apps.config.js b/apps.config.js index 6437403a..c1d9b425 100644 --- a/apps.config.js +++ b/apps.config.js @@ -6,6 +6,7 @@ import { displayChrome } from './components/apps/chrome'; import { displayTrash } from './components/apps/trash'; import { displayGedit } from './components/apps/gedit'; import { displayAboutVivek } from './components/apps/vivek'; +import { displayCalendar } from './components/apps/calendar'; import { displayTerminalCalc } from './components/apps/calc'; const apps = [ @@ -35,6 +36,15 @@ const apps = [ favourite: true, desktop_shortcut: true, screen: displayAboutVivek, + }, + { + id: "calendar", + title: "Void Calendar", + icon: './themes/Yaru/apps/calendar.png', + disabled: false, + favourite: true, + desktop_shortcut: true, + screen: displayCalendar, }, { id: "vscode", @@ -113,4 +123,4 @@ const apps = [ }, ] -export default apps; \ No newline at end of file +export default apps; diff --git a/components/apps/calendar.js b/components/apps/calendar.js new file mode 100644 index 00000000..2cfa1eec --- /dev/null +++ b/components/apps/calendar.js @@ -0,0 +1,1039 @@ +import React, { useState, useEffect } from 'react'; + +const Calendar = () => { + const [currentDate, setCurrentDate] = useState(new Date()); + const [selectedDate, setSelectedDate] = useState(new Date()); + const [events, setEvents] = useState({}); + const [showEventModal, setShowEventModal] = useState(false); + const [eventText, setEventText] = useState(''); + const [eventTime, setEventTime] = useState('09:00'); + const [eventCategory, setEventCategory] = useState('personal'); + const [filterCategory, setFilterCategory] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); + + // US Federal Holidays 2024-2026 + const holidays = { + '2025-01-01': 'New Year\'s Day', + '2025-01-20': 'Martin Luther King Jr. Day', + '2025-02-17': 'Presidents\' Day', + '2025-05-26': 'Memorial Day', + '2025-06-19': 'Juneteenth', + '2025-07-04': 'Independence Day', + '2025-09-01': 'Labor Day', + '2025-10-13': 'Columbus Day', + '2025-11-11': 'Veterans Day', + '2025-11-27': 'Thanksgiving', + '2025-12-25': 'Christmas Day', + '2024-01-01': 'New Year\'s Day', + '2024-01-15': 'Martin Luther King Jr. Day', + '2024-02-19': 'Presidents\' Day', + '2024-05-27': 'Memorial Day', + '2024-06-19': 'Juneteenth', + '2024-07-04': 'Independence Day', + '2024-09-02': 'Labor Day', + '2024-10-14': 'Columbus Day', + '2024-11-11': 'Veterans Day', + '2024-11-28': 'Thanksgiving', + '2024-12-25': 'Christmas Day', + '2026-01-01': 'New Year\'s Day', + '2026-01-19': 'Martin Luther King Jr. Day', + '2026-02-16': 'Presidents\' Day', + '2026-05-25': 'Memorial Day', + '2026-06-19': 'Juneteenth', + '2026-07-03': 'Independence Day (Observed)', + '2026-09-07': 'Labor Day', + '2026-10-12': 'Columbus Day', + '2026-11-11': 'Veterans Day', + '2026-11-26': 'Thanksgiving', + '2026-12-25': 'Christmas Day' + }; + + useEffect(() => { + try { + const savedEvents = localStorage.getItem('ubuntu_calendar_events'); + if (savedEvents) { + setEvents(JSON.parse(savedEvents)); + } + } catch (e) { + console.error('Error loading events:', e); + } + }, []); + + useEffect(() => { + try { + localStorage.setItem('ubuntu_calendar_events', JSON.stringify(events)); + } catch (e) { + console.error('Error saving events:', e); + } + }, [events]); + + const getDaysInMonth = (date) => { + return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + }; + + const getFirstDayOfMonth = (date) => { + return new Date(date.getFullYear(), date.getMonth(), 1).getDay(); + }; + + const formatDateKey = (date) => { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; + }; + + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + + const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + + const previousMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1)); + }; + + const nextMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1)); + }; + + const goToToday = () => { + const today = new Date(); + setCurrentDate(today); + setSelectedDate(today); + }; + + const jumpToDate = () => { + const input = prompt('Enter date (YYYY-MM-DD):'); + if (input) { + const date = new Date(input); + if (!isNaN(date.getTime())) { + setCurrentDate(date); + setSelectedDate(date); + } + } + }; + + const handleDateClick = (day) => { + const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + setSelectedDate(newDate); + }; + + const handleAddEvent = () => { + if (eventText.trim()) { + const dateKey = formatDateKey(selectedDate); + const newEvent = { + text: eventText, + time: eventTime, + category: eventCategory, + id: Date.now() + }; + + setEvents(prev => ({ + ...prev, + [dateKey]: [...(prev[dateKey] || []), newEvent] + })); + + setEventText(''); + setEventTime('09:00'); + setEventCategory('personal'); + setShowEventModal(false); + } + }; + + const handleDeleteEvent = (dateKey, eventId) => { + setEvents(prev => ({ + ...prev, + [dateKey]: prev[dateKey].filter(e => e.id !== eventId) + })); + }; + + const getFilteredEvents = (dateKey) => { + const dayEvents = events[dateKey] || []; + if (filterCategory === 'all') return dayEvents; + return dayEvents.filter(e => e.category === filterCategory); + }; + + const searchEvents = () => { + if (!searchQuery.trim()) return []; + const results = []; + Object.keys(events).forEach(dateKey => { + events[dateKey].forEach(event => { + if (event.text.toLowerCase().includes(searchQuery.toLowerCase())) { + results.push({ ...event, date: dateKey }); + } + }); + }); + return results; + }; + + const renderCalendarDays = () => { + const daysInMonth = getDaysInMonth(currentDate); + const firstDay = getFirstDayOfMonth(currentDate); + const days = []; + const today = new Date(); + const isCurrentMonth = currentDate.getMonth() === today.getMonth() && + currentDate.getFullYear() === today.getFullYear(); + + for (let i = 0; i < firstDay; i++) { + days.push(
); + } + + for (let day = 1; day <= daysInMonth; day++) { + const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), day); + const dateKey = formatDateKey(date); + const isToday = isCurrentMonth && day === today.getDate(); + const isSelected = selectedDate.getDate() === day && + selectedDate.getMonth() === currentDate.getMonth() && + selectedDate.getFullYear() === currentDate.getFullYear(); + const dayEvents = getFilteredEvents(dateKey); + const hasEvents = dayEvents.length > 0; + const holiday = holidays[dateKey]; + const isWeekend = date.getDay() === 0 || date.getDay() === 6; + + days.push( +
handleDateClick(day)} + > + {day} + {hasEvents &&
+ {dayEvents.slice(0, 3).map((_, i) => )} +
} +
+ ); + } + + return days; + }; + + const renderEvents = () => { + const dateKey = formatDateKey(selectedDate); + const dayEvents = getFilteredEvents(dateKey); + const holiday = holidays[dateKey]; + + return ( +
+
+
+

+ {selectedDate.toLocaleDateString('en-US', { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric' + })} +

+ {holiday && ( +
+ 🎉 + {holiday} +
+ )} +
+ +
+ +
+ {dayEvents.length === 0 && !holiday ? ( +
+
📅
+

No events scheduled

+ Click "Add Event" to create one +
+ ) : ( + <> + {holiday && dayEvents.length === 0 && ( +
+ 🎊 +

It's {holiday}!

+
+ )} + {dayEvents.sort((a, b) => a.time.localeCompare(b.time)).map(event => ( +
+
+ {event.time} + {event.category} +
+
{event.text}
+ +
+ ))} + + )} +
+
+ ); + }; + + const renderSearchResults = () => { + const results = searchEvents(); + return ( +
+

Search Results ({results.length})

+ {results.length === 0 ? ( +
+
🔍
+

No events found

+
+ ) : ( + results.map(event => ( +
+
{new Date(event.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
+
+ {event.time} + {event.text} + {event.category} +
+
+ )) + )} +
+ ); + }; + + return ( +
+ + +
+

{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}

+
+ + + + +
+
+ +
+
+ + +
+
+ setSearchQuery(e.target.value)} + /> + +
+
+ +
+
+
+ {dayNames.map(day => ( +
{day}
+ ))} +
+
+ {renderCalendarDays()} +
+
+ + {searchQuery.trim() ? renderSearchResults() : renderEvents()} +
+ + {showEventModal && ( +
setShowEventModal(false)}> +
e.stopPropagation()}> +

Add Event

+
+ +