diff --git a/.prettierrc b/.prettierrc index a77fddea..d8bcc7b4 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { - "useTabs": true, + "useTabs": false, + "tabWidth": 4, "singleQuote": true, "trailingComma": "none", "printWidth": 100, diff --git a/src/lib/components/calendar/CalendarGrid.svelte b/src/lib/components/calendar/CalendarGrid.svelte new file mode 100644 index 00000000..410539ee --- /dev/null +++ b/src/lib/components/calendar/CalendarGrid.svelte @@ -0,0 +1,174 @@ + + +
+
+ +
+ {new Date(year, month).toLocaleDateString(undefined, { + month: 'long', + year: 'numeric' + })} +
+ +
+ +
+ {#each weekdayNames as dayName} +
{dayName}
+ {/each} + + {#each weeks as week} + {#each week as dayObj} + + +
onSelectDay(dayObj)} + > + {dayObj.day} + {#if hasEvents(dayObj)} +
+ {/if} +
+ {/each} + {/each} +
+
+ + diff --git a/src/lib/components/calendar/calendarState.ts b/src/lib/components/calendar/calendarState.ts new file mode 100644 index 00000000..028ba1a4 --- /dev/null +++ b/src/lib/components/calendar/calendarState.ts @@ -0,0 +1,44 @@ +import { writable, derived } from 'svelte/store'; +import type { SelectedDay } from './calendarUtils'; + +function createCalendarState() { + const currentDate = new Date(); + const month = writable(currentDate.getMonth()); + const year = writable(currentDate.getFullYear()); + const selectedDay = writable(null); + const activeDate = writable(new Date()); + + return { + month, + year, + selectedDay, + activeDate, + + nextMonth: () => { + month.update(m => { + if (m === 11) { + year.update(y => y + 1); + return 0; + } + return m + 1; + }); + }, + + previousMonth: () => { + month.update(m => { + if (m === 0) { + year.update(y => y - 1); + return 11; + } + return m - 1; + }); + }, + + setMonth: (newMonth: number) => month.set(newMonth), + setYear: (newYear: number) => year.set(newYear), + setSelectedDay: (day: SelectedDay | null) => selectedDay.set(day), + setActiveDate: (date: Date) => activeDate.set(date) + }; +} + +export const calendarState = createCalendarState(); diff --git a/src/lib/components/calendar/calendarUtils.ts b/src/lib/components/calendar/calendarUtils.ts new file mode 100644 index 00000000..00af7e28 --- /dev/null +++ b/src/lib/components/calendar/calendarUtils.ts @@ -0,0 +1,139 @@ +import type { Event } from './event/Event'; +import { isCurrentDay } from './CalendarFunctions'; + +export interface DayObject { + day: number; + isCurrentMonth: boolean; + hasEvents: boolean; +} + +export interface SelectedDay { + day: number; + month: number; + year: number; +} + +/** + * Get localized weekday names (first letter, capitalized) + */ +export function getWeekdayNames(): string[] { + return Array.from({ length: 7 }, (_, i) => { + const date = new Date(2024, 0, i); + const fullName = date.toLocaleDateString(undefined, { weekday: 'long' }); + return fullName.charAt(0).toUpperCase(); + }); +} + +/** + * Build calendar weeks for a given month and year + */ +export function buildCalendarWeeks( + month: number, + year: number, + events: Event[] +): DayObject[][] { + const firstDay = new Date(year, month, 1).getDay(); + const daysInMonth = new Date(year, month + 1, 0).getDate(); + const prevMonthDays = new Date(year, month, 0).getDate(); + + const tempWeeks: DayObject[][] = []; + let week: DayObject[] = []; + + // Fill previous month's days + for (let i = firstDay - 1; i >= 0; i--) { + week.push({ day: prevMonthDays - i, isCurrentMonth: false, hasEvents: false }); + } + + // Fill current month's days + for (let d = 1; d <= daysInMonth; d++) { + const dateToCheck = new Date(year, month, d); + const hasEventOnDay = events.some((event) => isCurrentDay(event, dateToCheck)); + week.push({ day: d, isCurrentMonth: true, hasEvents: hasEventOnDay }); + if (week.length === 7) { + tempWeeks.push(week); + week = []; + } + } + + // Fill next month's days + if (week.length > 0) { + let nextDay = 1; + while (week.length < 7) { + week.push({ day: nextDay++, isCurrentMonth: false, hasEvents: false }); + } + tempWeeks.push(week); + } + + return tempWeeks; +} + +/** + * Compute days with events for the current month (including recurring events) + */ +export function computeDaysWithEvents( + month: number, + year: number, + events: Event[] +): Set { + const days = new Set(); + const daysInMonth = new Date(year, month + 1, 0).getDate(); + + for (let day = 1; day <= daysInMonth; day++) { + const dateToCheck = new Date(year, month, day); + const hasEventOnDay = events.some((event) => isCurrentDay(event, dateToCheck)); + if (hasEventOnDay) { + days.add(day.toString()); + } + } + return days; +} + +/** + * Check if a day object is the selected day + */ +export function isSelectedDay( + dayObj: DayObject | null, + selectedDay: SelectedDay | null, + month: number, + year: number +): boolean { + if (!selectedDay || !dayObj || !dayObj.isCurrentMonth) return false; + return ( + selectedDay.day === dayObj.day && + selectedDay.month === month && + selectedDay.year === year + ); +} + +/** + * Check if a day object is today + */ +export function isToday(dayObj: DayObject | null, month: number, year: number): boolean { + if (!dayObj || !dayObj.isCurrentMonth) return false; + const today = new Date(); + return ( + today.getDate() === dayObj.day && + today.getMonth() === month && + today.getFullYear() === year + ); +} + +/** + * Navigate to the next month + */ +export function getNextMonth(month: number, year: number): { month: number; year: number } { + if (month === 11) { + return { month: 0, year: year + 1 }; + } + return { month: month + 1, year }; +} + +/** + * Navigate to the previous month + */ +export function getPreviousMonth(month: number, year: number): { month: number; year: number } { + if (month === 0) { + return { month: 11, year: year - 1 }; + } + return { month: month - 1, year }; +} diff --git a/src/lib/components/calendar/event/EventCard.svelte b/src/lib/components/calendar/event/EventCard.svelte index 7f17c49f..de27f094 100644 --- a/src/lib/components/calendar/event/EventCard.svelte +++ b/src/lib/components/calendar/event/EventCard.svelte @@ -49,12 +49,14 @@
-
+
{eventItem.title} - {eventItem.description} + {#if eventItem.description} + {eventItem.description} + {/if}
@@ -65,10 +67,9 @@
{#if eventItem.slot.end && new Date(eventItem.slot.end).getTime() != new Date(eventItem.slot.start).getTime()} {#if new Date(eventItem.slot.end).getDay() - new Date(eventItem.slot.start).getDay() != 0} -  -* +  -* {:else} - -  - {new Date(eventItem.slot.end).toLocaleTimeString('el-GR', { hour: '2-digit', minute: '2-digit', hour12: false })} +  - {new Date(eventItem.slot.end).toLocaleTimeString('el-GR', { hour: '2-digit', minute: '2-digit', hour12: false })} {/if} {/if}
@@ -80,7 +81,7 @@ diff --git a/src/lib/components/calendar/event/EventModal.svelte b/src/lib/components/calendar/event/EventModal.svelte new file mode 100644 index 00000000..6ea7874a --- /dev/null +++ b/src/lib/components/calendar/event/EventModal.svelte @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + {event.title || $t('event.title')} + + + + + + + + + + + diff --git a/src/lib/translations/translations.js b/src/lib/translations/translations.js index 0fa13eba..fde20032 100644 --- a/src/lib/translations/translations.js +++ b/src/lib/translations/translations.js @@ -114,6 +114,7 @@ export default { "links.erasmus": "Γραφείο Erasmus", "schedule.title": "Ημερολόγιο", "schedule.no_event": "Δεν υπάρχουν προγραμματισμένα συμβάντα αυτήν τη μέρα.", + "schedule.no_events_day": "Δεν υπάρχουν συμβάντα για αυτήν την ημέρα", "event.title": "Συμβάν", "event.start": "Έναρξη", "event.end": "Λήξη", @@ -345,6 +346,7 @@ export default { 'links.erasmus': 'Erasmus Office', 'schedule.title': 'Calendar', 'schedule.no_event': 'There are no scheduled events for this day.', + 'schedule.no_events_day': 'No events for this day', 'event.title': 'Event', 'event.start': 'Starts', 'event.end': 'Ends', diff --git a/src/routes/pages/calendar/+page.svelte b/src/routes/pages/calendar/+page.svelte index 4e4efe28..e61c2b9e 100644 --- a/src/routes/pages/calendar/+page.svelte +++ b/src/routes/pages/calendar/+page.svelte @@ -1,343 +1,334 @@ + + - - - {$t('schedule.title')} - - {modalOpen=true; selectedEvent=null; recreatePrototype();}} aria-hidden> - - - - - - - - -
- {#if eventList.length > 0} -
- -
- {#each eventList as eventItem} - - {/each} -
-
-
- {:else} -
- - {$t('schedule.no_event')} -
- {/if} +
+ -
+ +
+

+ {activeDate.toLocaleDateString(undefined, { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + })} +

+ {#if eventList.length > 0} +
+ {#each eventList as eventItem} + + {/each} +
+ {:else} +

+ {$t('schedule.no_events_day')} +

+ {/if} +
- {modalOpen = event.detail.breakpoint!=0; if(!modalOpen) selectedEvent=null;}} - on:ionModalDidDismiss={()=>{modalOpen=false; selectedEvent=null; recreatePrototype();}} - on:ionModalWillPresent={setupModal} - > - - - - - - - - - - {selectedEvent?.title? selectedEvent.title : $t('event.title')} - - {modalOpen=false; selectedEvent=null; recreatePrototype();}} aria-hidden> - - - - - - + + + + + + - { + + {#if selectedEvent} + + {/if} + + + { addInactiveDateToEvent(selectedEvent); - } - }, - { - text: $t('event.deleteAll'), - role: 'destructive', - handler: () => { - removeEvent(selectedEvent); - } - }, - { - text: $t('event.cancel'), - role: 'cancel', - handler: () => { + } + }, + { + text: $t('event.deleteAll'), + role: 'destructive', + handler: () => { + if (selectedEvent) { + handleEventDelete(selectedEvent); + deleteModalOpen = false; + } + } + }, + { + text: $t('event.cancel'), + role: 'cancel', + handler: () => { deleteModalOpen = false; selectedEvent = null; - } } - ]} - mode="ios"> - - - + } + ]} + mode="ios"> + +
\ No newline at end of file + ion-fab-button { + --background: var(--ion-color-primary); + --color: white; + --border-radius: 16px; + --box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + width: 56px; + height: 56px; + } + ion-fab-button:hover { + --box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16); + transform: scale(1.05); + } + ion-fab-button::part(native) { + border-radius: 16px; + } + ion-fab-button ion-icon { + font-size: 28px; + } + diff --git a/src/routes/persistedStoreDeclarations.ts b/src/routes/persistedStoreDeclarations.ts index ed1d5dc3..a5614383 100644 --- a/src/routes/persistedStoreDeclarations.ts +++ b/src/routes/persistedStoreDeclarations.ts @@ -25,5 +25,5 @@ const persistedStores: CapacitorPersistedStore[] = [ export async function loadPersistedStores() { console.log('Loading persisted stores'); - persistedStores.forEach(async (store) => { await store.loadFromStorage();}); + await Promise.all(persistedStores.map(store => store.loadFromStorage())); }