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
|
// Handle scheduled event completion toggle
|
||||||
async function onToggleComplete(event: ScheduledEvent) {
|
async function onToggleComplete(event: ScheduledEvent) {
|
||||||
|
console.log('🎯 NostrFeed: onToggleComplete called for event:', event.title)
|
||||||
try {
|
try {
|
||||||
await toggleComplete(event)
|
await toggleComplete(event)
|
||||||
|
console.log('✅ NostrFeed: toggleComplete succeeded')
|
||||||
} catch (error) {
|
} 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"
|
v-for="event in todaysScheduledEvents"
|
||||||
:key="`${event.pubkey}:${event.dTag}`"
|
:key="`${event.pubkey}:${event.dTag}`"
|
||||||
:event="event"
|
:event="event"
|
||||||
:completion="getCompletion(`31922:${event.pubkey}:${event.dTag}`)"
|
|
||||||
:get-display-name="getDisplayName"
|
:get-display-name="getDisplayName"
|
||||||
|
:get-completion="getCompletion"
|
||||||
:admin-pubkeys="adminPubkeys"
|
:admin-pubkeys="adminPubkeys"
|
||||||
@toggle-complete="onToggleComplete"
|
@toggle-complete="onToggleComplete"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Calendar, MapPin, Clock } from 'lucide-vue-next'
|
import { Calendar, MapPin, Clock, CheckCircle } from 'lucide-vue-next'
|
||||||
import type { ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
|
import type { ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: ScheduledEvent
|
event: ScheduledEvent
|
||||||
completion?: EventCompletion
|
|
||||||
getDisplayName: (pubkey: string) => string
|
getDisplayName: (pubkey: string) => string
|
||||||
|
getCompletion: (eventAddress: string) => EventCompletion | undefined
|
||||||
adminPubkeys?: string[]
|
adminPubkeys?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,11 +22,14 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
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
|
// Check if this is an admin event
|
||||||
const isAdminEvent = computed(() => props.adminPubkeys.includes(props.event.pubkey))
|
const isAdminEvent = computed(() => props.adminPubkeys.includes(props.event.pubkey))
|
||||||
|
|
||||||
// Check if event is completed
|
// Check if event is completed - call function directly
|
||||||
const isCompleted = computed(() => props.completion?.completed || false)
|
const isCompleted = computed(() => props.getCompletion(eventAddress.value)?.completed || false)
|
||||||
|
|
||||||
// Format the date/time
|
// Format the date/time
|
||||||
const formattedDate = computed(() => {
|
const formattedDate = computed(() => {
|
||||||
|
|
@ -79,8 +82,9 @@ const formattedTimeRange = computed(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle checkbox toggle
|
// Handle mark complete button click
|
||||||
function handleToggleComplete() {
|
function handleMarkComplete() {
|
||||||
|
console.log('🔘 Mark Complete button clicked for event:', props.event.title)
|
||||||
emit('toggle-complete', props.event)
|
emit('toggle-complete', props.event)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -88,17 +92,6 @@ function handleToggleComplete() {
|
||||||
<template>
|
<template>
|
||||||
<div class="border-b md:border md:rounded-lg bg-card p-4 md:p-6 transition-all"
|
<div class="border-b md:border md:rounded-lg bg-card p-4 md:p-6 transition-all"
|
||||||
:class="{ 'opacity-60': isCompleted }">
|
: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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Event Details -->
|
<!-- Event Details -->
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<!-- Title and badges -->
|
<!-- Title and badges -->
|
||||||
|
|
@ -136,15 +129,27 @@ function handleToggleComplete() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Completion info -->
|
<!-- Completion info -->
|
||||||
<div v-if="isCompleted && completion" class="text-xs text-muted-foreground">
|
<div v-if="isCompleted && getCompletion(eventAddress)" class="text-xs text-muted-foreground mb-3">
|
||||||
✓ Completed by {{ getDisplayName(completion.pubkey) }}
|
✓ Completed by {{ getDisplayName(getCompletion(eventAddress)!.pubkey) }}
|
||||||
<span v-if="completion.notes"> - {{ completion.notes }}</span>
|
<span v-if="getCompletion(eventAddress)!.notes"> - {{ getCompletion(eventAddress)!.notes }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Author (if not admin) -->
|
<!-- Author (if not admin) -->
|
||||||
<div v-if="!isAdminEvent" class="text-xs text-muted-foreground mt-2">
|
<div v-if="!isAdminEvent" class="text-xs text-muted-foreground mb-3">
|
||||||
Posted by {{ getDisplayName(event.pubkey) }}
|
Posted by {{ getDisplayName(event.pubkey) }}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,10 @@ export function useScheduledEvents() {
|
||||||
* Toggle completion status of an event
|
* Toggle completion status of an event
|
||||||
*/
|
*/
|
||||||
const toggleComplete = async (event: ScheduledEvent, notes: string = ''): Promise<void> => {
|
const toggleComplete = async (event: ScheduledEvent, notes: string = ''): Promise<void> => {
|
||||||
|
console.log('🔧 useScheduledEvents: toggleComplete called for event:', event.title)
|
||||||
|
|
||||||
if (!scheduledEventService) {
|
if (!scheduledEventService) {
|
||||||
|
console.error('❌ useScheduledEvents: Scheduled event service not available')
|
||||||
toast.error('Scheduled event service not available')
|
toast.error('Scheduled event service not available')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -62,11 +65,14 @@ export function useScheduledEvents() {
|
||||||
try {
|
try {
|
||||||
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
|
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
|
||||||
const currentlyCompleted = scheduledEventService.isCompleted(eventAddress)
|
const currentlyCompleted = scheduledEventService.isCompleted(eventAddress)
|
||||||
|
console.log('📊 useScheduledEvents: Current completion status:', currentlyCompleted)
|
||||||
|
|
||||||
if (currentlyCompleted) {
|
if (currentlyCompleted) {
|
||||||
|
console.log('⬇️ useScheduledEvents: Marking as incomplete...')
|
||||||
await scheduledEventService.uncompleteEvent(event)
|
await scheduledEventService.uncompleteEvent(event)
|
||||||
toast.success('Event marked as incomplete')
|
toast.success('Event marked as incomplete')
|
||||||
} else {
|
} else {
|
||||||
|
console.log('⬆️ useScheduledEvents: Marking as complete...')
|
||||||
await scheduledEventService.completeEvent(event, notes)
|
await scheduledEventService.completeEvent(event, notes)
|
||||||
toast.success('Event completed!')
|
toast.success('Event completed!')
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +87,7 @@ export function useScheduledEvents() {
|
||||||
toast.error(message)
|
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
|
// Route RSVP/completion events (kind 31925) to ScheduledEventService
|
||||||
if (event.kind === 31925) {
|
if (event.kind === 31925) {
|
||||||
|
console.log('🔀 FeedService: Routing kind 31925 (completion) to ScheduledEventService')
|
||||||
if (this.scheduledEventService) {
|
if (this.scheduledEventService) {
|
||||||
this.scheduledEventService.handleCompletionEvent(event)
|
this.scheduledEventService.handleCompletionEvent(event)
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ FeedService: ScheduledEventService not available')
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,8 @@ export class ScheduledEventService extends BaseService {
|
||||||
* Made public so FeedService can route kind 31925 events to this service
|
* Made public so FeedService can route kind 31925 events to this service
|
||||||
*/
|
*/
|
||||||
public handleCompletionEvent(event: NostrEvent): void {
|
public handleCompletionEvent(event: NostrEvent): void {
|
||||||
|
console.log('🔔 ScheduledEventService: Received completion event (kind 31925)', event.id)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Find the event being responded to
|
// Find the event being responded to
|
||||||
const aTag = event.tags.find(tag => tag[0] === 'a')?.[1]
|
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 completedAtTag = event.tags.find(tag => tag[0] === 'completed_at')?.[1]
|
||||||
const completedAt = completedAtTag ? parseInt(completedAtTag) : undefined
|
const completedAt = completedAtTag ? parseInt(completedAtTag) : undefined
|
||||||
|
|
||||||
|
console.log('📋 Completion details:', {
|
||||||
|
aTag,
|
||||||
|
completed,
|
||||||
|
pubkey: event.pubkey,
|
||||||
|
eventId: event.id
|
||||||
|
})
|
||||||
|
|
||||||
const completion: EventCompletion = {
|
const completion: EventCompletion = {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
eventAddress: aTag,
|
eventAddress: aTag,
|
||||||
|
|
@ -139,6 +148,9 @@ export class ScheduledEventService extends BaseService {
|
||||||
const existing = this._completions.get(aTag)
|
const existing = this._completions.get(aTag)
|
||||||
if (!existing || event.created_at > existing.created_at) {
|
if (!existing || event.created_at > existing.created_at) {
|
||||||
this._completions.set(aTag, completion)
|
this._completions.set(aTag, completion)
|
||||||
|
console.log('✅ Stored completion for:', aTag, '- completed:', completed)
|
||||||
|
} else {
|
||||||
|
console.log('⏭️ Skipped older completion for:', aTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -228,9 +240,12 @@ export class ScheduledEventService extends BaseService {
|
||||||
const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
|
const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
|
||||||
|
|
||||||
// Publish the completion
|
// 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
|
// Optimistically update local state
|
||||||
|
console.log('🔄 Optimistically updating local state')
|
||||||
this.handleCompletionEvent(signedEvent)
|
this.handleCompletionEvent(signedEvent)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue