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:
parent
83a87b2da6
commit
937a0e9075
3 changed files with 137 additions and 23 deletions
|
|
@ -9,7 +9,7 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog'
|
} 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 { useFeed } from '../composables/useFeed'
|
||||||
import { useProfiles } from '../composables/useProfiles'
|
import { useProfiles } from '../composables/useProfiles'
|
||||||
import { useReactions } from '../composables/useReactions'
|
import { useReactions } from '../composables/useReactions'
|
||||||
|
|
@ -99,10 +99,66 @@ const { getDisplayName, fetchProfiles } = useProfiles()
|
||||||
const { getEventReactions, subscribeToReactions, toggleLike } = useReactions()
|
const { getEventReactions, subscribeToReactions, toggleLike } = useReactions()
|
||||||
|
|
||||||
// Use scheduled events service
|
// Use scheduled events service
|
||||||
const { getTodaysEvents, getCompletion, toggleComplete, allCompletions } = useScheduledEvents()
|
const { getEventsForSpecificDate, getCompletion, toggleComplete, allCompletions } = useScheduledEvents()
|
||||||
|
|
||||||
// Get today's scheduled events (reactive)
|
// Selected date for viewing events (defaults to today)
|
||||||
const todaysScheduledEvents = computed(() => getTodaysEvents())
|
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 for new posts and fetch their profiles and reactions
|
||||||
watch(notes, async (newNotes) => {
|
watch(notes, async (newNotes) => {
|
||||||
|
|
@ -119,12 +175,12 @@ watch(notes, async (newNotes) => {
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// Watch for scheduled events and fetch profiles for event authors and completers
|
// Watch for scheduled events and fetch profiles for event authors and completers
|
||||||
watch(todaysScheduledEvents, async (events) => {
|
watch(scheduledEventsForDate, async (events) => {
|
||||||
if (events.length > 0) {
|
if (events.length > 0) {
|
||||||
const pubkeys = new Set<string>()
|
const pubkeys = new Set<string>()
|
||||||
|
|
||||||
// Add event authors
|
// Add event authors
|
||||||
events.forEach(event => {
|
events.forEach((event: ScheduledEvent) => {
|
||||||
pubkeys.add(event.pubkey)
|
pubkeys.add(event.pubkey)
|
||||||
|
|
||||||
// Add completer pubkey if event is completed
|
// Add completer pubkey if event is completed
|
||||||
|
|
@ -421,14 +477,50 @@ function cancelDelete() {
|
||||||
|
|
||||||
<!-- Posts List - Natural flow without internal scrolling -->
|
<!-- Posts List - Natural flow without internal scrolling -->
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<!-- Today's Scheduled Events Section -->
|
<!-- Scheduled Events Section with Date Navigation -->
|
||||||
<div v-if="todaysScheduledEvents.length > 0" class="mb-6 md:mb-8">
|
<div v-if="scheduledEventsForDate.length > 0 || !isToday" 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">
|
<div class="flex items-center justify-between px-4 md:px-0 mb-3">
|
||||||
📅 Today's Events
|
<!-- 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>
|
</h3>
|
||||||
<div class="md:space-y-3">
|
<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
|
<ScheduledEventCard
|
||||||
v-for="event in todaysScheduledEvents"
|
v-for="event in scheduledEventsForDate"
|
||||||
:key="`${event.pubkey}:${event.dTag}`"
|
:key="`${event.pubkey}:${event.dTag}`"
|
||||||
:event="event"
|
:event="event"
|
||||||
:get-display-name="getDisplayName"
|
:get-display-name="getDisplayName"
|
||||||
|
|
@ -437,11 +529,14 @@ function cancelDelete() {
|
||||||
@toggle-complete="onToggleComplete"
|
@toggle-complete="onToggleComplete"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="text-center py-8 text-muted-foreground text-sm px-4">
|
||||||
|
No events scheduled for this day
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Posts Section -->
|
<!-- Posts Section -->
|
||||||
<div v-if="threadedPosts.length > 0" class="md:space-y-4 md:py-4">
|
<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
|
💬 Posts
|
||||||
</h3>
|
</h3>
|
||||||
<ThreadedPost
|
<ThreadedPost
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,15 @@ export function useScheduledEvents() {
|
||||||
return scheduledEventService.getEventsForDate(date)
|
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)
|
* Get today's scheduled events (filtered by current user participation)
|
||||||
*/
|
*/
|
||||||
|
|
@ -141,6 +150,7 @@ export function useScheduledEvents() {
|
||||||
// Methods
|
// Methods
|
||||||
getScheduledEvents,
|
getScheduledEvents,
|
||||||
getEventsForDate,
|
getEventsForDate,
|
||||||
|
getEventsForSpecificDate,
|
||||||
getTodaysEvents,
|
getTodaysEvents,
|
||||||
getCompletion,
|
getCompletion,
|
||||||
isCompleted,
|
isCompleted,
|
||||||
|
|
|
||||||
|
|
@ -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[] {
|
getEventsForSpecificDate(date?: string, userPubkey?: string): ScheduledEvent[] {
|
||||||
const today = new Date().toISOString().split('T')[0]
|
const targetDate = date || new Date().toISOString().split('T')[0]
|
||||||
|
|
||||||
// Get one-time events for today (exclude recurring events to avoid duplicates)
|
// Get one-time events for the date (exclude recurring events to avoid duplicates)
|
||||||
const oneTimeEvents = this.getEventsForDate(today).filter(event => !event.recurrence)
|
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 allEvents = this.getScheduledEvents()
|
||||||
const recurringEventsToday = allEvents.filter(event =>
|
const recurringEventsOnDate = allEvents.filter(event =>
|
||||||
event.recurrence && this.doesRecurringEventOccurOnDate(event, today)
|
event.recurrence && this.doesRecurringEventOccurOnDate(event, targetDate)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Combine one-time and recurring events
|
// Combine one-time and recurring events
|
||||||
let events = [...oneTimeEvents, ...recurringEventsToday]
|
let events = [...oneTimeEvents, ...recurringEventsOnDate]
|
||||||
|
|
||||||
// Filter events based on participation (if user pubkey provided)
|
// Filter events based on participation (if user pubkey provided)
|
||||||
if (userPubkey) {
|
if (userPubkey) {
|
||||||
|
|
@ -288,6 +290,13 @@ export class ScheduledEventService extends BaseService {
|
||||||
return events
|
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)
|
* Get completion status for an event (optionally for a specific occurrence)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue