Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,40 @@
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import apiClient from '../api/client';
|
||||
import { Appointment, AppointmentStatus } from '../types';
|
||||
import { Appointment, AppointmentStatus, Participant, ParticipantInput } from '../types';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
/**
|
||||
* Transform backend participant data to frontend format
|
||||
*/
|
||||
function transformParticipant(p: any): Participant {
|
||||
return {
|
||||
id: String(p.id),
|
||||
role: p.role,
|
||||
userId: p.object_id && p.content_type_str?.includes('user') ? String(p.object_id) : undefined,
|
||||
resourceId: p.object_id && p.content_type_str?.includes('resource') ? String(p.object_id) : undefined,
|
||||
displayName: p.display_name || p.participant_display || '',
|
||||
externalEmail: p.external_email || '',
|
||||
externalName: p.external_name || '',
|
||||
isExternal: p.is_external || false,
|
||||
calendarInviteSent: p.calendar_invite_sent || false,
|
||||
calendarInviteSentAt: p.calendar_invite_sent_at || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform frontend participant input to backend format
|
||||
*/
|
||||
function transformParticipantInput(p: ParticipantInput): Record<string, unknown> {
|
||||
return {
|
||||
role: p.role,
|
||||
user_id: p.userId || null,
|
||||
resource_id: p.resourceId || null,
|
||||
external_email: p.externalEmail || '',
|
||||
external_name: p.externalName || '',
|
||||
};
|
||||
}
|
||||
|
||||
interface AppointmentFilters {
|
||||
resource?: string;
|
||||
customer?: string;
|
||||
@@ -62,6 +93,8 @@ export const useAppointments = (filters?: AppointmentFilters) => {
|
||||
isVariablePricing: a.is_variable_pricing || false,
|
||||
remainingBalance: a.remaining_balance ? parseFloat(a.remaining_balance) / 100 : null,
|
||||
overpaidAmount: a.overpaid_amount ? parseFloat(a.overpaid_amount) / 100 : null,
|
||||
// Participants
|
||||
participants: a.participants?.map(transformParticipant) || [],
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -103,12 +136,19 @@ export const useAppointment = (id: string) => {
|
||||
isVariablePricing: data.is_variable_pricing || false,
|
||||
remainingBalance: data.remaining_balance ? parseFloat(data.remaining_balance) / 100 : null,
|
||||
overpaidAmount: data.overpaid_amount ? parseFloat(data.overpaid_amount) / 100 : null,
|
||||
// Participants
|
||||
participants: data.participants?.map(transformParticipant) || [],
|
||||
};
|
||||
},
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
// Extended create data that includes participants
|
||||
interface AppointmentCreateData extends Omit<Appointment, 'id'> {
|
||||
participantsInput?: ParticipantInput[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to create an appointment
|
||||
*/
|
||||
@@ -116,7 +156,7 @@ export const useCreateAppointment = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (appointmentData: Omit<Appointment, 'id'>) => {
|
||||
mutationFn: async (appointmentData: AppointmentCreateData) => {
|
||||
const startTime = appointmentData.startTime;
|
||||
const endTime = new Date(startTime.getTime() + appointmentData.durationMinutes * 60000);
|
||||
|
||||
@@ -133,6 +173,11 @@ export const useCreateAppointment = () => {
|
||||
backendData.customer = parseInt(appointmentData.customerId);
|
||||
}
|
||||
|
||||
// Include participants if provided
|
||||
if (appointmentData.participantsInput && appointmentData.participantsInput.length > 0) {
|
||||
backendData.participants_input = appointmentData.participantsInput.map(transformParticipantInput);
|
||||
}
|
||||
|
||||
const { data } = await apiClient.post('/appointments/', backendData);
|
||||
return data;
|
||||
},
|
||||
@@ -142,6 +187,11 @@ export const useCreateAppointment = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Extended update data that includes participants
|
||||
interface AppointmentUpdateData extends Partial<Appointment> {
|
||||
participantsInput?: ParticipantInput[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to update an appointment with optimistic updates for instant UI feedback
|
||||
*/
|
||||
@@ -149,7 +199,7 @@ export const useUpdateAppointment = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, updates }: { id: string; updates: Partial<Appointment> }) => {
|
||||
mutationFn: async ({ id, updates }: { id: string; updates: AppointmentUpdateData }) => {
|
||||
const backendData: any = {};
|
||||
|
||||
if (updates.serviceId) backendData.service = parseInt(updates.serviceId);
|
||||
@@ -172,6 +222,11 @@ export const useUpdateAppointment = () => {
|
||||
if (updates.status) backendData.status = updates.status;
|
||||
if (updates.notes !== undefined) backendData.notes = updates.notes;
|
||||
|
||||
// Handle participants update
|
||||
if (updates.participantsInput !== undefined) {
|
||||
backendData.participants_input = updates.participantsInput.map(transformParticipantInput);
|
||||
}
|
||||
|
||||
const { data } = await apiClient.patch(`/appointments/${id}/`, backendData);
|
||||
return data;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user