-
Notifications
You must be signed in to change notification settings - Fork 6
Implement calendar changes #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fd22fda
c88eea9
6844e41
ffdf9be
d7aa88f
a61ea31
d8f2955
3528443
8c1ab83
11f4d59
15d83f7
18fe2a5
47265cc
de54197
a8c5022
0e80f96
fae1135
a63a5dd
c1bbaa6
66130b5
2c487d3
f62b128
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| <script lang="ts"> | ||
| import { chevronBackOutline, chevronForwardOutline } from 'ionicons/icons'; | ||
| import type { DayObject } from './calendarUtils'; | ||
| import { getWeekdayNames, isSelectedDay as checkIsSelectedDay, isToday as checkIsToday } from './calendarUtils'; | ||
| import type { SelectedDay } from './calendarUtils'; | ||
|
|
||
| export let weeks: DayObject[][] = []; | ||
| export let month: number; | ||
| export let year: number; | ||
| export let selectedDay: SelectedDay | null = null; | ||
| export let onPreviousMonth: () => void; | ||
| export let onNextMonth: () => void; | ||
| export let onSelectDay: (dayObj: DayObject) => void; | ||
|
|
||
| const weekdayNames = getWeekdayNames(); | ||
|
|
||
| $: isSelectedDay = (dayObj: DayObject) => { | ||
| return checkIsSelectedDay(dayObj, selectedDay, month, year); | ||
| }; | ||
|
|
||
| function isToday(dayObj: DayObject) { | ||
| return checkIsToday(dayObj, month, year); | ||
| } | ||
|
|
||
| function hasEvents(dayObj: DayObject) { | ||
| if (!dayObj || !dayObj.isCurrentMonth) return false; | ||
| return dayObj.hasEvents; | ||
| } | ||
| </script> | ||
|
|
||
| <div class="calendar"> | ||
| <div class="header"> | ||
| <ion-card href="" on:click={onPreviousMonth} aria-label="Previous month" class="navButton" aria-hidden="true"> | ||
| <ion-icon icon={chevronBackOutline} /> | ||
| </ion-card> | ||
| <div class="month-title"> | ||
| {new Date(year, month).toLocaleDateString(undefined, { | ||
| month: 'long', | ||
| year: 'numeric' | ||
| })} | ||
| </div> | ||
| <ion-card href="" on:click={onNextMonth} aria-label="Next month" class="navButton" aria-hidden="true"> | ||
| <ion-icon icon={chevronForwardOutline} /> | ||
| </ion-card> | ||
| </div> | ||
|
|
||
| <div class="grid"> | ||
| {#each weekdayNames as dayName} | ||
| <div class="day-name">{dayName}</div> | ||
| {/each} | ||
|
|
||
| {#each weeks as week} | ||
| {#each week as dayObj} | ||
| <!-- svelte-ignore a11y-no-static-element-interactions --> | ||
| <!-- svelte-ignore a11y-click-events-have-key-events --> | ||
| <div | ||
| class="day {dayObj.isCurrentMonth | ||
| ? 'selectable' | ||
| : 'unselectable'} {isSelectedDay(dayObj) ? 'selected' : ''} {isToday( | ||
| dayObj | ||
| ) | ||
| ? 'today' | ||
| : ''}" | ||
| on:click={() => onSelectDay(dayObj)} | ||
| > | ||
| <span>{dayObj.day}</span> | ||
| {#if hasEvents(dayObj)} | ||
| <div class="event-badge" /> | ||
| {/if} | ||
| </div> | ||
| {/each} | ||
| {/each} | ||
| </div> | ||
| </div> | ||
|
|
||
| <style> | ||
| .navButton { | ||
| background: transparent; | ||
| border: none; | ||
| color: inherit; | ||
| border-radius: 1px; | ||
| box-shadow: none; | ||
| margin: 0; | ||
| padding: 1rem; | ||
| } | ||
|
|
||
| .calendar { | ||
| width: 100%; | ||
| padding: 1rem 1rem 0.5rem 1rem; | ||
| font-family: sans-serif; | ||
| color: var(--ion-color-dark); | ||
| display: flex; | ||
| flex-direction: column; | ||
| } | ||
| .header { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| margin-bottom: 1rem; | ||
| flex-shrink: 0; | ||
| } | ||
| .header .month-title { | ||
| cursor: pointer; | ||
| padding: 0.5rem 1rem; | ||
| border-radius: 8px; | ||
| transition: background 0.15s ease; | ||
| font-weight: 500; | ||
| } | ||
| .header .month-title:hover { | ||
| background: var(--ion-color-light); | ||
| } | ||
|
|
||
| .header ion-icon { | ||
| font-size: 1.15rem; | ||
| color: var(--ion-color-dark); | ||
| display: block; | ||
| width: 1em; | ||
| height: 1em; | ||
| } | ||
| .grid { | ||
| display: grid; | ||
| grid-template-columns: repeat(7, 1fr); | ||
| grid-auto-rows: minmax(2.5rem, auto); | ||
| text-align: center; | ||
| gap: 0.5rem; | ||
| margin-bottom: 0.5rem; | ||
| } | ||
| .day-name { | ||
| font-weight: bold; | ||
| color: var(--ion-color-primary); | ||
| padding: 0.25rem; | ||
| } | ||
| .day { | ||
| padding: 0.5rem 0.25rem; | ||
| border-radius: 8px; | ||
| user-select: none; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| min-height: 0; | ||
| transition: all 0.15s ease; | ||
| gap: 0.15rem; | ||
| } | ||
| .day.selectable:hover { | ||
| background: var(--ion-color-light); | ||
| cursor: pointer; | ||
| transform: scale(1.05); | ||
| } | ||
| .day.today { | ||
| color: var(--ion-color-primary); | ||
| font-weight: 600; | ||
| } | ||
| .day.selected { | ||
| background: var(--ion-color-light); | ||
| color: var(--ion-color-dark); | ||
| font-weight: 600; | ||
| box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); | ||
| } | ||
| .day.selected.today { | ||
| color: var(--ion-color-primary); | ||
| } | ||
| .day.unselectable { | ||
| color: var(--ion-color-medium); | ||
| cursor: pointer; | ||
| } | ||
| .event-badge { | ||
| width: 4px; | ||
| height: 4px; | ||
| border-radius: 50%; | ||
| background: var(--ion-color-primary); | ||
| opacity: 0.8; | ||
| } | ||
| </style> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,44 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { writable, derived } from 'svelte/store'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { writable, derived } from 'svelte/store'; | |
| import { writable } from 'svelte/store'; |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused calendar state module. The calendarState.ts file exports calendar state management functionality but is not imported or used anywhere in the calendar page component. The calendar page implements its own local state management instead. Consider removing this file if it's not needed, or integrate it into the calendar implementation.
| 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<SelectedDay | null>(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(); |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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); | ||||||||||||||
|
Comment on lines
+20
to
+21
|
||||||||||||||
| return Array.from({ length: 7 }, (_, i) => { | |
| const date = new Date(2024, 0, i); | |
| // Use current year to avoid hardcoding and ensure future-proofing | |
| const currentYear = new Date().getFullYear(); | |
| return Array.from({ length: 7 }, (_, i) => { | |
| const date = new Date(currentYear, 0, i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Significant formatting change. Switching from tabs to spaces (with 4-space indentation) is a major change that will affect the entire codebase formatting. This should be applied consistently across all files, ideally in a separate commit/PR dedicated to formatting changes only. Mixing formatting changes with functional changes makes code review more difficult.