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:
padreug 2025-10-21 22:31:35 +02:00
parent b15a8c21c0
commit 5d6f702859
5 changed files with 91 additions and 60 deletions

View file

@ -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>