Improves the Scheduled Event Card component by adding an expandable view. This change introduces a collapsed view that shows the event time and title, and an expanded view which displays all event details. This allows users to quickly scan the scheduled events and expand those they are interested in.
259 lines
8.1 KiB
Vue
259 lines
8.1 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog'
|
|
import { Calendar, MapPin, Clock, CheckCircle } from 'lucide-vue-next'
|
|
import type { ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
|
|
|
|
interface Props {
|
|
event: ScheduledEvent
|
|
getDisplayName: (pubkey: string) => string
|
|
getCompletion: (eventAddress: string) => EventCompletion | undefined
|
|
adminPubkeys?: string[]
|
|
}
|
|
|
|
interface Emits {
|
|
(e: 'toggle-complete', event: ScheduledEvent): void
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
adminPubkeys: () => []
|
|
})
|
|
|
|
const emit = defineEmits<Emits>()
|
|
|
|
// Confirmation dialog state
|
|
const showConfirmDialog = ref(false)
|
|
|
|
// Collapsed state (collapsed by default)
|
|
const isExpanded = ref(false)
|
|
|
|
// 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 - call function directly
|
|
const isCompleted = computed(() => props.getCompletion(eventAddress.value)?.completed || false)
|
|
|
|
// Check if event is completable (task type)
|
|
const isCompletable = computed(() => props.event.eventType === 'task')
|
|
|
|
// Format the date/time
|
|
const formattedDate = computed(() => {
|
|
try {
|
|
const date = new Date(props.event.start)
|
|
|
|
// Check if it's a datetime or just date
|
|
if (props.event.start.includes('T')) {
|
|
// Full datetime - show date and time
|
|
return date.toLocaleString('en-US', {
|
|
weekday: 'short',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: 'numeric',
|
|
minute: '2-digit'
|
|
})
|
|
} else {
|
|
// Just date
|
|
return date.toLocaleDateString('en-US', {
|
|
weekday: 'short',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
})
|
|
}
|
|
} catch (error) {
|
|
return props.event.start
|
|
}
|
|
})
|
|
|
|
// Format the time range if end time exists
|
|
const formattedTimeRange = computed(() => {
|
|
if (!props.event.end || !props.event.start.includes('T')) return null
|
|
|
|
try {
|
|
const start = new Date(props.event.start)
|
|
const end = new Date(props.event.end)
|
|
|
|
const startTime = start.toLocaleTimeString('en-US', {
|
|
hour: 'numeric',
|
|
minute: '2-digit'
|
|
})
|
|
const endTime = end.toLocaleTimeString('en-US', {
|
|
hour: 'numeric',
|
|
minute: '2-digit'
|
|
})
|
|
|
|
return `${startTime} - ${endTime}`
|
|
} catch (error) {
|
|
return null
|
|
}
|
|
})
|
|
|
|
// Handle mark complete button click - show confirmation dialog
|
|
function handleMarkComplete() {
|
|
console.log('🔘 Mark Complete button clicked for event:', props.event.title)
|
|
showConfirmDialog.value = true
|
|
}
|
|
|
|
// Confirm and execute mark complete
|
|
function confirmMarkComplete() {
|
|
console.log('✅ Confirmed mark complete for event:', props.event.title)
|
|
emit('toggle-complete', props.event)
|
|
showConfirmDialog.value = false
|
|
}
|
|
|
|
// Cancel mark complete
|
|
function cancelMarkComplete() {
|
|
showConfirmDialog.value = false
|
|
}
|
|
|
|
// Toggle expanded/collapsed state
|
|
function toggleExpanded() {
|
|
isExpanded.value = !isExpanded.value
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="border-b md:border md:rounded-lg bg-card transition-all"
|
|
:class="{ 'opacity-60': isCompletable && isCompleted }">
|
|
|
|
<!-- Collapsed View (Default) -->
|
|
<div v-if="!isExpanded"
|
|
class="flex items-center gap-3 p-3 md:p-4">
|
|
<!-- Time -->
|
|
<div @click="toggleExpanded" class="flex items-center gap-1.5 text-sm text-muted-foreground shrink-0 cursor-pointer">
|
|
<Clock class="h-3.5 w-3.5" />
|
|
<span class="font-medium">{{ formattedTimeRange || formattedDate }}</span>
|
|
</div>
|
|
|
|
<!-- Title -->
|
|
<h3 @click="toggleExpanded"
|
|
class="font-semibold text-sm md:text-base flex-1 truncate cursor-pointer hover:text-foreground/80 transition-colors"
|
|
:class="{ 'line-through': isCompletable && isCompleted }">
|
|
{{ event.title }}
|
|
</h3>
|
|
|
|
<!-- Badges and Actions -->
|
|
<div class="flex items-center gap-2 shrink-0">
|
|
<!-- Mark Complete Button (for uncompleted tasks) -->
|
|
<Button
|
|
v-if="isCompletable && !isCompleted"
|
|
@click.stop="handleMarkComplete"
|
|
variant="ghost"
|
|
size="sm"
|
|
class="h-7 w-7 p-0"
|
|
>
|
|
<CheckCircle class="h-4 w-4" />
|
|
</Button>
|
|
|
|
<!-- Completed Badge -->
|
|
<Badge v-if="isCompletable && isCompleted" variant="secondary" class="text-xs">
|
|
✓
|
|
</Badge>
|
|
|
|
<!-- Admin Badge -->
|
|
<Badge v-if="isAdminEvent" variant="secondary" class="text-xs">
|
|
Admin
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Expanded View -->
|
|
<div v-else class="p-4 md:p-6">
|
|
<!-- Event Details -->
|
|
<div class="flex-1 min-w-0">
|
|
<!-- Title and badges with close button -->
|
|
<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': isCompletable && isCompleted }">
|
|
{{ event.title }}
|
|
</h3>
|
|
<Badge v-if="isAdminEvent" variant="secondary" class="shrink-0">
|
|
Admin
|
|
</Badge>
|
|
<Button
|
|
@click="toggleExpanded"
|
|
variant="ghost"
|
|
size="sm"
|
|
class="h-6 w-6 p-0 shrink-0"
|
|
>
|
|
✕
|
|
</Button>
|
|
</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>
|
|
|
|
<!-- 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 (only for completable events) -->
|
|
<div v-if="isCompletable && 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>
|
|
|
|
<!-- Author (if not admin) -->
|
|
<div v-if="!isAdminEvent" class="text-xs text-muted-foreground mb-3">
|
|
Posted by {{ getDisplayName(event.pubkey) }}
|
|
</div>
|
|
|
|
<!-- Mark Complete Button (only for completable task events) -->
|
|
<div v-if="isCompletable && !isCompleted" class="mt-3">
|
|
<Button
|
|
@click.stop="handleMarkComplete"
|
|
variant="outline"
|
|
size="sm"
|
|
class="gap-2"
|
|
>
|
|
<CheckCircle class="h-4 w-4" />
|
|
Mark Complete
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Confirmation Dialog -->
|
|
<Dialog :open="showConfirmDialog" @update:open="(val: boolean) => showConfirmDialog = val">
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Mark Event as Complete?</DialogTitle>
|
|
<DialogDescription>
|
|
This will mark "{{ event.title }}" as completed by you. Other users will be able to see that you completed this event.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button variant="outline" @click="cancelMarkComplete">Cancel</Button>
|
|
<Button @click="confirmMarkComplete">Mark Complete</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</template>
|