@gymspace/sdk 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.ts CHANGED
@@ -17,6 +17,11 @@ import {
17
17
  export class ApiClient {
18
18
  private axiosInstance: AxiosInstance;
19
19
  private config: GymSpaceConfig;
20
+ private refreshPromise: Promise<any> | null = null; // To prevent concurrent refresh attempts
21
+
22
+ // Callbacks for token updates and auth errors
23
+ public onTokensUpdated?: (accessToken: string, refreshToken: string) => void;
24
+ public onAuthError?: (error: any) => void;
20
25
 
21
26
  constructor(config: GymSpaceConfig) {
22
27
  this.config = config;
@@ -42,6 +47,11 @@ export class ApiClient {
42
47
  config.headers['Authorization'] = `Bearer ${this.config.apiKey}`;
43
48
  }
44
49
 
50
+ // Add refresh token header if available
51
+ if (this.config.refreshToken && config.headers) {
52
+ config.headers['X-Refresh-Token'] = this.config.refreshToken;
53
+ }
54
+
45
55
  return config;
46
56
  },
47
57
  (error) => {
@@ -49,33 +59,91 @@ export class ApiClient {
49
59
  },
50
60
  );
51
61
 
52
- // Response interceptor
62
+ // Response interceptor with automatic token refresh
53
63
  this.axiosInstance.interceptors.response.use(
54
- (response) => response,
55
- (error: AxiosError) => {
64
+ (response) => {
65
+ // Check for new tokens in response headers
66
+ const newAccessToken = response.headers['x-new-access-token'];
67
+ const newRefreshToken = response.headers['x-new-refresh-token'];
68
+
69
+ if (newAccessToken && newRefreshToken) {
70
+ this.setTokens(newAccessToken, newRefreshToken);
71
+ // Emit token update event if needed
72
+ this.onTokensUpdated?.(newAccessToken, newRefreshToken);
73
+ }
74
+
75
+ return response;
76
+ },
77
+ async (error: AxiosError) => {
78
+ const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
79
+
80
+ // If it's a 401 error and we haven't retried yet, try to refresh the token
81
+ if (error.response?.status === 401 && !originalRequest._retry && this.config.refreshToken) {
82
+ originalRequest._retry = true;
83
+
84
+ try {
85
+ // Prevent concurrent refresh attempts
86
+ if (!this.refreshPromise) {
87
+ this.refreshPromise = this.refreshAccessToken();
88
+ }
89
+
90
+ const newTokens = await this.refreshPromise;
91
+ this.refreshPromise = null;
92
+
93
+ if (newTokens) {
94
+ // Update the original request with new token
95
+ originalRequest.headers = originalRequest.headers || {};
96
+ originalRequest.headers['Authorization'] = `Bearer ${newTokens.access_token}`;
97
+
98
+ // Retry the original request
99
+ return this.axiosInstance(originalRequest);
100
+ }
101
+ } catch (refreshError) {
102
+ this.refreshPromise = null;
103
+ // If refresh fails, clear tokens and emit error
104
+ this.clearAuth();
105
+ this.onAuthError?.(refreshError);
106
+ }
107
+ }
108
+
56
109
  throw this.handleError(error);
57
110
  },
58
111
  );
59
112
  }
60
113
 
114
+ /**
115
+ * Refresh the access token using the stored refresh token
116
+ */
117
+ private async refreshAccessToken(): Promise<any> {
118
+ if (!this.config.refreshToken) {
119
+ throw new AuthenticationError('No refresh token available');
120
+ }
121
+
122
+ try {
123
+ const response = await axios.post(
124
+ `${this.config.baseURL}/auth/refresh`,
125
+ { refresh_token: this.config.refreshToken },
126
+ {
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ },
130
+ timeout: this.config.timeout || 30000,
131
+ }
132
+ );
133
+
134
+ const newTokens = response.data;
135
+ this.setTokens(newTokens.access_token, newTokens.refresh_token);
136
+
137
+ return newTokens;
138
+ } catch (error) {
139
+ throw new AuthenticationError('Failed to refresh token');
140
+ }
141
+ }
142
+
61
143
  private handleError(error: AxiosError): GymSpaceError {
62
144
  const requestPath = error.config?.url || 'unknown';
63
145
  const method = error.config?.method?.toUpperCase() || 'unknown';
64
146
 
65
- console.error(
66
- 'API Error:',
67
- JSON.stringify(
68
- {
69
- method,
70
- path: requestPath,
71
- message: error.message,
72
- baseURL: error.config?.baseURL,
73
- },
74
- null,
75
- 3,
76
- ),
77
- );
78
-
79
147
  if (!error.response) {
80
148
  console.error('Network Error:', {
81
149
  method,
@@ -150,6 +150,26 @@ export interface CurrentSessionResponse {
150
150
  timezone: string;
151
151
  settings?: Record<string, any>;
152
152
  };
153
+ subscription?: {
154
+ id: string;
155
+ organizationId: string;
156
+ subscriptionPlanId: string;
157
+ subscriptionPlan?: {
158
+ id: string;
159
+ name: string;
160
+ price: any;
161
+ billingFrequency: string;
162
+ maxGyms: number;
163
+ maxClientsPerGym: number;
164
+ maxUsersPerGym: number;
165
+ features: any;
166
+ description?: string;
167
+ };
168
+ status: string;
169
+ startDate: Date;
170
+ endDate: Date;
171
+ isActive: boolean;
172
+ };
153
173
  permissions: string[];
154
174
  isAuthenticated: boolean;
155
175
  }
@@ -80,14 +80,42 @@ export interface Client {
80
80
  };
81
81
  }
82
82
 
83
+ export interface ClientStat {
84
+ key: string;
85
+ name: string;
86
+ description: string;
87
+ category: 'activity' | 'contracts' | 'general';
88
+ value: any;
89
+ }
90
+
83
91
  export interface ClientStats {
84
- totalContracts: number;
85
- activeContracts: number;
86
- totalCheckIns: number;
87
- checkInsThisMonth: number;
88
- lastCheckIn?: string;
89
- totalEvaluations: number;
90
- lastEvaluation?: string;
92
+ client: {
93
+ id: string;
94
+ name: string;
95
+ email: string | null;
96
+ status: string;
97
+ registrationDate: string;
98
+ };
99
+ activity: {
100
+ totalCheckIns: number;
101
+ monthlyCheckIns: number;
102
+ lastCheckIn: string | null;
103
+ };
104
+ contracts: {
105
+ active: number;
106
+ totalSpent: number;
107
+ };
108
+ membershipHistory: Array<{
109
+ id: string;
110
+ status: string;
111
+ startDate: string;
112
+ endDate: string | null;
113
+ gymMembershipPlan: {
114
+ id: string;
115
+ name: string;
116
+ basePrice: number;
117
+ };
118
+ }>;
91
119
  }
92
120
 
93
121
  export interface SearchClientsParams extends PaginationQueryDto {
@@ -4,6 +4,7 @@ import { PaginationQueryDto } from '../types';
4
4
  export interface CreateContractDto {
5
5
  gymClientId: string;
6
6
  gymMembershipPlanId: string;
7
+ paymentMethodId: string;
7
8
  startDate: string;
8
9
  discountPercentage?: number;
9
10
  customPrice?: number;
@@ -15,7 +16,12 @@ export interface RenewContractDto {
15
16
  startDate?: string;
16
17
  discountPercentage?: number;
17
18
  customPrice?: number;
18
- metadata?: Record<string, any>;
19
+ // Payment method ID (UUID) - uses the existing payment method if not provided
20
+ paymentMethodId?: string;
21
+ applyAtEndOfContract?: boolean;
22
+ notes?: string;
23
+ contractDocumentId?: string;
24
+ receiptIds?: string[];
19
25
  }
20
26
 
21
27
  export interface FreezeContractDto {
@@ -30,6 +36,8 @@ export interface Contract {
30
36
  contractNumber: string;
31
37
  gymClientId: string;
32
38
  gymMembershipPlanId: string;
39
+ paymentMethodId?: string;
40
+ parentId?: string; // Reference to parent contract for renewals
33
41
  startDate: string;
34
42
  endDate: string;
35
43
  status: ContractStatus;
@@ -54,8 +62,22 @@ export interface Contract {
54
62
  basePrice?: number;
55
63
  durationMonths?: number;
56
64
  };
65
+ paymentMethod?: {
66
+ id: string;
67
+ name: string;
68
+ description?: string;
69
+ code: string;
70
+ enabled: boolean;
71
+ };
72
+ renewals?: Contract[]; // Renewal contracts for this contract
57
73
  }
58
74
 
59
75
  export interface GetContractsParams extends PaginationQueryDto {
60
76
  status?: ContractStatus;
77
+ clientName?: string;
78
+ clientId?: string;
79
+ startDateFrom?: string;
80
+ startDateTo?: string;
81
+ endDateFrom?: string;
82
+ endDateTo?: string;
61
83
  }
@@ -5,21 +5,49 @@ export interface DashboardStats {
5
5
  activeClients: number;
6
6
  totalContracts: number;
7
7
  activeContracts: number;
8
- monthlyRevenue: number;
8
+ monthlyRevenue: number; // Deprecated, use getContractsRevenue instead
9
9
  todayCheckIns: number;
10
10
  expiringContractsCount: number;
11
11
  newClientsThisMonth: number;
12
12
  }
13
13
 
14
- export type ActivityType = 'check_in' | 'new_client' | 'new_contract' | 'contract_expired';
14
+ export interface ContractsRevenue {
15
+ totalRevenue: number;
16
+ contractCount: number;
17
+ averageRevenue: number;
18
+ startDate: string;
19
+ endDate: string;
20
+ }
15
21
 
16
- export interface RecentActivity {
17
- id: string;
18
- type: ActivityType;
19
- description: string;
20
- timestamp: string;
21
- clientName?: string;
22
- clientId?: string;
22
+ export interface SalesRevenue {
23
+ totalRevenue: number;
24
+ salesCount: number;
25
+ averageRevenue: number;
26
+ startDate: string;
27
+ endDate: string;
28
+ }
29
+
30
+ export interface Debts {
31
+ totalDebt: number;
32
+ clientsWithDebt: number;
33
+ averageDebt: number;
34
+ startDate: string;
35
+ endDate: string;
36
+ }
37
+
38
+ export interface CheckIns {
39
+ totalCheckIns: number;
40
+ uniqueClients: number;
41
+ averagePerDay: number;
42
+ startDate: string;
43
+ endDate: string;
44
+ }
45
+
46
+ export interface NewClients {
47
+ totalNewClients: number;
48
+ averagePerDay: number;
49
+ startDate: string;
50
+ endDate: string;
23
51
  }
24
52
 
25
53
  export interface ExpiringContract {
@@ -29,4 +57,9 @@ export interface ExpiringContract {
29
57
  planName: string;
30
58
  endDate: string;
31
59
  daysRemaining: number;
60
+ }
61
+
62
+ export interface DateRangeParams {
63
+ startDate?: string;
64
+ endDate?: string;
32
65
  }
@@ -57,11 +57,63 @@ export interface Gym {
57
57
  capacity?: number;
58
58
  amenities?: GymAmenities;
59
59
  settings?: GymSettings;
60
+ schedule?: GymSchedule;
61
+ socialMedia?: GymSocialMedia;
60
62
  isActive: boolean;
61
63
  createdAt: string;
62
64
  updatedAt: string;
63
65
  }
64
66
 
67
+ export interface TimeSlot {
68
+ open: string;
69
+ close: string;
70
+ }
71
+
72
+ export interface DaySchedule {
73
+ isOpen: boolean;
74
+ slots?: TimeSlot[];
75
+ }
76
+
77
+ export interface GymSchedule {
78
+ monday?: DaySchedule;
79
+ tuesday?: DaySchedule;
80
+ wednesday?: DaySchedule;
81
+ thursday?: DaySchedule;
82
+ friday?: DaySchedule;
83
+ saturday?: DaySchedule;
84
+ sunday?: DaySchedule;
85
+ }
86
+
87
+ export interface GymSocialMedia {
88
+ facebook?: string;
89
+ instagram?: string;
90
+ whatsapp?: string;
91
+ twitter?: string;
92
+ linkedin?: string;
93
+ youtube?: string;
94
+ tiktok?: string;
95
+ }
96
+
97
+ export interface UpdateGymScheduleDto {
98
+ monday?: DaySchedule;
99
+ tuesday?: DaySchedule;
100
+ wednesday?: DaySchedule;
101
+ thursday?: DaySchedule;
102
+ friday?: DaySchedule;
103
+ saturday?: DaySchedule;
104
+ sunday?: DaySchedule;
105
+ }
106
+
107
+ export interface UpdateGymSocialMediaDto {
108
+ facebook?: string;
109
+ instagram?: string;
110
+ whatsapp?: string;
111
+ twitter?: string;
112
+ linkedin?: string;
113
+ youtube?: string;
114
+ tiktok?: string;
115
+ }
116
+
65
117
  export interface GymStats {
66
118
  totalClients: number;
67
119
  activeClients: number;
@@ -17,6 +17,8 @@ export * from './products';
17
17
  export * from './sales';
18
18
  export * from './suppliers';
19
19
  export * from './users';
20
+ export * from './subscriptions';
21
+ export * from './payment-methods';
20
22
 
21
23
  export interface ApiResponse<T> {
22
24
  data: T;
@@ -1,8 +1,5 @@
1
1
  export interface UpdateOrganizationDto {
2
- country?: string;
3
- currency?: string;
4
- timezone?: string;
5
- settings?: Record<string, any>;
2
+ name: string;
6
3
  }
7
4
 
8
5
  export interface Organization {
@@ -0,0 +1,41 @@
1
+ import { PaginationQueryDto } from '../types';
2
+
3
+ export interface CreatePaymentMethodDto {
4
+ name: string;
5
+ description?: string;
6
+ code: string;
7
+ enabled?: boolean;
8
+ metadata?: Record<string, any>;
9
+ }
10
+
11
+ export interface UpdatePaymentMethodDto {
12
+ name?: string;
13
+ description?: string;
14
+ code?: string;
15
+ enabled?: boolean;
16
+ metadata?: Record<string, any>;
17
+ }
18
+
19
+ export interface PaymentMethod {
20
+ id: string;
21
+ gymId: string;
22
+ name: string;
23
+ description?: string;
24
+ code: string;
25
+ enabled: boolean;
26
+ metadata?: Record<string, any>;
27
+ createdAt: string;
28
+ updatedAt: string;
29
+ }
30
+
31
+ export interface SearchPaymentMethodsParams extends PaginationQueryDto {
32
+ search?: string;
33
+ enabledOnly?: boolean;
34
+ code?: string;
35
+ }
36
+
37
+ export interface PaymentMethodStats {
38
+ totalPaymentMethods: number;
39
+ enabledPaymentMethods: number;
40
+ disabledPaymentMethods: number;
41
+ }
@@ -42,9 +42,20 @@ export interface CreateProductDto {
42
42
  description?: string;
43
43
  price: number;
44
44
  stock?: number;
45
+ minStock?: number;
46
+ maxStock?: number;
45
47
  categoryId?: string;
46
48
  imageId?: string;
47
49
  status?: 'active' | 'inactive';
50
+ trackInventory?: 'none' | 'simple' | 'advanced' | 'capacity';
51
+ }
52
+
53
+ export interface CreateServiceDto {
54
+ name: string;
55
+ description?: string;
56
+ price: number;
57
+ categoryId?: string;
58
+ imageId?: string;
48
59
  }
49
60
 
50
61
  export interface UpdateProductDto {
@@ -52,13 +63,19 @@ export interface UpdateProductDto {
52
63
  description?: string;
53
64
  price?: number;
54
65
  stock?: number;
66
+ minStock?: number;
67
+ maxStock?: number;
55
68
  categoryId?: string;
56
69
  imageId?: string;
57
70
  status?: 'active' | 'inactive';
71
+ trackInventory?: 'none' | 'simple' | 'advanced' | 'capacity';
58
72
  }
59
73
 
60
74
  export interface UpdateStockDto {
61
75
  quantity: number;
76
+ notes?: string;
77
+ supplierId?: string;
78
+ fileId?: string;
62
79
  }
63
80
 
64
81
  export interface Product {
@@ -68,9 +85,13 @@ export interface Product {
68
85
  name: string;
69
86
  description?: string;
70
87
  price: number;
71
- stock: number;
88
+ stock: number | null;
89
+ minStock?: number | null;
90
+ maxStock?: number | null;
72
91
  imageId?: string;
73
92
  status: 'active' | 'inactive';
93
+ type?: 'Product' | 'Service';
94
+ trackInventory?: 'none' | 'simple' | 'advanced' | 'capacity';
74
95
  createdAt: string;
75
96
  updatedAt: string;
76
97
  category?: ProductCategory;
@@ -92,8 +113,34 @@ export interface Product {
92
113
  export interface SearchProductsParams extends PaginationQueryDto {
93
114
  search?: string;
94
115
  categoryId?: string;
116
+ type?: 'Product' | 'Service';
95
117
  status?: 'active' | 'inactive';
96
118
  inStock?: boolean;
97
119
  minPrice?: number;
98
120
  maxPrice?: number;
121
+ }
122
+
123
+ // Stock Movement Models
124
+ export interface StockMovement {
125
+ id: string;
126
+ productId: string;
127
+ gymId: string;
128
+ type: 'manual_entry' | 'sale' | 'return' | 'adjustment' | 'initial_stock';
129
+ quantity: number;
130
+ previousStock?: number;
131
+ newStock?: number;
132
+ notes?: string;
133
+ supplierId?: string;
134
+ fileId?: string;
135
+ createdByUserId: string;
136
+ createdAt: string;
137
+ product?: Product;
138
+ supplier?: {
139
+ id: string;
140
+ name: string;
141
+ };
142
+ createdBy?: {
143
+ id: string;
144
+ name: string;
145
+ };
99
146
  }
@@ -31,15 +31,21 @@ export interface SaleItem {
31
31
  // Sale Models
32
32
  export interface CreateSaleDto {
33
33
  items: SaleItemDto[];
34
+ customerId?: string;
34
35
  customerName?: string;
35
36
  notes?: string;
37
+ fileIds?: string[];
36
38
  paymentStatus?: 'paid' | 'unpaid';
39
+ paymentMethodId?: string;
37
40
  }
38
41
 
39
42
  export interface UpdateSaleDto {
43
+ customerId?: string;
40
44
  customerName?: string;
41
45
  notes?: string;
46
+ fileIds?: string[];
42
47
  paymentStatus?: 'paid' | 'unpaid';
48
+ paymentMethodId?: string;
43
49
  }
44
50
 
45
51
  export interface UpdatePaymentStatusDto {
@@ -49,15 +55,31 @@ export interface UpdatePaymentStatusDto {
49
55
  export interface Sale {
50
56
  id: string;
51
57
  gymId: string;
58
+ customerId?: string;
52
59
  saleNumber: string;
53
60
  total: number;
54
61
  paymentStatus: 'paid' | 'unpaid';
62
+ paymentMethodId?: string;
55
63
  saleDate: string;
56
64
  customerName?: string;
57
65
  notes?: string;
66
+ fileIds?: string[];
58
67
  createdAt: string;
59
68
  updatedAt: string;
60
69
  saleItems?: SaleItem[];
70
+ customer?: {
71
+ id: string;
72
+ clientNumber: string;
73
+ name: string;
74
+ phone?: string;
75
+ email?: string;
76
+ };
77
+ paymentMethod?: {
78
+ id: string;
79
+ name: string;
80
+ code: string;
81
+ description?: string;
82
+ };
61
83
  createdBy?: {
62
84
  id: string;
63
85
  name: string;
@@ -75,6 +97,7 @@ export interface Sale {
75
97
 
76
98
  export interface SearchSalesParams extends PaginationQueryDto {
77
99
  customerName?: string;
100
+ customerId?: string;
78
101
  paymentStatus?: 'paid' | 'unpaid';
79
102
  startDate?: string;
80
103
  endDate?: string;
@@ -105,4 +128,28 @@ export interface TopSellingProduct {
105
128
  };
106
129
  totalQuantity: number;
107
130
  totalRevenue: number;
131
+ }
132
+
133
+ export interface CustomerSalesReport {
134
+ summary: {
135
+ totalCustomers: number;
136
+ totalSales: number;
137
+ totalRevenue: number;
138
+ };
139
+ customers: Array<{
140
+ customer: {
141
+ id: string | null;
142
+ clientNumber?: string;
143
+ name: string;
144
+ phone?: string;
145
+ email?: string;
146
+ };
147
+ totalSales: number;
148
+ totalRevenue: number;
149
+ sales: Array<{
150
+ id: string;
151
+ total: number;
152
+ saleDate: string;
153
+ }>;
154
+ }>;
108
155
  }