Skip to main content
Glama
classManagement.ts12.3 kB
import { mindbodyClient } from '../api/client'; import { classCache } from '../cache/index'; import { GetClassesRequest, GetClassesResponse, Class, ClassDescription, } from '../types/mindbody'; // Get all classes with filtering options export async function getClassesTool( startDate?: string, endDate?: string, locationIds?: number[], classDescriptionIds?: number[], staffIds?: number[] ): Promise<{ classes: Array<{ id: number; name: string; description: string; instructor: string; startTime: string; endTime: string; location: string; spotsAvailable: number; totalSpots: number; isWaitlistAvailable: boolean; isCanceled: boolean; isSubstitute: boolean; virtualStreamLink?: string; }>; summary: { totalClasses: number; totalAvailableSpots: number; byInstructor: Record<string, number>; byLocation: Record<string, number>; byClassType: Record<string, number>; }; }> { const cacheKey = `classes:${startDate}:${endDate}:${locationIds?.join(',')}:${classDescriptionIds?.join(',')}:${staffIds?.join(',')}`; let cached = classCache.get<GetClassesResponse>(cacheKey); if (!cached) { const request: GetClassesRequest = { StartDateTime: startDate ? `${startDate}T00:00:00` : new Date().toISOString(), EndDateTime: endDate ? `${endDate}T23:59:59` : new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), LocationIds: locationIds, ClassDescriptionIds: classDescriptionIds, StaffIds: staffIds, Limit: 200, }; cached = await mindbodyClient.get<GetClassesResponse>('/class/classes', { params: request, }); classCache.set(cacheKey, cached); } // Process and format results const formattedClasses = cached.Classes.map((cls) => ({ id: cls.Id, name: cls.ClassDescription?.Name || '', description: cls.ClassDescription?.Description || '', instructor: cls.Staff?.Name || `${cls.Staff?.FirstName || ''} ${cls.Staff?.LastName || ''}`.trim() || '', startTime: cls.StartDateTime, endTime: cls.EndDateTime, location: cls.Location?.Name || '', spotsAvailable: cls.MaxCapacity - cls.TotalBooked, totalSpots: cls.MaxCapacity, isWaitlistAvailable: cls.IsWaitlistAvailable, isCanceled: cls.IsCanceled, isSubstitute: cls.IsSubstitute, virtualStreamLink: (cls as any).VirtualStreamLink, })); // Generate summary const summary = { totalClasses: formattedClasses.filter(c => !c.isCanceled).length, totalAvailableSpots: formattedClasses.reduce((sum, c) => sum + (c.isCanceled ? 0 : c.spotsAvailable), 0), byInstructor: {} as Record<string, number>, byLocation: {} as Record<string, number>, byClassType: {} as Record<string, number>, }; formattedClasses.forEach((cls) => { if (!cls.isCanceled) { summary.byInstructor[cls.instructor] = (summary.byInstructor[cls.instructor] || 0) + 1; summary.byLocation[cls.location] = (summary.byLocation[cls.location] || 0) + 1; summary.byClassType[cls.name] = (summary.byClassType[cls.name] || 0) + 1; } }); return { classes: formattedClasses, summary }; } // Get class descriptions (types of classes offered) export async function getClassDescriptionsTool(): Promise<{ classTypes: Array<{ id: number; name: string; description: string; imageUrl?: string; prerequisites?: string; level?: string; }>; totalTypes: number; }> { const cacheKey = 'class-descriptions'; let cached = classCache.get<any>(cacheKey); if (!cached) { cached = await mindbodyClient.get<any>('/class/classdescriptions', { params: { Limit: 200 }, }); classCache.set(cacheKey, cached, 60); // Cache for 60 minutes } const classTypes = cached.ClassDescriptions.map((desc: any) => ({ id: desc.Id, name: desc.Name, description: desc.Description || '', imageUrl: desc.ImageUrl, prerequisites: desc.Prereq, level: desc.Level?.Name, })); return { classTypes, totalTypes: classTypes.length, }; } // Add client to class (book a class) export async function addClientToClassTool( clientId: string, classId: number, requirePayment: boolean = true, waitlist: boolean = false ): Promise<{ success: boolean; visitId?: number; message: string; requiresPayment?: boolean; amountDue?: number; }> { try { const response = await mindbodyClient.post<any>('/class/addclienttoclass', { ClientId: clientId, ClassId: classId, RequirePayment: requirePayment, Waitlist: waitlist, }); // Clear cache after booking classCache.clear(); return { success: true, visitId: response.Visit?.Id, message: waitlist ? 'Successfully added to waitlist' : 'Successfully booked class', requiresPayment: response.PaymentRequired, amountDue: response.ServicePaymentInfo?.Amount, }; } catch (error: any) { return { success: false, message: error.message || 'Failed to book class', }; } } // Remove client from class (cancel booking) export async function removeClientFromClassTool( clientId: string, classId: number, lateCancel: boolean = false ): Promise<{ success: boolean; message: string; }> { try { await mindbodyClient.post('/class/removeclientfromclass', { ClientId: clientId, ClassId: classId, LateCancel: lateCancel, }); // Clear cache after cancellation classCache.clear(); return { success: true, message: 'Successfully cancelled class booking', }; } catch (error: any) { return { success: false, message: error.message || 'Failed to cancel class', }; } } // Get waitlist entries for a class export async function getWaitlistEntriesTool( classScheduleIds?: number[], classIds?: number[], clientIds?: string[] ): Promise<{ entries: Array<{ classId: number; className: string; classDate: string; clientId: string; clientName: string; enrollmentDatetime: string; requestDatetime: string; position: number; }>; totalEntries: number; }> { const response = await mindbodyClient.get<any>('/class/waitlistentries', { params: { ClassScheduleIds: classScheduleIds, ClassIds: classIds, ClientIds: clientIds, Limit: 200, }, }); const entries = response.WaitlistEntries.map((entry: any) => ({ classId: entry.ClassId, className: entry.ClassName, classDate: entry.ClassDate, clientId: entry.ClientId, clientName: entry.ClientName, enrollmentDatetime: entry.EnrollmentDatetime, requestDatetime: entry.RequestDatetime, position: entry.Position, })); return { entries, totalEntries: entries.length, }; } // Substitute class teacher export async function substituteClassTeacherTool( classId: number, originalTeacherId: number, substituteTeacherId: number, substituteTeacherName?: string ): Promise<{ success: boolean; message: string; updatedClass?: { id: number; name: string; originalTeacher: string; substituteTeacher: string; startTime: string; }; }> { try { const response = await mindbodyClient.post<any>('/class/substituteclassteacher', { ClassId: classId, StaffId: originalTeacherId, SubstituteStaffId: substituteTeacherId, SubstituteStaffName: substituteTeacherName, }); // Clear cache after substitution classCache.clear(); return { success: true, message: 'Successfully substituted teacher', updatedClass: response.Class ? { id: response.Class.Id, name: response.Class.ClassDescription?.Name || '', originalTeacher: response.Class.Staff?.Name || `${response.Class.Staff?.FirstName || ''} ${response.Class.Staff?.LastName || ''}`.trim() || '', substituteTeacher: response.Class.SubstituteTeacher?.Name || substituteTeacherName || 'Unknown', startTime: response.Class.StartDateTime, } : undefined, }; } catch (error: any) { return { success: false, message: error.message || 'Failed to substitute teacher', }; } } // Get class schedules export async function getClassSchedulesTool( locationIds?: number[], classDescriptionIds?: number[], staffIds?: number[], programIds?: number[] ): Promise<{ schedules: Array<{ id: number; className: string; instructor: string; location: string; dayOfWeek: string; startTime: string; endTime: string; startDate: string; endDate?: string; isActive: boolean; }>; totalSchedules: number; }> { const response = await mindbodyClient.get<any>('/class/classschedules', { params: { LocationIds: locationIds, ClassDescriptionIds: classDescriptionIds, StaffIds: staffIds, ProgramIds: programIds, Limit: 200, }, }); const schedules = response.ClassSchedules.map((schedule: any) => ({ id: schedule.Id, className: schedule.ClassDescription?.Name || '', instructor: schedule.Staff?.Name || `${schedule.Staff?.FirstName || ''} ${schedule.Staff?.LastName || ''}`.trim() || '', location: schedule.Location?.Name || '', dayOfWeek: schedule.DayOfWeek, startTime: schedule.StartTime, endTime: schedule.EndTime, startDate: schedule.StartDate, endDate: schedule.EndDate, isActive: schedule.IsActive, })); return { schedules, totalSchedules: schedules.length, }; } // Get visits for a specific class (attendance/booking history) export async function getClassVisitsTool( classId: number, lastModifiedDate?: string ): Promise<{ classInfo: { id: number; name: string; instructor: string; startTime: string; endTime: string; location: string; }; visits: Array<{ id: number; classId: number; clientId: string; clientFirstName: string; clientLastName: string; clientEmail?: string; clientPhone?: string; startDateTime: string; endDateTime: string; signedIn: boolean; webSignup: boolean; makeUp: boolean; lateCancel: boolean; serviceName?: string; serviceId?: number; locationId?: number; locationName?: string; }>; totalVisits: number; summary: { totalSignedIn: number; totalNotSignedIn: number; totalLateCancels: number; totalWebSignups: number; }; }> { const response = await mindbodyClient.get<any>('/class/classvisits', { params: { ClassId: classId, LastModifiedDate: lastModifiedDate, Limit: 200, }, }); const classInfo = { id: response.Class?.Id || classId, name: response.Class?.ClassDescription?.Name || '', instructor: response.Class?.Staff?.DisplayName || `${response.Class?.Staff?.FirstName || ''} ${response.Class?.Staff?.LastName || ''}`.trim() || '', startTime: response.Class?.StartDateTime, endTime: response.Class?.EndDateTime, location: response.Class?.Location?.Name || '', }; const visits = (response.Class?.Visits || []).map((visit: any) => ({ id: visit.Id, classId: visit.ClassId || classId, clientId: visit.ClientId, clientFirstName: visit.Client?.FirstName || visit.FirstName || '', clientLastName: visit.Client?.LastName || visit.LastName || '', clientEmail: visit.Client?.Email || visit.Email, clientPhone: visit.Client?.MobilePhone || visit.Client?.HomePhone || visit.Phone, startDateTime: visit.StartDateTime, endDateTime: visit.EndDateTime, signedIn: visit.SignedIn || false, webSignup: visit.WebSignup || false, makeUp: visit.MakeUp || false, lateCancel: visit.LateCancelled || false, serviceName: visit.Service?.Name || visit.ServiceName, serviceId: visit.Service?.Id || visit.ServiceId, locationId: visit.Location?.Id || visit.LocationId, locationName: visit.Location?.Name || visit.LocationName, })); // Generate summary const summary = { totalSignedIn: visits.filter((v: any) => v.signedIn).length, totalNotSignedIn: visits.filter((v: any) => !v.signedIn && !v.lateCancel).length, totalLateCancels: visits.filter((v: any) => v.lateCancel).length, totalWebSignups: visits.filter((v: any) => v.webSignup).length, }; return { classInfo, visits, totalVisits: visits.length, summary, }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vespo92/MindbodyMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server