Add series management and ticketing features: Introduce series event functionality in event creation, enhance event display with series information, and implement ticketing options for public events. Update layouts and improve form handling for better user experience.
This commit is contained in:
parent
c3a29fa47c
commit
a88aa62198
24 changed files with 2897 additions and 44 deletions
|
|
@ -29,13 +29,32 @@ export default defineEventHandler(async (event) => {
|
|||
|
||||
await connectDB()
|
||||
|
||||
const newEvent = new Event({
|
||||
const eventData = {
|
||||
...body,
|
||||
createdBy: 'admin@ghostguild.org', // TODO: Use actual authenticated user
|
||||
startDate: new Date(body.startDate),
|
||||
endDate: new Date(body.endDate),
|
||||
registrationDeadline: body.registrationDeadline ? new Date(body.registrationDeadline) : null
|
||||
})
|
||||
}
|
||||
|
||||
// Handle ticket data
|
||||
if (body.tickets) {
|
||||
eventData.tickets = {
|
||||
enabled: body.tickets.enabled || false,
|
||||
public: {
|
||||
available: body.tickets.public?.available || false,
|
||||
name: body.tickets.public?.name || 'Public Ticket',
|
||||
description: body.tickets.public?.description || '',
|
||||
price: body.tickets.public?.price || 0,
|
||||
quantity: body.tickets.public?.quantity || null,
|
||||
sold: 0, // Initialize sold count
|
||||
earlyBirdPrice: body.tickets.public?.earlyBirdPrice || null,
|
||||
earlyBirdDeadline: body.tickets.public?.earlyBirdDeadline ? new Date(body.tickets.public.earlyBirdDeadline) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newEvent = new Event(eventData)
|
||||
|
||||
const savedEvent = await newEvent.save()
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,23 @@ export default defineEventHandler(async (event) => {
|
|||
registrationDeadline: body.registrationDeadline ? new Date(body.registrationDeadline) : null,
|
||||
updatedAt: new Date()
|
||||
}
|
||||
|
||||
// Handle ticket data
|
||||
if (body.tickets) {
|
||||
updateData.tickets = {
|
||||
enabled: body.tickets.enabled || false,
|
||||
public: {
|
||||
available: body.tickets.public?.available || false,
|
||||
name: body.tickets.public?.name || 'Public Ticket',
|
||||
description: body.tickets.public?.description || '',
|
||||
price: body.tickets.public?.price || 0,
|
||||
quantity: body.tickets.public?.quantity || null,
|
||||
sold: body.tickets.public?.sold || 0,
|
||||
earlyBirdPrice: body.tickets.public?.earlyBirdPrice || null,
|
||||
earlyBirdDeadline: body.tickets.public?.earlyBirdDeadline ? new Date(body.tickets.public.earlyBirdDeadline) : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedEvent = await Event.findByIdAndUpdate(
|
||||
eventId,
|
||||
|
|
|
|||
60
server/api/admin/series.get.js
Normal file
60
server/api/admin/series.get.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import Series from '../../models/series.js'
|
||||
import Event from '../../models/event.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
// Fetch all series
|
||||
const series = await Series.find({ isActive: true })
|
||||
.sort({ createdAt: -1 })
|
||||
.lean()
|
||||
|
||||
// For each series, get event count and statistics
|
||||
const seriesWithStats = await Promise.all(
|
||||
series.map(async (s) => {
|
||||
const events = await Event.find({
|
||||
'series.id': s.id,
|
||||
'series.isSeriesEvent': true
|
||||
}).select('_id startDate endDate registrations').lean()
|
||||
|
||||
const now = new Date()
|
||||
const eventCount = events.length
|
||||
const completedEvents = events.filter(e => e.endDate < now).length
|
||||
const upcomingEvents = events.filter(e => e.startDate > now).length
|
||||
|
||||
const firstEventDate = events.length > 0 ?
|
||||
Math.min(...events.map(e => new Date(e.startDate))) : null
|
||||
const lastEventDate = events.length > 0 ?
|
||||
Math.max(...events.map(e => new Date(e.endDate))) : null
|
||||
|
||||
let status = 'upcoming'
|
||||
if (lastEventDate && lastEventDate < now) {
|
||||
status = 'completed'
|
||||
} else if (firstEventDate && firstEventDate <= now && lastEventDate && lastEventDate >= now) {
|
||||
status = 'active'
|
||||
}
|
||||
|
||||
return {
|
||||
...s,
|
||||
eventCount,
|
||||
completedEvents,
|
||||
upcomingEvents,
|
||||
startDate: firstEventDate,
|
||||
endDate: lastEventDate,
|
||||
status,
|
||||
totalRegistrations: events.reduce((sum, e) => sum + (e.registrations?.length || 0), 0)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return seriesWithStats
|
||||
} catch (error) {
|
||||
console.error('Error fetching series:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to fetch series'
|
||||
})
|
||||
}
|
||||
})
|
||||
49
server/api/admin/series.post.js
Normal file
49
server/api/admin/series.post.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import Series from '../../models/series.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const body = await readBody(event)
|
||||
|
||||
// Validate required fields
|
||||
if (!body.id || !body.title || !body.description) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Series ID, title, and description are required'
|
||||
})
|
||||
}
|
||||
|
||||
// Create new series
|
||||
const newSeries = new Series({
|
||||
id: body.id,
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
type: body.type || 'workshop_series',
|
||||
totalEvents: body.totalEvents || null,
|
||||
createdBy: 'admin' // TODO: Get from authentication
|
||||
})
|
||||
|
||||
await newSeries.save()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: newSeries
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating series:', error)
|
||||
|
||||
if (error.code === 11000) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'A series with this ID already exists'
|
||||
})
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || 'Failed to create series'
|
||||
})
|
||||
}
|
||||
})
|
||||
58
server/api/admin/series/[id].delete.js
Normal file
58
server/api/admin/series/[id].delete.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import Series from '../../../models/series.js'
|
||||
import Event from '../../../models/event.js'
|
||||
import { connectDB } from '../../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const id = getRouterParam(event, 'id')
|
||||
|
||||
if (!id) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Series ID is required'
|
||||
})
|
||||
}
|
||||
|
||||
// Find the series
|
||||
const series = await Series.findOne({ id: id })
|
||||
|
||||
if (!series) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Series not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Remove series relationship from all related events
|
||||
await Event.updateMany(
|
||||
{ 'series.id': id, 'series.isSeriesEvent': true },
|
||||
{
|
||||
$set: {
|
||||
'series.isSeriesEvent': false,
|
||||
'series.id': '',
|
||||
'series.title': '',
|
||||
'series.description': '',
|
||||
'series.type': 'workshop_series',
|
||||
'series.position': 1,
|
||||
'series.totalEvents': null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Delete the series
|
||||
await Series.deleteOne({ id: id })
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Series deleted and events converted to standalone events'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting series:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || 'Failed to delete series'
|
||||
})
|
||||
}
|
||||
})
|
||||
62
server/api/admin/series/[id].put.js
Normal file
62
server/api/admin/series/[id].put.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import Series from '../../../models/series.js'
|
||||
import Event from '../../../models/event.js'
|
||||
import { connectDB } from '../../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const id = getRouterParam(event, 'id')
|
||||
const body = await readBody(event)
|
||||
|
||||
if (!id) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Series ID is required'
|
||||
})
|
||||
}
|
||||
|
||||
// Find and update the series
|
||||
const series = await Series.findOne({ id: id })
|
||||
|
||||
if (!series) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Series not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Update series fields
|
||||
if (body.title !== undefined) series.title = body.title
|
||||
if (body.description !== undefined) series.description = body.description
|
||||
if (body.type !== undefined) series.type = body.type
|
||||
if (body.totalEvents !== undefined) series.totalEvents = body.totalEvents
|
||||
if (body.isActive !== undefined) series.isActive = body.isActive
|
||||
|
||||
await series.save()
|
||||
|
||||
// Also update all related events with the new series information
|
||||
await Event.updateMany(
|
||||
{ 'series.id': id, 'series.isSeriesEvent': true },
|
||||
{
|
||||
$set: {
|
||||
'series.title': series.title,
|
||||
'series.description': series.description,
|
||||
'series.type': series.type,
|
||||
'series.totalEvents': series.totalEvents
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: series
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating series:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || 'Failed to update series'
|
||||
})
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue