Enables marking scheduled events as complete
Implements a feature to mark scheduled events as complete, replacing the checkbox with a button for improved UX. This commit enhances the Scheduled Events functionality by allowing users to mark events as complete. It also includes: - Replaces the checkbox with a "Mark Complete" button for better usability. - Adds logging for debugging purposes during event completion toggling. - Routes completion events (kind 31925) to the ScheduledEventService. - Optimistically updates the local state after publishing completion events.
This commit is contained in:
parent
9b05bcc238
commit
4050b33d0e
5 changed files with 91 additions and 60 deletions
|
|
@ -169,10 +169,12 @@ async function onToggleLike(note: FeedPost) {
|
|||
|
||||
// Handle scheduled event completion toggle
|
||||
async function onToggleComplete(event: ScheduledEvent) {
|
||||
console.log('🎯 NostrFeed: onToggleComplete called for event:', event.title)
|
||||
try {
|
||||
await toggleComplete(event)
|
||||
console.log('✅ NostrFeed: toggleComplete succeeded')
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle event completion:', error)
|
||||
console.error('❌ NostrFeed: Failed to toggle event completion:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -397,8 +399,8 @@ function cancelDelete() {
|
|||
v-for="event in todaysScheduledEvents"
|
||||
:key="`${event.pubkey}:${event.dTag}`"
|
||||
:event="event"
|
||||
:completion="getCompletion(`31922:${event.pubkey}:${event.dTag}`)"
|
||||
:get-display-name="getDisplayName"
|
||||
:get-completion="getCompletion"
|
||||
:admin-pubkeys="adminPubkeys"
|
||||
@toggle-complete="onToggleComplete"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Calendar, MapPin, Clock } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Calendar, MapPin, Clock, CheckCircle } from 'lucide-vue-next'
|
||||
import type { ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
|
||||
|
||||
interface Props {
|
||||
event: ScheduledEvent
|
||||
completion?: EventCompletion
|
||||
getDisplayName: (pubkey: string) => string
|
||||
getCompletion: (eventAddress: string) => EventCompletion | undefined
|
||||
adminPubkeys?: string[]
|
||||
}
|
||||
|
||||
|
|
@ -22,11 +22,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// Event address for tracking completion
|
||||
const eventAddress = computed(() => `31922:${props.event.pubkey}:${props.event.dTag}`)
|
||||
|
||||
// Check if this is an admin event
|
||||
const isAdminEvent = computed(() => props.adminPubkeys.includes(props.event.pubkey))
|
||||
|
||||
// Check if event is completed
|
||||
const isCompleted = computed(() => props.completion?.completed || false)
|
||||
// Check if event is completed - call function directly
|
||||
const isCompleted = computed(() => props.getCompletion(eventAddress.value)?.completed || false)
|
||||
|
||||
// Format the date/time
|
||||
const formattedDate = computed(() => {
|
||||
|
|
@ -79,8 +82,9 @@ const formattedTimeRange = computed(() => {
|
|||
}
|
||||
})
|
||||
|
||||
// Handle checkbox toggle
|
||||
function handleToggleComplete() {
|
||||
// Handle mark complete button click
|
||||
function handleMarkComplete() {
|
||||
console.log('🔘 Mark Complete button clicked for event:', props.event.title)
|
||||
emit('toggle-complete', props.event)
|
||||
}
|
||||
</script>
|
||||
|
|
@ -88,63 +92,64 @@ function handleToggleComplete() {
|
|||
<template>
|
||||
<div class="border-b md:border md:rounded-lg bg-card p-4 md:p-6 transition-all"
|
||||
:class="{ 'opacity-60': isCompleted }">
|
||||
<!-- Header -->
|
||||
<div class="flex items-start gap-3 mb-3">
|
||||
<!-- Completion Checkbox -->
|
||||
<div class="flex items-center pt-1">
|
||||
<Checkbox
|
||||
:checked="isCompleted"
|
||||
@update:checked="handleToggleComplete"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<!-- Event Details -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<!-- Title and badges -->
|
||||
<div class="flex items-start gap-2 mb-2 flex-wrap">
|
||||
<h3 class="font-semibold text-base md:text-lg flex-1"
|
||||
:class="{ 'line-through': isCompleted }">
|
||||
{{ event.title }}
|
||||
</h3>
|
||||
<Badge v-if="isAdminEvent" variant="secondary" class="shrink-0">
|
||||
Admin
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<!-- Event Details -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<!-- Title and badges -->
|
||||
<div class="flex items-start gap-2 mb-2 flex-wrap">
|
||||
<h3 class="font-semibold text-base md:text-lg flex-1"
|
||||
:class="{ 'line-through': isCompleted }">
|
||||
{{ event.title }}
|
||||
</h3>
|
||||
<Badge v-if="isAdminEvent" variant="secondary" class="shrink-0">
|
||||
Admin
|
||||
</Badge>
|
||||
<!-- Date/Time -->
|
||||
<div class="flex items-center gap-4 text-sm text-muted-foreground mb-2 flex-wrap">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Calendar class="h-4 w-4" />
|
||||
<span>{{ formattedDate }}</span>
|
||||
</div>
|
||||
<div v-if="formattedTimeRange" class="flex items-center gap-1.5">
|
||||
<Clock class="h-4 w-4" />
|
||||
<span>{{ formattedTimeRange }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date/Time -->
|
||||
<div class="flex items-center gap-4 text-sm text-muted-foreground mb-2 flex-wrap">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Calendar class="h-4 w-4" />
|
||||
<span>{{ formattedDate }}</span>
|
||||
</div>
|
||||
<div v-if="formattedTimeRange" class="flex items-center gap-1.5">
|
||||
<Clock class="h-4 w-4" />
|
||||
<span>{{ formattedTimeRange }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Location -->
|
||||
<div v-if="event.location" class="flex items-center gap-1.5 text-sm text-muted-foreground mb-3">
|
||||
<MapPin class="h-4 w-4" />
|
||||
<span>{{ event.location }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div v-if="event.location" class="flex items-center gap-1.5 text-sm text-muted-foreground mb-3">
|
||||
<MapPin class="h-4 w-4" />
|
||||
<span>{{ event.location }}</span>
|
||||
</div>
|
||||
<!-- Description/Content -->
|
||||
<div v-if="event.description || event.content" class="text-sm mb-3">
|
||||
<p class="whitespace-pre-wrap break-words">{{ event.description || event.content }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Description/Content -->
|
||||
<div v-if="event.description || event.content" class="text-sm mb-3">
|
||||
<p class="whitespace-pre-wrap break-words">{{ event.description || event.content }}</p>
|
||||
</div>
|
||||
<!-- Completion info -->
|
||||
<div v-if="isCompleted && getCompletion(eventAddress)" class="text-xs text-muted-foreground mb-3">
|
||||
✓ Completed by {{ getDisplayName(getCompletion(eventAddress)!.pubkey) }}
|
||||
<span v-if="getCompletion(eventAddress)!.notes"> - {{ getCompletion(eventAddress)!.notes }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Completion info -->
|
||||
<div v-if="isCompleted && completion" class="text-xs text-muted-foreground">
|
||||
✓ Completed by {{ getDisplayName(completion.pubkey) }}
|
||||
<span v-if="completion.notes"> - {{ completion.notes }}</span>
|
||||
</div>
|
||||
<!-- Author (if not admin) -->
|
||||
<div v-if="!isAdminEvent" class="text-xs text-muted-foreground mb-3">
|
||||
Posted by {{ getDisplayName(event.pubkey) }}
|
||||
</div>
|
||||
|
||||
<!-- Author (if not admin) -->
|
||||
<div v-if="!isAdminEvent" class="text-xs text-muted-foreground mt-2">
|
||||
Posted by {{ getDisplayName(event.pubkey) }}
|
||||
</div>
|
||||
<!-- Mark Complete Button -->
|
||||
<div v-if="!isCompleted" class="mt-3">
|
||||
<Button
|
||||
@click="handleMarkComplete"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="gap-2"
|
||||
>
|
||||
<CheckCircle class="h-4 w-4" />
|
||||
Mark Complete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@ export function useScheduledEvents() {
|
|||
* Toggle completion status of an event
|
||||
*/
|
||||
const toggleComplete = async (event: ScheduledEvent, notes: string = ''): Promise<void> => {
|
||||
console.log('🔧 useScheduledEvents: toggleComplete called for event:', event.title)
|
||||
|
||||
if (!scheduledEventService) {
|
||||
console.error('❌ useScheduledEvents: Scheduled event service not available')
|
||||
toast.error('Scheduled event service not available')
|
||||
return
|
||||
}
|
||||
|
|
@ -62,11 +65,14 @@ export function useScheduledEvents() {
|
|||
try {
|
||||
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
|
||||
const currentlyCompleted = scheduledEventService.isCompleted(eventAddress)
|
||||
console.log('📊 useScheduledEvents: Current completion status:', currentlyCompleted)
|
||||
|
||||
if (currentlyCompleted) {
|
||||
console.log('⬇️ useScheduledEvents: Marking as incomplete...')
|
||||
await scheduledEventService.uncompleteEvent(event)
|
||||
toast.success('Event marked as incomplete')
|
||||
} else {
|
||||
console.log('⬆️ useScheduledEvents: Marking as complete...')
|
||||
await scheduledEventService.completeEvent(event, notes)
|
||||
toast.success('Event completed!')
|
||||
}
|
||||
|
|
@ -81,7 +87,7 @@ export function useScheduledEvents() {
|
|||
toast.error(message)
|
||||
}
|
||||
|
||||
console.error('Failed to toggle completion:', error)
|
||||
console.error('❌ useScheduledEvents: Failed to toggle completion:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -276,8 +276,11 @@ export class FeedService extends BaseService {
|
|||
|
||||
// Route RSVP/completion events (kind 31925) to ScheduledEventService
|
||||
if (event.kind === 31925) {
|
||||
console.log('🔀 FeedService: Routing kind 31925 (completion) to ScheduledEventService')
|
||||
if (this.scheduledEventService) {
|
||||
this.scheduledEventService.handleCompletionEvent(event)
|
||||
} else {
|
||||
console.warn('⚠️ FeedService: ScheduledEventService not available')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ export class ScheduledEventService extends BaseService {
|
|||
* Made public so FeedService can route kind 31925 events to this service
|
||||
*/
|
||||
public handleCompletionEvent(event: NostrEvent): void {
|
||||
console.log('🔔 ScheduledEventService: Received completion event (kind 31925)', event.id)
|
||||
|
||||
try {
|
||||
// Find the event being responded to
|
||||
const aTag = event.tags.find(tag => tag[0] === 'a')?.[1]
|
||||
|
|
@ -125,6 +127,13 @@ export class ScheduledEventService extends BaseService {
|
|||
const completedAtTag = event.tags.find(tag => tag[0] === 'completed_at')?.[1]
|
||||
const completedAt = completedAtTag ? parseInt(completedAtTag) : undefined
|
||||
|
||||
console.log('📋 Completion details:', {
|
||||
aTag,
|
||||
completed,
|
||||
pubkey: event.pubkey,
|
||||
eventId: event.id
|
||||
})
|
||||
|
||||
const completion: EventCompletion = {
|
||||
id: event.id,
|
||||
eventAddress: aTag,
|
||||
|
|
@ -139,6 +148,9 @@ export class ScheduledEventService extends BaseService {
|
|||
const existing = this._completions.get(aTag)
|
||||
if (!existing || event.created_at > existing.created_at) {
|
||||
this._completions.set(aTag, completion)
|
||||
console.log('✅ Stored completion for:', aTag, '- completed:', completed)
|
||||
} else {
|
||||
console.log('⏭️ Skipped older completion for:', aTag)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -228,9 +240,12 @@ export class ScheduledEventService extends BaseService {
|
|||
const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
|
||||
|
||||
// Publish the completion
|
||||
await this.relayHub.publishEvent(signedEvent)
|
||||
console.log('📤 Publishing completion event (kind 31925) for:', eventAddress)
|
||||
const result = await this.relayHub.publishEvent(signedEvent)
|
||||
console.log('✅ Completion event published to', result.success, '/', result.total, 'relays')
|
||||
|
||||
// Optimistically update local state
|
||||
console.log('🔄 Optimistically updating local state')
|
||||
this.handleCompletionEvent(signedEvent)
|
||||
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue