Adds date navigation to scheduled events

Implements date navigation for scheduled events, allowing users to view events for different days.

This change replaces the static "Today's Events" section with a dynamic date selector.
It introduces buttons for navigating to the previous and next days, as well as a "Today" button to return to the current date.
A date display shows the selected date, and a message indicates when there are no scheduled events for a given day.
This commit is contained in:
padreug 2025-10-23 15:05:58 +02:00
parent a27a8232f2
commit 0e42318036
3 changed files with 137 additions and 23 deletions

View file

@ -9,7 +9,7 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Megaphone, RefreshCw, AlertCircle } from 'lucide-vue-next'
import { Megaphone, RefreshCw, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { useFeed } from '../composables/useFeed'
import { useProfiles } from '../composables/useProfiles'
import { useReactions } from '../composables/useReactions'
@ -99,10 +99,66 @@ const { getDisplayName, fetchProfiles } = useProfiles()
const { getEventReactions, subscribeToReactions, toggleLike } = useReactions()
// Use scheduled events service
const { getTodaysEvents, getCompletion, toggleComplete, allCompletions } = useScheduledEvents()
const { getEventsForSpecificDate, getCompletion, toggleComplete, allCompletions } = useScheduledEvents()
// Get today's scheduled events (reactive)
const todaysScheduledEvents = computed(() => getTodaysEvents())
// Selected date for viewing events (defaults to today)
const selectedDate = ref(new Date().toISOString().split('T')[0])
// Get scheduled events for the selected date (reactive)
const scheduledEventsForDate = computed(() => getEventsForSpecificDate(selectedDate.value))
// Navigate to previous day
function goToPreviousDay() {
const date = new Date(selectedDate.value)
date.setDate(date.getDate() - 1)
selectedDate.value = date.toISOString().split('T')[0]
}
// Navigate to next day
function goToNextDay() {
const date = new Date(selectedDate.value)
date.setDate(date.getDate() + 1)
selectedDate.value = date.toISOString().split('T')[0]
}
// Go back to today
function goToToday() {
selectedDate.value = new Date().toISOString().split('T')[0]
}
// Check if selected date is today
const isToday = computed(() => {
const today = new Date().toISOString().split('T')[0]
return selectedDate.value === today
})
// Format date for display
const dateDisplayText = computed(() => {
const today = new Date().toISOString().split('T')[0]
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
const yesterdayStr = yesterday.toISOString().split('T')[0]
const tomorrow = new Date()
tomorrow.setDate(tomorrow.getDate() + 1)
const tomorrowStr = tomorrow.toISOString().split('T')[0]
if (selectedDate.value === today) {
return "Today's Events"
} else if (selectedDate.value === yesterdayStr) {
return "Yesterday's Events"
} else if (selectedDate.value === tomorrowStr) {
return "Tomorrow's Events"
} else {
// Format as "Events for Mon, Jan 15"
const date = new Date(selectedDate.value + 'T00:00:00')
const formatted = date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
})
return `Events for ${formatted}`
}
})
// Watch for new posts and fetch their profiles and reactions
watch(notes, async (newNotes) => {
@ -119,12 +175,12 @@ watch(notes, async (newNotes) => {
}, { immediate: true })
// Watch for scheduled events and fetch profiles for event authors and completers
watch(todaysScheduledEvents, async (events) => {
watch(scheduledEventsForDate, async (events) => {
if (events.length > 0) {
const pubkeys = new Set<string>()
// Add event authors
events.forEach(event => {
events.forEach((event: ScheduledEvent) => {
pubkeys.add(event.pubkey)
// Add completer pubkey if event is completed
@ -421,14 +477,50 @@ function cancelDelete() {
<!-- Posts List - Natural flow without internal scrolling -->
<div v-else>
<!-- Today's Scheduled Events Section -->
<div v-if="todaysScheduledEvents.length > 0" class="mb-6 md:mb-8">
<h3 class="text-sm font-semibold text-muted-foreground uppercase tracking-wide px-4 md:px-0 mb-3">
📅 Today's Events
</h3>
<div class="md:space-y-3">
<!-- Scheduled Events Section with Date Navigation -->
<div v-if="scheduledEventsForDate.length > 0 || !isToday" class="mb-6 md:mb-8">
<div class="flex items-center justify-between px-4 md:px-0 mb-3">
<!-- Left Arrow -->
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
@click="goToPreviousDay"
>
<ChevronLeft class="h-4 w-4" />
</Button>
<!-- Date Header with Today Button -->
<div class="flex items-center gap-2">
<h3 class="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
📅 {{ dateDisplayText }}
</h3>
<Button
v-if="!isToday"
variant="outline"
size="sm"
class="h-6 text-xs"
@click="goToToday"
>
Today
</Button>
</div>
<!-- Right Arrow -->
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
@click="goToNextDay"
>
<ChevronRight class="h-4 w-4" />
</Button>
</div>
<!-- Events List or Empty State -->
<div v-if="scheduledEventsForDate.length > 0" class="md:space-y-3">
<ScheduledEventCard
v-for="event in todaysScheduledEvents"
v-for="event in scheduledEventsForDate"
:key="`${event.pubkey}:${event.dTag}`"
:event="event"
:get-display-name="getDisplayName"
@ -437,11 +529,14 @@ function cancelDelete() {
@toggle-complete="onToggleComplete"
/>
</div>
<div v-else class="text-center py-8 text-muted-foreground text-sm px-4">
No events scheduled for this day
</div>
</div>
<!-- Posts Section -->
<div v-if="threadedPosts.length > 0" class="md:space-y-4 md:py-4">
<h3 v-if="todaysScheduledEvents.length > 0" class="text-sm font-semibold text-muted-foreground uppercase tracking-wide px-4 md:px-0 mb-3 mt-6">
<h3 v-if="scheduledEventsForDate.length > 0" class="text-sm font-semibold text-muted-foreground uppercase tracking-wide px-4 md:px-0 mb-3 mt-6">
💬 Posts
</h3>
<ThreadedPost

View file

@ -31,6 +31,15 @@ export function useScheduledEvents() {
return scheduledEventService.getEventsForDate(date)
}
/**
* Get events for a specific date (filtered by current user participation)
* @param date - ISO date string (YYYY-MM-DD). Defaults to today.
*/
const getEventsForSpecificDate = (date?: string): ScheduledEvent[] => {
if (!scheduledEventService) return []
return scheduledEventService.getEventsForSpecificDate(date, currentUserPubkey.value)
}
/**
* Get today's scheduled events (filtered by current user participation)
*/
@ -141,6 +150,7 @@ export function useScheduledEvents() {
// Methods
getScheduledEvents,
getEventsForDate,
getEventsForSpecificDate,
getTodaysEvents,
getCompletion,
isCompleted,

View file

@ -251,22 +251,24 @@ export class ScheduledEventService extends BaseService {
}
/**
* Get events for today, optionally filtered by user participation
* Get events for a specific date, optionally filtered by user participation
* @param date - ISO date string (YYYY-MM-DD). Defaults to today.
* @param userPubkey - Optional user pubkey to filter by participation
*/
getTodaysEvents(userPubkey?: string): ScheduledEvent[] {
const today = new Date().toISOString().split('T')[0]
getEventsForSpecificDate(date?: string, userPubkey?: string): ScheduledEvent[] {
const targetDate = date || new Date().toISOString().split('T')[0]
// Get one-time events for today (exclude recurring events to avoid duplicates)
const oneTimeEvents = this.getEventsForDate(today).filter(event => !event.recurrence)
// Get one-time events for the date (exclude recurring events to avoid duplicates)
const oneTimeEvents = this.getEventsForDate(targetDate).filter(event => !event.recurrence)
// Get all events and check for recurring events that occur today
// Get all events and check for recurring events that occur on this date
const allEvents = this.getScheduledEvents()
const recurringEventsToday = allEvents.filter(event =>
event.recurrence && this.doesRecurringEventOccurOnDate(event, today)
const recurringEventsOnDate = allEvents.filter(event =>
event.recurrence && this.doesRecurringEventOccurOnDate(event, targetDate)
)
// Combine one-time and recurring events
let events = [...oneTimeEvents, ...recurringEventsToday]
let events = [...oneTimeEvents, ...recurringEventsOnDate]
// Filter events based on participation (if user pubkey provided)
if (userPubkey) {
@ -288,6 +290,13 @@ export class ScheduledEventService extends BaseService {
return events
}
/**
* Get events for today, optionally filtered by user participation
*/
getTodaysEvents(userPubkey?: string): ScheduledEvent[] {
return this.getEventsForSpecificDate(undefined, userPubkey)
}
/**
* Get completion status for an event (optionally for a specific occurrence)
*/