feat: visible date range
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -53,6 +53,7 @@ import { AGENDA_VIEW_SPANS, spanToDays, type AgendaViewSpan } from '@/types/span
|
||||
import type { EntityObject } from '@ChronoManager/models/entity';
|
||||
import type { CollectionObject } from '@ChronoManager/models/collection';
|
||||
import { EventObject } from '@ChronoManager/models/event';
|
||||
import { daysVisibleRange, shiftLocalDays } from '@/utils/dateRanges';
|
||||
|
||||
type CalendarEntity = EntityObject;
|
||||
type CalendarCollection = CollectionObject;
|
||||
@@ -60,25 +61,18 @@ type CalendarCollection = CollectionObject;
|
||||
const props = defineProps<{
|
||||
events: CalendarEntity[];
|
||||
calendars: CalendarCollection[];
|
||||
currentDate?: Date;
|
||||
currentDate: Date;
|
||||
initialSpan?: AgendaViewSpan;
|
||||
}>();
|
||||
|
||||
const selectedSpan = ref<AgendaViewSpan>(props.initialSpan ?? '1w');
|
||||
const localDate = ref(new Date(props.currentDate ?? new Date()));
|
||||
|
||||
const emit = defineEmits<{
|
||||
'event-click': [event: CalendarEntity];
|
||||
'update:span': [span: AgendaViewSpan];
|
||||
'update:current-date': [date: Date];
|
||||
}>();
|
||||
|
||||
// Watch for external date changes (e.g., from mini calendar)
|
||||
watch(() => props.currentDate, (newDate) => {
|
||||
if (newDate) {
|
||||
localDate.value = new Date(newDate);
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.initialSpan, (newSpan) => {
|
||||
if (newSpan && newSpan !== selectedSpan.value) {
|
||||
selectedSpan.value = newSpan;
|
||||
@@ -95,34 +89,29 @@ function getSpanDays(): number {
|
||||
}
|
||||
|
||||
function previousPeriod() {
|
||||
const date = new Date(localDate.value);
|
||||
date.setDate(date.getDate() - getSpanDays());
|
||||
localDate.value = date;
|
||||
emit('update:current-date', shiftLocalDays(props.currentDate, -getSpanDays()));
|
||||
}
|
||||
|
||||
function nextPeriod() {
|
||||
const date = new Date(localDate.value);
|
||||
date.setDate(date.getDate() + getSpanDays());
|
||||
localDate.value = date;
|
||||
emit('update:current-date', shiftLocalDays(props.currentDate, getSpanDays()));
|
||||
}
|
||||
|
||||
function goToToday() {
|
||||
localDate.value = new Date();
|
||||
emit('update:current-date', new Date());
|
||||
}
|
||||
|
||||
const dateRange = computed(() => {
|
||||
const start = new Date(localDate.value);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + getSpanDays() - 1);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
const range = daysVisibleRange(props.currentDate, getSpanDays());
|
||||
return {
|
||||
start: new Date(range.startMs),
|
||||
end: new Date(range.endMs),
|
||||
};
|
||||
});
|
||||
|
||||
const dateRangeLabel = computed(() => {
|
||||
const { start, end } = dateRange.value;
|
||||
const { start, end: exclusiveEnd } = dateRange.value;
|
||||
const end = new Date(exclusiveEnd);
|
||||
end.setDate(end.getDate() - 1);
|
||||
const formatOptions: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' };
|
||||
|
||||
if (getSpanDays() === 1) {
|
||||
@@ -144,7 +133,7 @@ const groupedEvents = computed(() => {
|
||||
const startsOn = getEventProperties(e).startsOn;
|
||||
if (typeof startsOn !== 'string') return false;
|
||||
const eventStart = new Date(startsOn);
|
||||
return eventStart >= start && eventStart <= end;
|
||||
return eventStart >= start && eventStart < end;
|
||||
});
|
||||
|
||||
const sorted = filtered.sort((a, b) =>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
@date-click="$emit('date-click', $event)"
|
||||
@event-hover="handleEventHover"
|
||||
@event-hover-end="hidePopup"
|
||||
@update:current-date="$emit('update:current-date', $event)"
|
||||
/>
|
||||
<DaysView
|
||||
v-else-if="view === 'days'"
|
||||
@@ -27,6 +28,7 @@
|
||||
@event-hover="handleEventHover"
|
||||
@event-hover-end="hidePopup"
|
||||
@update:span="$emit('update:days-span', $event)"
|
||||
@update:current-date="$emit('update:current-date', $event)"
|
||||
/>
|
||||
<AgendaView
|
||||
v-else-if="view === 'agenda'"
|
||||
@@ -36,21 +38,24 @@
|
||||
:initial-span="initialAgendaViewSpan"
|
||||
@event-click="$emit('event-click', $event)"
|
||||
@update:span="$emit('update:agenda-span', $event)"
|
||||
@update:current-date="$emit('update:current-date', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import MonthView from './MonthView.vue';
|
||||
import DaysView from './DaysView.vue';
|
||||
import AgendaView from './AgendaView.vue';
|
||||
import EventViewerPopup from './EventViewerPopup.vue';
|
||||
import type { AgendaViewSpan, DaysViewSpan } from '@/types/spans';
|
||||
import { spanToDays, type AgendaViewSpan, type DaysViewSpan } from '@/types/spans';
|
||||
import type { EntityObject } from '@ChronoManager/models/entity';
|
||||
import type { CollectionObject } from '@ChronoManager/models/collection';
|
||||
import type { VisibleDateRange } from '@/types';
|
||||
import { daysVisibleRange, monthGridVisibleRange } from '@/utils/dateRanges';
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
view: 'days' | 'month' | 'agenda';
|
||||
currentDate: Date;
|
||||
events: EntityObject[];
|
||||
@@ -59,13 +64,31 @@ defineProps<{
|
||||
initialAgendaViewSpan?: AgendaViewSpan;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
const emit = defineEmits<{
|
||||
'event-click': [event: EntityObject];
|
||||
'date-click': [date: Date];
|
||||
'update:days-span': [span: DaysViewSpan];
|
||||
'update:agenda-span': [span: AgendaViewSpan];
|
||||
'update:current-date': [date: Date];
|
||||
'update:visible-range': [range: VisibleDateRange];
|
||||
}>();
|
||||
|
||||
const visibleRange = computed(() => {
|
||||
if (props.view === 'month') {
|
||||
return monthGridVisibleRange(props.currentDate);
|
||||
}
|
||||
|
||||
const span = props.view === 'agenda'
|
||||
? props.initialAgendaViewSpan ?? '1w'
|
||||
: props.initialDaysSpan ?? '7d';
|
||||
|
||||
return daysVisibleRange(props.currentDate, spanToDays(span));
|
||||
});
|
||||
|
||||
watch(visibleRange, range => {
|
||||
emit('update:visible-range', range);
|
||||
}, { immediate: true });
|
||||
|
||||
// Popup state
|
||||
const hoveredEvent = ref<EntityObject | null>(null);
|
||||
const popupPosition = ref({ x: 0, y: 0 });
|
||||
|
||||
@@ -107,6 +107,7 @@ import {
|
||||
getTimedEventsForDate,
|
||||
type MultiDaySegment,
|
||||
} from '@/utils/calendarHelpers';
|
||||
import { shiftLocalDays } from '@/utils/dateRanges';
|
||||
import { DAYS_VIEW_SPANS, spanToDays, type DaysViewSpan } from '@/types/spans';
|
||||
import type { EntityObject } from '@ChronoManager/models/entity';
|
||||
import type { CollectionObject } from '@ChronoManager/models/collection';
|
||||
@@ -126,7 +127,6 @@ const props = defineProps<{
|
||||
|
||||
const selectedSpan = ref<DaysViewSpan>(props.initialSpan ?? '7d');
|
||||
const daysCount = computed(() => spanToDays(selectedSpan.value));
|
||||
const localDate = ref(new Date(props.currentDate));
|
||||
|
||||
const emit = defineEmits<{
|
||||
'event-click': [event: CalendarEntity];
|
||||
@@ -134,13 +134,9 @@ const emit = defineEmits<{
|
||||
'event-hover-end': [];
|
||||
'date-click': [date: Date];
|
||||
'update:span': [span: DaysViewSpan];
|
||||
'update:current-date': [date: Date];
|
||||
}>();
|
||||
|
||||
// Watch for external date changes (e.g., from mini calendar)
|
||||
watch(() => props.currentDate, (newDate) => {
|
||||
localDate.value = new Date(newDate);
|
||||
});
|
||||
|
||||
watch(() => props.initialSpan, (newSpan) => {
|
||||
if (newSpan && newSpan !== selectedSpan.value) {
|
||||
selectedSpan.value = newSpan;
|
||||
@@ -153,19 +149,15 @@ function setSpan(span: DaysViewSpan) {
|
||||
}
|
||||
|
||||
function previousPeriod() {
|
||||
const date = new Date(localDate.value);
|
||||
date.setDate(date.getDate() - daysCount.value);
|
||||
localDate.value = date;
|
||||
emit('update:current-date', shiftLocalDays(props.currentDate, -daysCount.value));
|
||||
}
|
||||
|
||||
function nextPeriod() {
|
||||
const date = new Date(localDate.value);
|
||||
date.setDate(date.getDate() + daysCount.value);
|
||||
localDate.value = date;
|
||||
emit('update:current-date', shiftLocalDays(props.currentDate, daysCount.value));
|
||||
}
|
||||
|
||||
function goToToday() {
|
||||
localDate.value = new Date();
|
||||
emit('update:current-date', new Date());
|
||||
}
|
||||
|
||||
const dateRangeLabel = computed(() => {
|
||||
@@ -189,7 +181,7 @@ const dateRangeLabel = computed(() => {
|
||||
|
||||
const visibleDates = computed(() => {
|
||||
const dates: Date[] = [];
|
||||
const start = new Date(localDate.value);
|
||||
const start = new Date(props.currentDate);
|
||||
|
||||
for (let i = 0; i < daysCount.value; i++) {
|
||||
dates.push(new Date(start));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
||||
import {
|
||||
startOfDay,
|
||||
daysDiff,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getSingleDayEventsForDate,
|
||||
type MultiDaySegment,
|
||||
} from '@/utils/calendarHelpers';
|
||||
import { shiftLocalMonths } from '@/utils/dateRanges';
|
||||
|
||||
const EVENT_HEIGHT = 22; // Height of each event row in pixels
|
||||
const MAX_VISIBLE_EVENTS = 3; // Maximum event rows to show before "+N more"
|
||||
@@ -24,42 +25,31 @@ const props = defineProps<{
|
||||
calendars: any[];
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
const emit = defineEmits<{
|
||||
'event-click': [event: any];
|
||||
'date-click': [date: Date];
|
||||
'event-hover': [data: { event: MouseEvent; entity: any }];
|
||||
'event-hover-end': [];
|
||||
'update:current-date': [date: Date];
|
||||
}>();
|
||||
|
||||
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
// Local date for navigation
|
||||
const localDate = ref(new Date(props.currentDate));
|
||||
|
||||
// Watch for external date changes (e.g., from mini calendar)
|
||||
watch(() => props.currentDate, (newDate) => {
|
||||
localDate.value = new Date(newDate);
|
||||
});
|
||||
|
||||
// Navigation functions
|
||||
function previousMonth() {
|
||||
const date = new Date(localDate.value);
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
localDate.value = date;
|
||||
emit('update:current-date', shiftLocalMonths(props.currentDate, -1));
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
const date = new Date(localDate.value);
|
||||
date.setMonth(date.getMonth() + 1);
|
||||
localDate.value = date;
|
||||
emit('update:current-date', shiftLocalMonths(props.currentDate, 1));
|
||||
}
|
||||
|
||||
function goToToday() {
|
||||
localDate.value = new Date();
|
||||
emit('update:current-date', new Date());
|
||||
}
|
||||
|
||||
const monthLabel = computed(() => {
|
||||
return localDate.value.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
||||
return props.currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
||||
});
|
||||
|
||||
// Track container size for dynamic calculations
|
||||
@@ -83,8 +73,8 @@ onUnmounted(() => {
|
||||
|
||||
// Calculate how many weeks are needed for this month
|
||||
const weeksNeeded = computed(() => {
|
||||
const year = localDate.value.getFullYear();
|
||||
const month = localDate.value.getMonth();
|
||||
const year = props.currentDate.getFullYear();
|
||||
const month = props.currentDate.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
|
||||
@@ -97,8 +87,8 @@ const weeksNeeded = computed(() => {
|
||||
|
||||
// Build weeks with multiday event segments
|
||||
const weeks = computed<WeekData[]>(() => {
|
||||
const year = localDate.value.getFullYear();
|
||||
const month = localDate.value.getMonth();
|
||||
const year = props.currentDate.getFullYear();
|
||||
const month = props.currentDate.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const gridStart = new Date(firstDay);
|
||||
gridStart.setDate(gridStart.getDate() - gridStart.getDay());
|
||||
|
||||
Reference in New Issue
Block a user