Features: - Complete multi-step booking flow with service selection, date/time picker, auth (login/signup with email verification), payment, and confirmation - Business hours settings page for defining when business is open - TimeBlock purpose field (BUSINESS_HOURS, CLOSURE, UNAVAILABLE) - Service resource assignment with prep/takedown time buffers - Availability checking respects business hours and service buffers - Customer registration via email verification code UI/UX: - Full dark mode support for all booking components - Separate first/last name fields in signup form - Back buttons on each wizard step - Removed auto-redirect from confirmation page API: - Public endpoints for services, availability, business hours - Customer verification and registration endpoints - Tenant lookup from X-Business-Subdomain header 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
698 lines
18 KiB
TypeScript
698 lines
18 KiB
TypeScript
// Domain models based on the Game Plan
|
|
|
|
// FIX: Added PageComponent types and updated Business interface for website editor feature.
|
|
export type PageComponentType = 'HEADING' | 'TEXT' | 'IMAGE' | 'BUTTON' | 'SERVICE' | 'COLUMNS';
|
|
|
|
export interface PageComponent {
|
|
id: string;
|
|
type: PageComponentType;
|
|
content?: {
|
|
text?: string;
|
|
level?: 1 | 2 | 3;
|
|
src?: string;
|
|
alt?: string;
|
|
buttonText?: string;
|
|
href?: string;
|
|
serviceId?: string;
|
|
};
|
|
children?: PageComponent[][];
|
|
}
|
|
|
|
export interface CustomDomain {
|
|
id: number;
|
|
domain: string;
|
|
is_verified: boolean;
|
|
ssl_provisioned: boolean;
|
|
is_primary: boolean;
|
|
verification_token: string;
|
|
dns_txt_record: string;
|
|
dns_txt_record_name: string;
|
|
created_at: string;
|
|
verified_at?: string;
|
|
}
|
|
|
|
export interface PlanPermissions {
|
|
sms_reminders: boolean;
|
|
webhooks: boolean;
|
|
api_access: boolean;
|
|
custom_domain: boolean;
|
|
white_label: boolean;
|
|
custom_oauth: boolean;
|
|
plugins: boolean;
|
|
can_create_plugins: boolean;
|
|
tasks: boolean;
|
|
export_data: boolean;
|
|
video_conferencing: boolean;
|
|
two_factor_auth: boolean;
|
|
masked_calling: boolean;
|
|
pos_system: boolean;
|
|
mobile_app: boolean;
|
|
contracts: boolean;
|
|
}
|
|
|
|
export interface Business {
|
|
id: string;
|
|
name: string;
|
|
subdomain: string;
|
|
primaryColor: string;
|
|
secondaryColor: string;
|
|
logoUrl?: string;
|
|
emailLogoUrl?: string;
|
|
logoDisplayMode?: 'logo-only' | 'text-only' | 'logo-and-text'; // How to display branding
|
|
timezone?: string; // IANA timezone (e.g., 'America/New_York')
|
|
timezoneDisplayMode?: 'business' | 'viewer'; // How times are displayed to users
|
|
whitelabelEnabled: boolean;
|
|
plan?: 'Free' | 'Professional' | 'Business' | 'Enterprise';
|
|
status?: 'Active' | 'Suspended' | 'Trial';
|
|
joinedAt?: Date;
|
|
resourcesCanReschedule?: boolean;
|
|
paymentsEnabled: boolean;
|
|
requirePaymentMethodToBook: boolean;
|
|
cancellationWindowHours: number;
|
|
lateCancellationFeePercent: number;
|
|
initialSetupComplete?: boolean;
|
|
customDomain?: string;
|
|
customDomainVerified?: boolean;
|
|
bookingReturnUrl?: string; // URL to redirect customers after booking completion
|
|
stripeConnectAccountId?: string;
|
|
websitePages?: Record<string, { name: string; content: PageComponent[] }>;
|
|
customerDashboardContent?: PageComponent[];
|
|
// Booking page customization
|
|
serviceSelectionHeading?: string; // Custom heading for service selection (default: "Choose your experience")
|
|
serviceSelectionSubheading?: string; // Custom subheading (default: "Select a service to begin your booking.")
|
|
trialStart?: string;
|
|
trialEnd?: string;
|
|
isTrialActive?: boolean;
|
|
isTrialExpired?: boolean;
|
|
daysLeftInTrial?: number;
|
|
resourceTypes?: ResourceTypeDefinition[]; // Custom resource types
|
|
// Platform-controlled permissions
|
|
canManageOAuthCredentials?: boolean;
|
|
// Plan permissions (what features are available based on subscription)
|
|
planPermissions?: PlanPermissions;
|
|
}
|
|
|
|
export type UserRole = 'superuser' | 'platform_manager' | 'platform_support' | 'owner' | 'manager' | 'staff' | 'resource' | 'customer';
|
|
|
|
export interface NotificationPreferences {
|
|
email: boolean;
|
|
sms: boolean;
|
|
in_app: boolean;
|
|
appointment_reminders: boolean;
|
|
marketing: boolean;
|
|
}
|
|
|
|
export interface QuotaOverage {
|
|
id: number;
|
|
quota_type: string;
|
|
display_name: string;
|
|
current_usage: number;
|
|
allowed_limit: number;
|
|
overage_amount: number;
|
|
days_remaining: number;
|
|
grace_period_ends_at: string;
|
|
}
|
|
|
|
export interface User {
|
|
id: string | number;
|
|
username?: string;
|
|
name: string;
|
|
email: string;
|
|
role: UserRole;
|
|
avatarUrl?: string;
|
|
phone?: string;
|
|
email_verified?: boolean;
|
|
two_factor_enabled?: boolean;
|
|
timezone?: string;
|
|
locale?: string;
|
|
notification_preferences?: NotificationPreferences;
|
|
can_invite_staff?: boolean;
|
|
can_access_tickets?: boolean;
|
|
can_send_messages?: boolean;
|
|
can_edit_schedule?: boolean;
|
|
linked_resource_id?: number;
|
|
linked_resource_name?: string;
|
|
permissions?: Record<string, boolean>;
|
|
quota_overages?: QuotaOverage[];
|
|
}
|
|
|
|
export type ResourceType = 'STAFF' | 'ROOM' | 'EQUIPMENT';
|
|
|
|
export type ResourceTypeCategory = 'STAFF' | 'OTHER';
|
|
|
|
export interface ResourceTypeDefinition {
|
|
id: string;
|
|
name: string; // User-facing name like "Stylist", "Massage Therapist", "Treatment Room"
|
|
description?: string; // Description of this resource type
|
|
category: ResourceTypeCategory; // STAFF (requires staff assignment) or OTHER
|
|
isDefault: boolean; // Cannot be deleted
|
|
iconName?: string; // Optional icon identifier
|
|
}
|
|
|
|
export interface Resource {
|
|
id: string;
|
|
name: string;
|
|
type: ResourceType; // Legacy field - will be deprecated
|
|
typeId?: string; // New field - references ResourceTypeDefinition
|
|
userId?: string;
|
|
maxConcurrentEvents: number;
|
|
savedLaneCount?: number; // Remembered lane count when multilane is disabled
|
|
created_at?: string; // Used for quota overage calculation (oldest archived first)
|
|
is_archived_by_quota?: boolean; // True if archived due to quota overage
|
|
userCanEditSchedule?: boolean; // Allow linked user to edit their schedule regardless of role
|
|
}
|
|
|
|
// Backend uses: SCHEDULED, EN_ROUTE, IN_PROGRESS, CANCELED, COMPLETED, AWAITING_PAYMENT, PAID, NOSHOW
|
|
// Frontend aliases: PENDING (for SCHEDULED), CONFIRMED (for SCHEDULED), CANCELLED (for CANCELED), NO_SHOW (for NOSHOW)
|
|
export type AppointmentStatus =
|
|
| 'SCHEDULED' | 'EN_ROUTE' | 'IN_PROGRESS' | 'CANCELED' | 'COMPLETED' | 'AWAITING_PAYMENT' | 'PAID' | 'NOSHOW'
|
|
// Legacy aliases for frontend compatibility
|
|
| 'PENDING' | 'CONFIRMED' | 'CANCELLED' | 'NO_SHOW';
|
|
|
|
export interface Appointment {
|
|
id: string;
|
|
resourceId: string | null; // null if unassigned
|
|
customerId?: string; // optional for walk-in appointments
|
|
customerName: string;
|
|
serviceId: string;
|
|
startTime: Date; // For MVP, we will assume a specific date
|
|
durationMinutes: number;
|
|
status: AppointmentStatus;
|
|
notes?: string;
|
|
}
|
|
|
|
export interface Blocker {
|
|
id: string;
|
|
resourceId: string;
|
|
startTime: Date;
|
|
durationMinutes: number;
|
|
title: string;
|
|
}
|
|
|
|
export interface PaymentMethod {
|
|
id: string;
|
|
brand: 'Visa' | 'Mastercard' | 'Amex';
|
|
last4: string;
|
|
isDefault: boolean;
|
|
}
|
|
|
|
|
|
export interface Customer {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
phone: string;
|
|
city?: string;
|
|
state?: string;
|
|
zip?: string;
|
|
totalSpend: number;
|
|
lastVisit: Date | null;
|
|
status: 'Active' | 'Inactive' | 'Blocked';
|
|
avatarUrl?: string;
|
|
tags?: string[];
|
|
userId?: string;
|
|
paymentMethods: PaymentMethod[];
|
|
}
|
|
|
|
export interface Service {
|
|
id: string;
|
|
name: string;
|
|
durationMinutes: number;
|
|
duration?: number; // Duration in minutes (backend field name)
|
|
price: number;
|
|
price_cents?: number; // Price in cents
|
|
description: string;
|
|
displayOrder: number;
|
|
display_order?: number;
|
|
photos?: string[];
|
|
is_active?: boolean;
|
|
created_at?: string; // Used for quota overage calculation (oldest archived first)
|
|
updated_at?: string;
|
|
is_archived_by_quota?: boolean; // True if archived due to quota overage
|
|
|
|
// Pricing fields
|
|
variable_pricing?: boolean; // If true, final price is determined after service completion
|
|
deposit_amount?: number | null; // Fixed deposit amount in dollars
|
|
deposit_amount_cents?: number | null; // Fixed deposit amount in cents
|
|
deposit_percent?: number | null; // Deposit as percentage (only for fixed pricing)
|
|
requires_deposit?: boolean; // True if deposit configured (computed)
|
|
requires_saved_payment_method?: boolean; // True if deposit > 0 or variable pricing (computed)
|
|
deposit_display?: string | null; // Human-readable deposit description
|
|
|
|
// Resource assignment
|
|
all_resources?: boolean;
|
|
resource_ids?: string[];
|
|
resource_names?: string[];
|
|
|
|
// Buffer time (frontend-only for now)
|
|
prep_time?: number;
|
|
takedown_time?: number;
|
|
|
|
// Notification settings (frontend-only for now)
|
|
reminder_enabled?: boolean;
|
|
reminder_hours_before?: number;
|
|
reminder_email?: boolean;
|
|
reminder_sms?: boolean;
|
|
thank_you_email_enabled?: boolean;
|
|
|
|
// Category (future feature)
|
|
category?: string | null;
|
|
}
|
|
|
|
export interface Metric {
|
|
label: string;
|
|
value: string;
|
|
change: string;
|
|
trend: 'up' | 'down' | 'neutral';
|
|
}
|
|
|
|
// --- Platform Types ---
|
|
|
|
export type TicketType = 'PLATFORM' | 'CUSTOMER' | 'STAFF_REQUEST' | 'INTERNAL';
|
|
export type TicketStatus = 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'CLOSED' | 'AWAITING_RESPONSE';
|
|
export type TicketPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
|
|
export type TicketCategory =
|
|
| 'BILLING'
|
|
| 'TECHNICAL'
|
|
| 'FEATURE_REQUEST'
|
|
| 'ACCOUNT'
|
|
| 'APPOINTMENT'
|
|
| 'REFUND'
|
|
| 'COMPLAINT'
|
|
| 'GENERAL_INQUIRY'
|
|
| 'TIME_OFF'
|
|
| 'SCHEDULE_CHANGE'
|
|
| 'EQUIPMENT'
|
|
| 'OTHER';
|
|
|
|
export interface TicketComment {
|
|
id: string;
|
|
ticket: string; // Ticket ID
|
|
author: string; // User ID
|
|
authorEmail: string;
|
|
authorFullName: string;
|
|
commentText: string;
|
|
createdAt: string; // Date string
|
|
isInternal: boolean;
|
|
}
|
|
|
|
export interface TicketEmailAddressListItem {
|
|
id: number;
|
|
display_name: string;
|
|
email_address: string;
|
|
color: string;
|
|
is_active: boolean;
|
|
is_default: boolean;
|
|
}
|
|
|
|
export interface Ticket {
|
|
id: string;
|
|
tenant?: string; // Tenant ID, optional for platform tickets
|
|
creator: string; // User ID
|
|
creatorEmail: string;
|
|
creatorFullName: string;
|
|
assignee?: string; // User ID, optional
|
|
assigneeEmail?: string;
|
|
assigneeFullName?: string;
|
|
ticketType: TicketType;
|
|
status: TicketStatus;
|
|
priority: TicketPriority;
|
|
subject: string;
|
|
description: string;
|
|
category: TicketCategory;
|
|
relatedAppointmentId?: string; // Appointment ID, optional
|
|
dueAt?: string; // Date string
|
|
firstResponseAt?: string; // Date string
|
|
isOverdue?: boolean;
|
|
createdAt: string; // Date string
|
|
updatedAt: string; // Date string
|
|
resolvedAt?: string; // Date string
|
|
comments?: TicketComment[]; // Nested comments
|
|
// External sender info (for tickets from non-registered users via email)
|
|
externalEmail?: string;
|
|
externalName?: string;
|
|
// Source email address (which email address received/sent this ticket)
|
|
source_email_address?: TicketEmailAddressListItem;
|
|
}
|
|
|
|
export interface TicketTemplate {
|
|
id: string;
|
|
tenant?: string; // Tenant ID, optional for platform templates
|
|
name: string;
|
|
description: string;
|
|
ticketType: TicketType;
|
|
category: TicketCategory;
|
|
defaultPriority: TicketPriority;
|
|
subjectTemplate: string;
|
|
descriptionTemplate: string;
|
|
isActive: boolean;
|
|
createdAt: string; // Date string
|
|
}
|
|
|
|
export interface CannedResponse {
|
|
id: string;
|
|
tenant?: string; // Tenant ID, optional for platform canned responses
|
|
title: string;
|
|
content: string;
|
|
category?: TicketCategory;
|
|
isActive: boolean;
|
|
useCount: number;
|
|
createdBy?: string; // User ID
|
|
createdAt: string; // Date string
|
|
}
|
|
|
|
export interface PlatformMetric {
|
|
label: string;
|
|
value: string;
|
|
change: string;
|
|
trend: 'up' | 'down' | 'neutral';
|
|
color: 'blue' | 'green' | 'purple' | 'orange';
|
|
}
|
|
|
|
// --- OAuth Settings Types ---
|
|
|
|
export interface OAuthProvider {
|
|
id: string;
|
|
name: string;
|
|
icon: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface BusinessOAuthSettings {
|
|
enabledProviders: string[];
|
|
allowRegistration: boolean;
|
|
autoLinkByEmail: boolean;
|
|
useCustomCredentials: boolean;
|
|
}
|
|
|
|
export interface BusinessOAuthSettingsResponse {
|
|
settings: BusinessOAuthSettings;
|
|
availableProviders: OAuthProvider[];
|
|
}
|
|
|
|
// --- OAuth Credentials Types ---
|
|
|
|
export interface OAuthProviderCredential {
|
|
client_id: string;
|
|
client_secret: string;
|
|
has_secret: boolean;
|
|
}
|
|
|
|
export interface BusinessOAuthCredentialsResponse {
|
|
credentials: Record<string, OAuthProviderCredential>;
|
|
useCustomCredentials: boolean;
|
|
}
|
|
|
|
// --- Plugin Types ---
|
|
|
|
export type PluginCategory = 'EMAIL' | 'REPORTS' | 'CUSTOMER' | 'BOOKING' | 'INTEGRATION' | 'AUTOMATION' | 'OTHER';
|
|
|
|
export interface PluginTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
category: PluginCategory;
|
|
version: string;
|
|
author: string;
|
|
logoUrl?: string;
|
|
pluginCode?: string;
|
|
rating: number;
|
|
ratingCount: number;
|
|
installCount: number;
|
|
isVerified: boolean;
|
|
isFeatured: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface PluginInstallation {
|
|
id: string;
|
|
template: string;
|
|
templateName: string;
|
|
templateDescription: string;
|
|
category: PluginCategory;
|
|
version: string;
|
|
authorName?: string;
|
|
logoUrl?: string;
|
|
templateVariables?: Record<string, any>;
|
|
configValues?: Record<string, any>;
|
|
isActive: boolean;
|
|
installedAt: string;
|
|
hasUpdate: boolean;
|
|
rating?: number;
|
|
review?: string;
|
|
scheduledTaskId?: string;
|
|
}
|
|
|
|
// --- Email Template Types ---
|
|
|
|
export type EmailTemplateScope = 'BUSINESS' | 'PLATFORM';
|
|
|
|
export type EmailTemplateCategory =
|
|
| 'APPOINTMENT'
|
|
| 'REMINDER'
|
|
| 'CONFIRMATION'
|
|
| 'MARKETING'
|
|
| 'NOTIFICATION'
|
|
| 'REPORT'
|
|
| 'OTHER';
|
|
|
|
export interface EmailTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
subject: string;
|
|
htmlContent: string;
|
|
textContent: string;
|
|
scope: EmailTemplateScope;
|
|
isDefault: boolean;
|
|
category: EmailTemplateCategory;
|
|
previewContext?: Record<string, any>;
|
|
createdBy?: number;
|
|
createdByName?: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface EmailTemplatePreview {
|
|
subject: string;
|
|
htmlContent: string;
|
|
textContent: string;
|
|
forceFooter: boolean;
|
|
}
|
|
|
|
export interface EmailTemplateVariable {
|
|
code: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface EmailTemplateVariableGroup {
|
|
category: string;
|
|
items: EmailTemplateVariable[];
|
|
}
|
|
|
|
// --- Contract Types ---
|
|
|
|
export type ContractScope = 'CUSTOMER' | 'APPOINTMENT';
|
|
export type ContractStatus = 'PENDING' | 'SIGNED' | 'EXPIRED' | 'VOIDED';
|
|
export type ContractTemplateStatus = 'DRAFT' | 'ACTIVE' | 'ARCHIVED';
|
|
|
|
export interface ContractTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
content: string;
|
|
scope: ContractScope;
|
|
status: ContractTemplateStatus;
|
|
expires_after_days: number | null;
|
|
version: number;
|
|
version_notes: string;
|
|
services: { id: string; name: string }[];
|
|
created_by: string | null;
|
|
created_by_name: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface Contract {
|
|
id: string;
|
|
template: string;
|
|
template_name: string;
|
|
template_version: number;
|
|
scope: ContractScope;
|
|
status: ContractStatus;
|
|
content: string;
|
|
customer?: string;
|
|
customer_name?: string;
|
|
customer_email?: string;
|
|
appointment?: string;
|
|
appointment_service_name?: string;
|
|
appointment_start_time?: string;
|
|
service?: string;
|
|
service_name?: string;
|
|
sent_at: string | null;
|
|
signed_at: string | null;
|
|
expires_at: string | null;
|
|
voided_at: string | null;
|
|
voided_reason: string | null;
|
|
public_token: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface ContractSignature {
|
|
id: string;
|
|
contract: string;
|
|
signer_name: string;
|
|
signer_email: string;
|
|
signature_data: string;
|
|
ip_address: string;
|
|
user_agent: string;
|
|
signed_at: string;
|
|
}
|
|
|
|
export interface ContractPublicView {
|
|
contract: Contract;
|
|
template: {
|
|
name: string;
|
|
content: string;
|
|
};
|
|
business: {
|
|
name: string;
|
|
logo_url?: string;
|
|
};
|
|
customer?: {
|
|
name: string;
|
|
email: string;
|
|
};
|
|
appointment?: {
|
|
service_name: string;
|
|
start_time: string;
|
|
};
|
|
is_expired: boolean;
|
|
can_sign: boolean;
|
|
signature?: ContractSignature;
|
|
}
|
|
|
|
// --- Time Blocking Types ---
|
|
|
|
export type BlockType = 'HARD' | 'SOFT';
|
|
export type BlockPurpose = 'CLOSURE' | 'UNAVAILABLE' | 'BUSINESS_HOURS' | 'OTHER';
|
|
export type RecurrenceType = 'NONE' | 'WEEKLY' | 'MONTHLY' | 'YEARLY' | 'HOLIDAY';
|
|
export type TimeBlockLevel = 'business' | 'resource';
|
|
|
|
export type HolidayType = 'FIXED' | 'FLOATING' | 'CALCULATED';
|
|
|
|
export interface Holiday {
|
|
code: string;
|
|
name: string;
|
|
country: string;
|
|
holiday_type?: HolidayType;
|
|
month?: number;
|
|
day?: number;
|
|
week_of_month?: number;
|
|
day_of_week?: number;
|
|
calculation_rule?: string;
|
|
is_active?: boolean;
|
|
next_occurrence?: string; // ISO date string
|
|
}
|
|
|
|
export interface RecurrencePattern {
|
|
days_of_week?: number[]; // 0=Mon, 6=Sun (for WEEKLY)
|
|
days_of_month?: number[]; // 1-31 (for MONTHLY)
|
|
month?: number; // 1-12 (for YEARLY)
|
|
day?: number; // 1-31 (for YEARLY)
|
|
holiday_code?: string; // holiday code (for HOLIDAY)
|
|
}
|
|
|
|
export interface TimeBlock {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
resource?: string | null; // Resource ID or null for business-level
|
|
resource_name?: string;
|
|
level: TimeBlockLevel;
|
|
block_type: BlockType;
|
|
purpose: BlockPurpose;
|
|
recurrence_type: RecurrenceType;
|
|
start_date?: string; // ISO date string (for NONE type)
|
|
end_date?: string; // ISO date string (for NONE type)
|
|
all_day: boolean;
|
|
start_time?: string; // HH:MM:SS (if not all_day)
|
|
end_time?: string; // HH:MM:SS (if not all_day)
|
|
recurrence_pattern?: RecurrencePattern;
|
|
pattern_display?: string; // Human-readable pattern description
|
|
holiday_name?: string; // Holiday name if HOLIDAY type
|
|
recurrence_start?: string; // ISO date string
|
|
recurrence_end?: string; // ISO date string
|
|
is_active: boolean;
|
|
created_by?: string;
|
|
created_by_name?: string;
|
|
conflict_count?: number;
|
|
created_at: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export type ApprovalStatus = 'APPROVED' | 'PENDING' | 'DENIED';
|
|
|
|
export interface TimeBlockListItem {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
resource?: string | null;
|
|
resource_name?: string;
|
|
level: TimeBlockLevel;
|
|
block_type: BlockType;
|
|
purpose: BlockPurpose;
|
|
recurrence_type: RecurrenceType;
|
|
start_date?: string;
|
|
end_date?: string;
|
|
all_day?: boolean;
|
|
start_time?: string;
|
|
end_time?: string;
|
|
recurrence_pattern?: RecurrencePattern;
|
|
recurrence_start?: string;
|
|
recurrence_end?: string;
|
|
pattern_display?: string;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
approval_status?: ApprovalStatus;
|
|
reviewed_by?: number;
|
|
reviewed_by_name?: string;
|
|
reviewed_at?: string;
|
|
review_notes?: string;
|
|
created_by_name?: string;
|
|
}
|
|
|
|
export interface BlockedDate {
|
|
date: string; // ISO date string
|
|
block_type: BlockType;
|
|
purpose: BlockPurpose;
|
|
title: string;
|
|
resource_id: string | null;
|
|
all_day: boolean;
|
|
start_time: string | null;
|
|
end_time: string | null;
|
|
time_block_id: string;
|
|
}
|
|
|
|
export interface TimeBlockConflict {
|
|
event_id: string;
|
|
title: string;
|
|
start_time: string;
|
|
end_time: string;
|
|
}
|
|
|
|
export interface TimeBlockConflictCheck {
|
|
has_conflicts: boolean;
|
|
conflict_count: number;
|
|
conflicts: TimeBlockConflict[];
|
|
}
|
|
|
|
export interface MyBlocksResponse {
|
|
business_blocks: TimeBlockListItem[];
|
|
my_blocks: TimeBlockListItem[];
|
|
resource_id: string | null;
|
|
resource_name: string | null;
|
|
can_self_approve: boolean;
|
|
} |