Start integrating the onboarding flow with backend 6
This commit is contained in:
87
frontend/src/api/services/auth.ts
Normal file
87
frontend/src/api/services/auth.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Auth Service - Mirror backend auth endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
UserRegistration,
|
||||
UserLogin,
|
||||
TokenResponse,
|
||||
RefreshTokenRequest,
|
||||
PasswordChange,
|
||||
PasswordReset,
|
||||
UserResponse,
|
||||
UserUpdate,
|
||||
TokenVerificationResponse,
|
||||
AuthHealthResponse,
|
||||
} from '../types/auth';
|
||||
|
||||
export class AuthService {
|
||||
private readonly baseUrl = '/auth';
|
||||
|
||||
async register(userData: UserRegistration): Promise<TokenResponse> {
|
||||
return apiClient.post<TokenResponse>(`${this.baseUrl}/register`, userData);
|
||||
}
|
||||
|
||||
async login(loginData: UserLogin): Promise<TokenResponse> {
|
||||
return apiClient.post<TokenResponse>(`${this.baseUrl}/login`, loginData);
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<TokenResponse> {
|
||||
const refreshData: RefreshTokenRequest = { refresh_token: refreshToken };
|
||||
return apiClient.post<TokenResponse>(`${this.baseUrl}/refresh`, refreshData);
|
||||
}
|
||||
|
||||
async verifyToken(token?: string): Promise<TokenVerificationResponse> {
|
||||
// If token is provided, temporarily set it; otherwise use current token
|
||||
const currentToken = apiClient.getAuthToken();
|
||||
if (token && token !== currentToken) {
|
||||
apiClient.setAuthToken(token);
|
||||
}
|
||||
|
||||
const response = await apiClient.post<TokenVerificationResponse>(`${this.baseUrl}/verify`);
|
||||
|
||||
// Restore original token if we temporarily changed it
|
||||
if (token && token !== currentToken) {
|
||||
apiClient.setAuthToken(currentToken);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async logout(refreshToken: string): Promise<{ message: string }> {
|
||||
const refreshData: RefreshTokenRequest = { refresh_token: refreshToken };
|
||||
return apiClient.post<{ message: string }>(`${this.baseUrl}/logout`, refreshData);
|
||||
}
|
||||
|
||||
async changePassword(passwordData: PasswordChange): Promise<{ message: string }> {
|
||||
return apiClient.post<{ message: string }>(`${this.baseUrl}/change-password`, passwordData);
|
||||
}
|
||||
|
||||
async resetPassword(resetData: PasswordReset): Promise<{ message: string }> {
|
||||
return apiClient.post<{ message: string }>(`${this.baseUrl}/reset-password`, resetData);
|
||||
}
|
||||
|
||||
async getProfile(): Promise<UserResponse> {
|
||||
return apiClient.get<UserResponse>(`${this.baseUrl}/profile`);
|
||||
}
|
||||
|
||||
async updateProfile(updateData: UserUpdate): Promise<UserResponse> {
|
||||
return apiClient.put<UserResponse>(`${this.baseUrl}/profile`, updateData);
|
||||
}
|
||||
|
||||
async verifyEmail(
|
||||
userId: string,
|
||||
verificationToken: string
|
||||
): Promise<{ message: string }> {
|
||||
return apiClient.post<{ message: string }>(`${this.baseUrl}/verify-email`, {
|
||||
user_id: userId,
|
||||
verification_token: verificationToken,
|
||||
});
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<AuthHealthResponse> {
|
||||
return apiClient.get<AuthHealthResponse>(`${this.baseUrl}/health`);
|
||||
}
|
||||
}
|
||||
|
||||
export const authService = new AuthService();
|
||||
94
frontend/src/api/services/classification.ts
Normal file
94
frontend/src/api/services/classification.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Classification Service - Mirror backend classification endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
ProductClassificationRequest,
|
||||
BatchClassificationRequest,
|
||||
ProductSuggestionResponse,
|
||||
BusinessModelAnalysisResponse,
|
||||
ClassificationApprovalRequest,
|
||||
ClassificationApprovalResponse,
|
||||
} from '../types/classification';
|
||||
|
||||
export class ClassificationService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
async classifyProduct(
|
||||
tenantId: string,
|
||||
classificationData: ProductClassificationRequest
|
||||
): Promise<ProductSuggestionResponse> {
|
||||
return apiClient.post<ProductSuggestionResponse>(
|
||||
`${this.baseUrl}/${tenantId}/classification/classify-product`,
|
||||
classificationData
|
||||
);
|
||||
}
|
||||
|
||||
async classifyProductsBatch(
|
||||
tenantId: string,
|
||||
batchData: BatchClassificationRequest
|
||||
): Promise<ProductSuggestionResponse[]> {
|
||||
return apiClient.post<ProductSuggestionResponse[]>(
|
||||
`${this.baseUrl}/${tenantId}/classification/classify-batch`,
|
||||
batchData
|
||||
);
|
||||
}
|
||||
|
||||
async getBusinessModelAnalysis(tenantId: string): Promise<BusinessModelAnalysisResponse> {
|
||||
return apiClient.get<BusinessModelAnalysisResponse>(
|
||||
`${this.baseUrl}/${tenantId}/classification/business-model-analysis`
|
||||
);
|
||||
}
|
||||
|
||||
async approveClassification(
|
||||
tenantId: string,
|
||||
approvalData: ClassificationApprovalRequest
|
||||
): Promise<ClassificationApprovalResponse> {
|
||||
return apiClient.post<ClassificationApprovalResponse>(
|
||||
`${this.baseUrl}/${tenantId}/classification/approve-suggestion`,
|
||||
approvalData
|
||||
);
|
||||
}
|
||||
|
||||
async getPendingSuggestions(tenantId: string): Promise<ProductSuggestionResponse[]> {
|
||||
return apiClient.get<ProductSuggestionResponse[]>(
|
||||
`${this.baseUrl}/${tenantId}/classification/pending-suggestions`
|
||||
);
|
||||
}
|
||||
|
||||
async getSuggestionHistory(
|
||||
tenantId: string,
|
||||
limit: number = 50,
|
||||
offset: number = 0
|
||||
): Promise<{
|
||||
items: ProductSuggestionResponse[];
|
||||
total: number;
|
||||
}> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('limit', limit.toString());
|
||||
queryParams.append('offset', offset.toString());
|
||||
|
||||
return apiClient.get(
|
||||
`${this.baseUrl}/${tenantId}/classification/suggestion-history?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async deleteSuggestion(tenantId: string, suggestionId: string): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(
|
||||
`${this.baseUrl}/${tenantId}/classification/suggestions/${suggestionId}`
|
||||
);
|
||||
}
|
||||
|
||||
async updateSuggestion(
|
||||
tenantId: string,
|
||||
suggestionId: string,
|
||||
updateData: Partial<ProductSuggestionResponse>
|
||||
): Promise<ProductSuggestionResponse> {
|
||||
return apiClient.put<ProductSuggestionResponse>(
|
||||
`${this.baseUrl}/${tenantId}/classification/suggestions/${suggestionId}`,
|
||||
updateData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const classificationService = new ClassificationService();
|
||||
99
frontend/src/api/services/dataImport.ts
Normal file
99
frontend/src/api/services/dataImport.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Data Import Service - Mirror backend data import endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
ImportValidationRequest,
|
||||
ImportValidationResponse,
|
||||
ImportProcessRequest,
|
||||
ImportProcessResponse,
|
||||
ImportStatusResponse
|
||||
} from '../types/dataImport';
|
||||
|
||||
export class DataImportService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
async validateJsonData(
|
||||
tenantId: string,
|
||||
data: any
|
||||
): Promise<ImportValidationResponse> {
|
||||
return apiClient.post<ImportValidationResponse>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/validate-json`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
async validateCsvFile(
|
||||
tenantId: string,
|
||||
file: File
|
||||
): Promise<ImportValidationResponse> {
|
||||
return apiClient.uploadFile<ImportValidationResponse>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/validate-csv`,
|
||||
file
|
||||
);
|
||||
}
|
||||
|
||||
async importJsonData(
|
||||
tenantId: string,
|
||||
data: any,
|
||||
options?: {
|
||||
skip_validation?: boolean;
|
||||
chunk_size?: number;
|
||||
}
|
||||
): Promise<ImportProcessResponse> {
|
||||
const payload = {
|
||||
...data,
|
||||
options,
|
||||
};
|
||||
return apiClient.post<ImportProcessResponse>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/json`,
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
async importCsvFile(
|
||||
tenantId: string,
|
||||
file: File,
|
||||
options?: {
|
||||
skip_validation?: boolean;
|
||||
chunk_size?: number;
|
||||
}
|
||||
): Promise<ImportProcessResponse> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
if (options) {
|
||||
formData.append('options', JSON.stringify(options));
|
||||
}
|
||||
|
||||
return apiClient.uploadFile<ImportProcessResponse>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/csv`,
|
||||
formData
|
||||
);
|
||||
}
|
||||
|
||||
async getImportStatus(
|
||||
tenantId: string,
|
||||
importId: string
|
||||
): Promise<ImportStatusResponse> {
|
||||
return apiClient.get<ImportStatusResponse>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/${importId}/status`
|
||||
);
|
||||
}
|
||||
|
||||
async cancelImport(
|
||||
tenantId: string,
|
||||
importId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
return apiClient.post<{ success: boolean; message: string }>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/${importId}/cancel`
|
||||
);
|
||||
}
|
||||
|
||||
async getImportHistory(tenantId: string): Promise<ImportStatusResponse[]> {
|
||||
return apiClient.get<ImportStatusResponse[]>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/history`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const dataImportService = new DataImportService();
|
||||
273
frontend/src/api/services/foodSafety.ts
Normal file
273
frontend/src/api/services/foodSafety.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* Food Safety Service - Mirror backend food safety endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
FoodSafetyComplianceCreate,
|
||||
FoodSafetyComplianceUpdate,
|
||||
FoodSafetyComplianceResponse,
|
||||
TemperatureLogCreate,
|
||||
BulkTemperatureLogCreate,
|
||||
TemperatureLogResponse,
|
||||
FoodSafetyAlertCreate,
|
||||
FoodSafetyAlertUpdate,
|
||||
FoodSafetyAlertResponse,
|
||||
FoodSafetyFilter,
|
||||
TemperatureMonitoringFilter,
|
||||
FoodSafetyMetrics,
|
||||
TemperatureAnalytics,
|
||||
FoodSafetyDashboard,
|
||||
} from '../types/foodSafety';
|
||||
import { PaginatedResponse } from '../types/inventory';
|
||||
|
||||
export class FoodSafetyService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
// Compliance Management
|
||||
async createComplianceRecord(
|
||||
tenantId: string,
|
||||
complianceData: FoodSafetyComplianceCreate
|
||||
): Promise<FoodSafetyComplianceResponse> {
|
||||
return apiClient.post<FoodSafetyComplianceResponse>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/compliance`,
|
||||
complianceData
|
||||
);
|
||||
}
|
||||
|
||||
async getComplianceRecord(
|
||||
tenantId: string,
|
||||
recordId: string
|
||||
): Promise<FoodSafetyComplianceResponse> {
|
||||
return apiClient.get<FoodSafetyComplianceResponse>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/compliance/${recordId}`
|
||||
);
|
||||
}
|
||||
|
||||
async getComplianceRecords(
|
||||
tenantId: string,
|
||||
filter?: FoodSafetyFilter
|
||||
): Promise<PaginatedResponse<FoodSafetyComplianceResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (filter?.compliance_type) queryParams.append('compliance_type', filter.compliance_type);
|
||||
if (filter?.status) queryParams.append('status', filter.status);
|
||||
if (filter?.ingredient_id) queryParams.append('ingredient_id', filter.ingredient_id);
|
||||
if (filter?.resolved !== undefined) queryParams.append('resolved', filter.resolved.toString());
|
||||
if (filter?.date_range?.start) queryParams.append('start_date', filter.date_range.start);
|
||||
if (filter?.date_range?.end) queryParams.append('end_date', filter.date_range.end);
|
||||
if (filter?.limit !== undefined) queryParams.append('limit', filter.limit.toString());
|
||||
if (filter?.offset !== undefined) queryParams.append('offset', filter.offset.toString());
|
||||
if (filter?.order_by) queryParams.append('order_by', filter.order_by);
|
||||
if (filter?.order_direction) queryParams.append('order_direction', filter.order_direction);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/food-safety/compliance?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/food-safety/compliance`;
|
||||
|
||||
return apiClient.get<PaginatedResponse<FoodSafetyComplianceResponse>>(url);
|
||||
}
|
||||
|
||||
async updateComplianceRecord(
|
||||
tenantId: string,
|
||||
recordId: string,
|
||||
updateData: FoodSafetyComplianceUpdate
|
||||
): Promise<FoodSafetyComplianceResponse> {
|
||||
return apiClient.put<FoodSafetyComplianceResponse>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/compliance/${recordId}`,
|
||||
updateData
|
||||
);
|
||||
}
|
||||
|
||||
async deleteComplianceRecord(
|
||||
tenantId: string,
|
||||
recordId: string
|
||||
): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/compliance/${recordId}`
|
||||
);
|
||||
}
|
||||
|
||||
// Temperature Monitoring
|
||||
async createTemperatureLog(
|
||||
tenantId: string,
|
||||
logData: TemperatureLogCreate
|
||||
): Promise<TemperatureLogResponse> {
|
||||
return apiClient.post<TemperatureLogResponse>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/temperature-logs`,
|
||||
logData
|
||||
);
|
||||
}
|
||||
|
||||
async createBulkTemperatureLogs(
|
||||
tenantId: string,
|
||||
bulkData: BulkTemperatureLogCreate
|
||||
): Promise<{
|
||||
created_count: number;
|
||||
failed_count: number;
|
||||
errors?: string[];
|
||||
}> {
|
||||
return apiClient.post(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/temperature-logs/bulk`,
|
||||
bulkData
|
||||
);
|
||||
}
|
||||
|
||||
async getTemperatureLogs(
|
||||
tenantId: string,
|
||||
filter?: TemperatureMonitoringFilter
|
||||
): Promise<PaginatedResponse<TemperatureLogResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (filter?.location) queryParams.append('location', filter.location);
|
||||
if (filter?.equipment_id) queryParams.append('equipment_id', filter.equipment_id);
|
||||
if (filter?.temperature_range?.min !== undefined)
|
||||
queryParams.append('min_temperature', filter.temperature_range.min.toString());
|
||||
if (filter?.temperature_range?.max !== undefined)
|
||||
queryParams.append('max_temperature', filter.temperature_range.max.toString());
|
||||
if (filter?.alert_triggered !== undefined)
|
||||
queryParams.append('alert_triggered', filter.alert_triggered.toString());
|
||||
if (filter?.date_range?.start) queryParams.append('start_date', filter.date_range.start);
|
||||
if (filter?.date_range?.end) queryParams.append('end_date', filter.date_range.end);
|
||||
if (filter?.limit !== undefined) queryParams.append('limit', filter.limit.toString());
|
||||
if (filter?.offset !== undefined) queryParams.append('offset', filter.offset.toString());
|
||||
if (filter?.order_by) queryParams.append('order_by', filter.order_by);
|
||||
if (filter?.order_direction) queryParams.append('order_direction', filter.order_direction);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/food-safety/temperature-logs?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/food-safety/temperature-logs`;
|
||||
|
||||
return apiClient.get<PaginatedResponse<TemperatureLogResponse>>(url);
|
||||
}
|
||||
|
||||
async getTemperatureAnalytics(
|
||||
tenantId: string,
|
||||
location: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<TemperatureAnalytics> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('location', location);
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
return apiClient.get<TemperatureAnalytics>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/temperature-analytics?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
// Alert Management
|
||||
async createFoodSafetyAlert(
|
||||
tenantId: string,
|
||||
alertData: FoodSafetyAlertCreate
|
||||
): Promise<FoodSafetyAlertResponse> {
|
||||
return apiClient.post<FoodSafetyAlertResponse>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/alerts`,
|
||||
alertData
|
||||
);
|
||||
}
|
||||
|
||||
async getFoodSafetyAlert(
|
||||
tenantId: string,
|
||||
alertId: string
|
||||
): Promise<FoodSafetyAlertResponse> {
|
||||
return apiClient.get<FoodSafetyAlertResponse>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/alerts/${alertId}`
|
||||
);
|
||||
}
|
||||
|
||||
async getFoodSafetyAlerts(
|
||||
tenantId: string,
|
||||
status?: 'open' | 'in_progress' | 'resolved' | 'dismissed',
|
||||
severity?: 'critical' | 'warning' | 'info',
|
||||
limit: number = 50,
|
||||
offset: number = 0
|
||||
): Promise<PaginatedResponse<FoodSafetyAlertResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (status) queryParams.append('status', status);
|
||||
if (severity) queryParams.append('severity', severity);
|
||||
queryParams.append('limit', limit.toString());
|
||||
queryParams.append('offset', offset.toString());
|
||||
|
||||
return apiClient.get<PaginatedResponse<FoodSafetyAlertResponse>>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/alerts?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async updateFoodSafetyAlert(
|
||||
tenantId: string,
|
||||
alertId: string,
|
||||
updateData: FoodSafetyAlertUpdate
|
||||
): Promise<FoodSafetyAlertResponse> {
|
||||
return apiClient.put<FoodSafetyAlertResponse>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/alerts/${alertId}`,
|
||||
updateData
|
||||
);
|
||||
}
|
||||
|
||||
async deleteFoodSafetyAlert(
|
||||
tenantId: string,
|
||||
alertId: string
|
||||
): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/alerts/${alertId}`
|
||||
);
|
||||
}
|
||||
|
||||
// Dashboard and Metrics
|
||||
async getFoodSafetyDashboard(tenantId: string): Promise<FoodSafetyDashboard> {
|
||||
return apiClient.get<FoodSafetyDashboard>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/dashboard`
|
||||
);
|
||||
}
|
||||
|
||||
async getFoodSafetyMetrics(
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<FoodSafetyMetrics> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/food-safety/metrics?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/food-safety/metrics`;
|
||||
|
||||
return apiClient.get<FoodSafetyMetrics>(url);
|
||||
}
|
||||
|
||||
async getTemperatureViolations(
|
||||
tenantId: string,
|
||||
limit: number = 20
|
||||
): Promise<TemperatureLogResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('limit', limit.toString());
|
||||
|
||||
return apiClient.get<TemperatureLogResponse[]>(
|
||||
`${this.baseUrl}/${tenantId}/food-safety/temperature-violations?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getComplianceRate(
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<{
|
||||
overall_rate: number;
|
||||
by_type: Record<string, number>;
|
||||
trend: Array<{ date: string; rate: number }>;
|
||||
}> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/food-safety/compliance-rate?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/food-safety/compliance-rate`;
|
||||
|
||||
return apiClient.get(url);
|
||||
}
|
||||
}
|
||||
|
||||
export const foodSafetyService = new FoodSafetyService();
|
||||
228
frontend/src/api/services/inventory.ts
Normal file
228
frontend/src/api/services/inventory.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Inventory Service - Mirror backend inventory endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
IngredientCreate,
|
||||
IngredientUpdate,
|
||||
IngredientResponse,
|
||||
StockCreate,
|
||||
StockUpdate,
|
||||
StockResponse,
|
||||
StockMovementCreate,
|
||||
StockMovementResponse,
|
||||
InventoryFilter,
|
||||
StockFilter,
|
||||
StockConsumptionRequest,
|
||||
StockConsumptionResponse,
|
||||
PaginatedResponse,
|
||||
} from '../types/inventory';
|
||||
|
||||
export class InventoryService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
// Ingredient Management
|
||||
async createIngredient(
|
||||
tenantId: string,
|
||||
ingredientData: IngredientCreate
|
||||
): Promise<IngredientResponse> {
|
||||
return apiClient.post<IngredientResponse>(`${this.baseUrl}/${tenantId}/ingredients`, ingredientData);
|
||||
}
|
||||
|
||||
async getIngredient(tenantId: string, ingredientId: string): Promise<IngredientResponse> {
|
||||
return apiClient.get<IngredientResponse>(`${this.baseUrl}/${tenantId}/ingredients/${ingredientId}`);
|
||||
}
|
||||
|
||||
async getIngredients(
|
||||
tenantId: string,
|
||||
filter?: InventoryFilter
|
||||
): Promise<PaginatedResponse<IngredientResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (filter?.category) queryParams.append('category', filter.category);
|
||||
if (filter?.stock_status) queryParams.append('stock_status', filter.stock_status);
|
||||
if (filter?.requires_refrigeration !== undefined)
|
||||
queryParams.append('requires_refrigeration', filter.requires_refrigeration.toString());
|
||||
if (filter?.requires_freezing !== undefined)
|
||||
queryParams.append('requires_freezing', filter.requires_freezing.toString());
|
||||
if (filter?.is_seasonal !== undefined)
|
||||
queryParams.append('is_seasonal', filter.is_seasonal.toString());
|
||||
if (filter?.supplier_id) queryParams.append('supplier_id', filter.supplier_id);
|
||||
if (filter?.expiring_within_days !== undefined)
|
||||
queryParams.append('expiring_within_days', filter.expiring_within_days.toString());
|
||||
if (filter?.search) queryParams.append('search', filter.search);
|
||||
if (filter?.limit !== undefined) queryParams.append('limit', filter.limit.toString());
|
||||
if (filter?.offset !== undefined) queryParams.append('offset', filter.offset.toString());
|
||||
if (filter?.order_by) queryParams.append('order_by', filter.order_by);
|
||||
if (filter?.order_direction) queryParams.append('order_direction', filter.order_direction);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/ingredients?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/ingredients`;
|
||||
|
||||
return apiClient.get<PaginatedResponse<IngredientResponse>>(url);
|
||||
}
|
||||
|
||||
async updateIngredient(
|
||||
tenantId: string,
|
||||
ingredientId: string,
|
||||
updateData: IngredientUpdate
|
||||
): Promise<IngredientResponse> {
|
||||
return apiClient.put<IngredientResponse>(
|
||||
`${this.baseUrl}/${tenantId}/ingredients/${ingredientId}`,
|
||||
updateData
|
||||
);
|
||||
}
|
||||
|
||||
async deleteIngredient(tenantId: string, ingredientId: string): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(`${this.baseUrl}/${tenantId}/ingredients/${ingredientId}`);
|
||||
}
|
||||
|
||||
async getIngredientsByCategory(tenantId: string): Promise<Record<string, IngredientResponse[]>> {
|
||||
return apiClient.get<Record<string, IngredientResponse[]>>(`${this.baseUrl}/${tenantId}/ingredients/by-category`);
|
||||
}
|
||||
|
||||
async getLowStockIngredients(tenantId: string): Promise<IngredientResponse[]> {
|
||||
return apiClient.get<IngredientResponse[]>(`${this.baseUrl}/${tenantId}/ingredients/low-stock`);
|
||||
}
|
||||
|
||||
// Stock Management
|
||||
async addStock(tenantId: string, stockData: StockCreate): Promise<StockResponse> {
|
||||
return apiClient.post<StockResponse>(`${this.baseUrl}/${tenantId}/stock`, stockData);
|
||||
}
|
||||
|
||||
async getStock(tenantId: string, stockId: string): Promise<StockResponse> {
|
||||
return apiClient.get<StockResponse>(`${this.baseUrl}/${tenantId}/stock/${stockId}`);
|
||||
}
|
||||
|
||||
async getStockByIngredient(
|
||||
tenantId: string,
|
||||
ingredientId: string,
|
||||
includeUnavailable: boolean = false
|
||||
): Promise<StockResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('include_unavailable', includeUnavailable.toString());
|
||||
|
||||
return apiClient.get<StockResponse[]>(
|
||||
`${this.baseUrl}/${tenantId}/stock/ingredient/${ingredientId}?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getAllStock(tenantId: string, filter?: StockFilter): Promise<PaginatedResponse<StockResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (filter?.ingredient_id) queryParams.append('ingredient_id', filter.ingredient_id);
|
||||
if (filter?.is_available !== undefined) queryParams.append('is_available', filter.is_available.toString());
|
||||
if (filter?.is_expired !== undefined) queryParams.append('is_expired', filter.is_expired.toString());
|
||||
if (filter?.expiring_within_days !== undefined)
|
||||
queryParams.append('expiring_within_days', filter.expiring_within_days.toString());
|
||||
if (filter?.batch_number) queryParams.append('batch_number', filter.batch_number);
|
||||
if (filter?.supplier_id) queryParams.append('supplier_id', filter.supplier_id);
|
||||
if (filter?.limit !== undefined) queryParams.append('limit', filter.limit.toString());
|
||||
if (filter?.offset !== undefined) queryParams.append('offset', filter.offset.toString());
|
||||
if (filter?.order_by) queryParams.append('order_by', filter.order_by);
|
||||
if (filter?.order_direction) queryParams.append('order_direction', filter.order_direction);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/stock?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/stock`;
|
||||
|
||||
return apiClient.get<PaginatedResponse<StockResponse>>(url);
|
||||
}
|
||||
|
||||
async updateStock(
|
||||
tenantId: string,
|
||||
stockId: string,
|
||||
updateData: StockUpdate
|
||||
): Promise<StockResponse> {
|
||||
return apiClient.put<StockResponse>(`${this.baseUrl}/${tenantId}/stock/${stockId}`, updateData);
|
||||
}
|
||||
|
||||
async deleteStock(tenantId: string, stockId: string): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(`${this.baseUrl}/${tenantId}/stock/${stockId}`);
|
||||
}
|
||||
|
||||
async consumeStock(
|
||||
tenantId: string,
|
||||
consumptionData: StockConsumptionRequest
|
||||
): Promise<StockConsumptionResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('ingredient_id', consumptionData.ingredient_id);
|
||||
queryParams.append('quantity', consumptionData.quantity.toString());
|
||||
if (consumptionData.reference_number)
|
||||
queryParams.append('reference_number', consumptionData.reference_number);
|
||||
if (consumptionData.notes) queryParams.append('notes', consumptionData.notes);
|
||||
if (consumptionData.fifo !== undefined) queryParams.append('fifo', consumptionData.fifo.toString());
|
||||
|
||||
return apiClient.post<StockConsumptionResponse>(
|
||||
`${this.baseUrl}/${tenantId}/stock/consume?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
// Stock Movements
|
||||
async createStockMovement(
|
||||
tenantId: string,
|
||||
movementData: StockMovementCreate
|
||||
): Promise<StockMovementResponse> {
|
||||
return apiClient.post<StockMovementResponse>(`${this.baseUrl}/${tenantId}/stock/movements`, movementData);
|
||||
}
|
||||
|
||||
async getStockMovements(
|
||||
tenantId: string,
|
||||
ingredientId?: string,
|
||||
limit: number = 50,
|
||||
offset: number = 0
|
||||
): Promise<PaginatedResponse<StockMovementResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (ingredientId) queryParams.append('ingredient_id', ingredientId);
|
||||
queryParams.append('limit', limit.toString());
|
||||
queryParams.append('offset', offset.toString());
|
||||
|
||||
return apiClient.get<PaginatedResponse<StockMovementResponse>>(
|
||||
`${this.baseUrl}/${tenantId}/stock/movements?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
// Expiry Management
|
||||
async getExpiringStock(
|
||||
tenantId: string,
|
||||
withinDays: number = 7
|
||||
): Promise<StockResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('within_days', withinDays.toString());
|
||||
|
||||
return apiClient.get<StockResponse[]>(
|
||||
`${this.baseUrl}/${tenantId}/stock/expiring?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getExpiredStock(tenantId: string): Promise<StockResponse[]> {
|
||||
return apiClient.get<StockResponse[]>(`${this.baseUrl}/${tenantId}/stock/expired`);
|
||||
}
|
||||
|
||||
// Analytics
|
||||
async getStockAnalytics(
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<{
|
||||
total_ingredients: number;
|
||||
total_stock_value: number;
|
||||
low_stock_count: number;
|
||||
out_of_stock_count: number;
|
||||
expiring_soon_count: number;
|
||||
stock_turnover_rate: number;
|
||||
}> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/inventory/analytics?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/inventory/analytics`;
|
||||
|
||||
return apiClient.get(url);
|
||||
}
|
||||
}
|
||||
|
||||
export const inventoryService = new InventoryService();
|
||||
138
frontend/src/api/services/inventoryDashboard.ts
Normal file
138
frontend/src/api/services/inventoryDashboard.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Inventory Dashboard Service - Mirror backend dashboard endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
InventoryDashboardSummary,
|
||||
InventoryAnalytics,
|
||||
BusinessModelInsights,
|
||||
DashboardFilter,
|
||||
AlertsFilter,
|
||||
RecentActivity,
|
||||
} from '../types/dashboard';
|
||||
|
||||
export class InventoryDashboardService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
async getDashboardSummary(
|
||||
tenantId: string,
|
||||
filter?: DashboardFilter
|
||||
): Promise<InventoryDashboardSummary> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (filter?.date_range?.start) queryParams.append('start_date', filter.date_range.start);
|
||||
if (filter?.date_range?.end) queryParams.append('end_date', filter.date_range.end);
|
||||
if (filter?.categories?.length) queryParams.append('categories', filter.categories.join(','));
|
||||
if (filter?.include_expired !== undefined)
|
||||
queryParams.append('include_expired', filter.include_expired.toString());
|
||||
if (filter?.include_unavailable !== undefined)
|
||||
queryParams.append('include_unavailable', filter.include_unavailable.toString());
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/dashboard/summary?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/dashboard/summary`;
|
||||
|
||||
return apiClient.get<InventoryDashboardSummary>(url);
|
||||
}
|
||||
|
||||
async getInventoryAnalytics(
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<InventoryAnalytics> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/dashboard/analytics?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/dashboard/analytics`;
|
||||
|
||||
return apiClient.get<InventoryAnalytics>(url);
|
||||
}
|
||||
|
||||
async getBusinessModelInsights(tenantId: string): Promise<BusinessModelInsights> {
|
||||
return apiClient.get<BusinessModelInsights>(
|
||||
`${this.baseUrl}/${tenantId}/dashboard/business-insights`
|
||||
);
|
||||
}
|
||||
|
||||
async getRecentActivity(
|
||||
tenantId: string,
|
||||
limit: number = 20
|
||||
): Promise<RecentActivity[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('limit', limit.toString());
|
||||
|
||||
return apiClient.get<RecentActivity[]>(
|
||||
`${this.baseUrl}/${tenantId}/dashboard/recent-activity?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getAlerts(
|
||||
tenantId: string,
|
||||
filter?: AlertsFilter
|
||||
): Promise<{
|
||||
items: any[];
|
||||
total: number;
|
||||
}> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (filter?.severity) queryParams.append('severity', filter.severity);
|
||||
if (filter?.type) queryParams.append('type', filter.type);
|
||||
if (filter?.resolved !== undefined) queryParams.append('resolved', filter.resolved.toString());
|
||||
if (filter?.limit !== undefined) queryParams.append('limit', filter.limit.toString());
|
||||
if (filter?.offset !== undefined) queryParams.append('offset', filter.offset.toString());
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/dashboard/alerts?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/dashboard/alerts`;
|
||||
|
||||
return apiClient.get(url);
|
||||
}
|
||||
|
||||
async getStockSummary(tenantId: string): Promise<{
|
||||
in_stock: number;
|
||||
low_stock: number;
|
||||
out_of_stock: number;
|
||||
overstock: number;
|
||||
total_value: number;
|
||||
}> {
|
||||
return apiClient.get(`${this.baseUrl}/${tenantId}/dashboard/stock-summary`);
|
||||
}
|
||||
|
||||
async getTopCategories(tenantId: string, limit: number = 10): Promise<Array<{
|
||||
category: string;
|
||||
ingredient_count: number;
|
||||
total_value: number;
|
||||
low_stock_count: number;
|
||||
}>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('limit', limit.toString());
|
||||
|
||||
return apiClient.get(
|
||||
`${this.baseUrl}/${tenantId}/dashboard/top-categories?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
async getExpiryCalendar(
|
||||
tenantId: string,
|
||||
daysAhead: number = 30
|
||||
): Promise<Array<{
|
||||
date: string;
|
||||
items: Array<{
|
||||
ingredient_name: string;
|
||||
quantity: number;
|
||||
batch_number?: string;
|
||||
}>;
|
||||
}>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('days_ahead', daysAhead.toString());
|
||||
|
||||
return apiClient.get(
|
||||
`${this.baseUrl}/${tenantId}/dashboard/expiry-calendar?${queryParams.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const inventoryDashboardService = new InventoryDashboardService();
|
||||
52
frontend/src/api/services/onboarding.ts
Normal file
52
frontend/src/api/services/onboarding.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Onboarding Service - Mirror backend onboarding endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import { UserProgress, UpdateStepRequest } from '../types/onboarding';
|
||||
|
||||
export class OnboardingService {
|
||||
private readonly baseUrl = '/onboarding';
|
||||
|
||||
async getUserProgress(userId: string): Promise<UserProgress> {
|
||||
return apiClient.get<UserProgress>(`${this.baseUrl}/progress/${userId}`);
|
||||
}
|
||||
|
||||
async updateStep(userId: string, stepData: UpdateStepRequest): Promise<UserProgress> {
|
||||
return apiClient.put<UserProgress>(`${this.baseUrl}/progress/${userId}/step`, stepData);
|
||||
}
|
||||
|
||||
async markStepCompleted(
|
||||
userId: string,
|
||||
stepName: string,
|
||||
data?: Record<string, any>
|
||||
): Promise<UserProgress> {
|
||||
return apiClient.post<UserProgress>(`${this.baseUrl}/progress/${userId}/complete`, {
|
||||
step_name: stepName,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
async resetProgress(userId: string): Promise<UserProgress> {
|
||||
return apiClient.post<UserProgress>(`${this.baseUrl}/progress/${userId}/reset`);
|
||||
}
|
||||
|
||||
async getStepDetails(stepName: string): Promise<{
|
||||
name: string;
|
||||
description: string;
|
||||
dependencies: string[];
|
||||
estimated_time_minutes: number;
|
||||
}> {
|
||||
return apiClient.get(`${this.baseUrl}/steps/${stepName}`);
|
||||
}
|
||||
|
||||
async getAllSteps(): Promise<Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
dependencies: string[];
|
||||
estimated_time_minutes: number;
|
||||
}>> {
|
||||
return apiClient.get(`${this.baseUrl}/steps`);
|
||||
}
|
||||
}
|
||||
|
||||
export const onboardingService = new OnboardingService();
|
||||
126
frontend/src/api/services/sales.ts
Normal file
126
frontend/src/api/services/sales.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Sales Service - Mirror backend sales endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
SalesDataCreate,
|
||||
SalesDataUpdate,
|
||||
SalesDataResponse,
|
||||
SalesDataQuery,
|
||||
SalesAnalytics,
|
||||
} from '../types/sales';
|
||||
|
||||
export class SalesService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
// Sales Data CRUD Operations
|
||||
async createSalesRecord(
|
||||
tenantId: string,
|
||||
salesData: SalesDataCreate
|
||||
): Promise<SalesDataResponse> {
|
||||
return apiClient.post<SalesDataResponse>(`${this.baseUrl}/${tenantId}/sales`, salesData);
|
||||
}
|
||||
|
||||
async getSalesRecords(
|
||||
tenantId: string,
|
||||
query?: SalesDataQuery
|
||||
): Promise<SalesDataResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (query?.start_date) queryParams.append('start_date', query.start_date);
|
||||
if (query?.end_date) queryParams.append('end_date', query.end_date);
|
||||
if (query?.product_name) queryParams.append('product_name', query.product_name);
|
||||
if (query?.product_category) queryParams.append('product_category', query.product_category);
|
||||
if (query?.location_id) queryParams.append('location_id', query.location_id);
|
||||
if (query?.sales_channel) queryParams.append('sales_channel', query.sales_channel);
|
||||
if (query?.source) queryParams.append('source', query.source);
|
||||
if (query?.is_validated !== undefined) queryParams.append('is_validated', query.is_validated.toString());
|
||||
if (query?.limit !== undefined) queryParams.append('limit', query.limit.toString());
|
||||
if (query?.offset !== undefined) queryParams.append('offset', query.offset.toString());
|
||||
if (query?.order_by) queryParams.append('order_by', query.order_by);
|
||||
if (query?.order_direction) queryParams.append('order_direction', query.order_direction);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/sales?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/sales`;
|
||||
|
||||
return apiClient.get<SalesDataResponse[]>(url);
|
||||
}
|
||||
|
||||
async getSalesRecord(
|
||||
tenantId: string,
|
||||
recordId: string
|
||||
): Promise<SalesDataResponse> {
|
||||
return apiClient.get<SalesDataResponse>(`${this.baseUrl}/${tenantId}/sales/${recordId}`);
|
||||
}
|
||||
|
||||
async updateSalesRecord(
|
||||
tenantId: string,
|
||||
recordId: string,
|
||||
updateData: SalesDataUpdate
|
||||
): Promise<SalesDataResponse> {
|
||||
return apiClient.put<SalesDataResponse>(`${this.baseUrl}/${tenantId}/sales/${recordId}`, updateData);
|
||||
}
|
||||
|
||||
async deleteSalesRecord(
|
||||
tenantId: string,
|
||||
recordId: string
|
||||
): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(`${this.baseUrl}/${tenantId}/sales/${recordId}`);
|
||||
}
|
||||
|
||||
async validateSalesRecord(
|
||||
tenantId: string,
|
||||
recordId: string,
|
||||
validationNotes?: string
|
||||
): Promise<SalesDataResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (validationNotes) queryParams.append('validation_notes', validationNotes);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/sales/${recordId}/validate?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/sales/${recordId}/validate`;
|
||||
|
||||
return apiClient.post<SalesDataResponse>(url);
|
||||
}
|
||||
|
||||
// Analytics & Reporting
|
||||
async getSalesAnalytics(
|
||||
tenantId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<SalesAnalytics> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/sales/analytics/summary?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/sales/analytics/summary`;
|
||||
|
||||
return apiClient.get<SalesAnalytics>(url);
|
||||
}
|
||||
|
||||
async getProductSales(
|
||||
tenantId: string,
|
||||
inventoryProductId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
): Promise<SalesDataResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (startDate) queryParams.append('start_date', startDate);
|
||||
if (endDate) queryParams.append('end_date', endDate);
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/inventory-products/${inventoryProductId}/sales?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/inventory-products/${inventoryProductId}/sales`;
|
||||
|
||||
return apiClient.get<SalesDataResponse[]>(url);
|
||||
}
|
||||
|
||||
async getProductCategories(tenantId: string): Promise<string[]> {
|
||||
return apiClient.get<string[]>(`${this.baseUrl}/${tenantId}/sales/categories`);
|
||||
}
|
||||
}
|
||||
|
||||
export const salesService = new SalesService();
|
||||
67
frontend/src/api/services/subscription.ts
Normal file
67
frontend/src/api/services/subscription.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Subscription Service - Mirror backend subscription endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
SubscriptionLimits,
|
||||
FeatureCheckRequest,
|
||||
FeatureCheckResponse,
|
||||
UsageCheckRequest,
|
||||
UsageCheckResponse
|
||||
} from '../types/subscription';
|
||||
|
||||
export class SubscriptionService {
|
||||
private readonly baseUrl = '/subscriptions';
|
||||
|
||||
async getSubscriptionLimits(tenantId: string): Promise<SubscriptionLimits> {
|
||||
return apiClient.get<SubscriptionLimits>(`${this.baseUrl}/${tenantId}/limits`);
|
||||
}
|
||||
|
||||
async checkFeatureAccess(
|
||||
tenantId: string,
|
||||
featureName: string
|
||||
): Promise<FeatureCheckResponse> {
|
||||
return apiClient.get<FeatureCheckResponse>(
|
||||
`${this.baseUrl}/${tenantId}/features/${featureName}/check`
|
||||
);
|
||||
}
|
||||
|
||||
async checkUsageLimit(
|
||||
tenantId: string,
|
||||
resourceType: 'users' | 'sales_records' | 'inventory_items' | 'api_requests',
|
||||
requestedAmount?: number
|
||||
): Promise<UsageCheckResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (requestedAmount !== undefined) {
|
||||
queryParams.append('requested_amount', requestedAmount.toString());
|
||||
}
|
||||
|
||||
const url = queryParams.toString()
|
||||
? `${this.baseUrl}/${tenantId}/usage/${resourceType}/check?${queryParams.toString()}`
|
||||
: `${this.baseUrl}/${tenantId}/usage/${resourceType}/check`;
|
||||
|
||||
return apiClient.get<UsageCheckResponse>(url);
|
||||
}
|
||||
|
||||
async recordUsage(
|
||||
tenantId: string,
|
||||
resourceType: 'users' | 'sales_records' | 'inventory_items' | 'api_requests',
|
||||
amount: number = 1
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
return apiClient.post<{ success: boolean; message: string }>(
|
||||
`${this.baseUrl}/${tenantId}/usage/${resourceType}/record`,
|
||||
{ amount }
|
||||
);
|
||||
}
|
||||
|
||||
async getCurrentUsage(tenantId: string): Promise<{
|
||||
users: number;
|
||||
sales_records: number;
|
||||
inventory_items: number;
|
||||
api_requests_this_hour: number;
|
||||
}> {
|
||||
return apiClient.get(`${this.baseUrl}/${tenantId}/usage/current`);
|
||||
}
|
||||
}
|
||||
|
||||
export const subscriptionService = new SubscriptionService();
|
||||
145
frontend/src/api/services/tenant.ts
Normal file
145
frontend/src/api/services/tenant.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Tenant Service - Mirror backend tenant endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import {
|
||||
BakeryRegistration,
|
||||
TenantResponse,
|
||||
TenantAccessResponse,
|
||||
TenantUpdate,
|
||||
TenantMemberResponse,
|
||||
TenantStatistics,
|
||||
TenantSearchParams,
|
||||
TenantNearbyParams,
|
||||
} from '../types/tenant';
|
||||
|
||||
export class TenantService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
// Tenant CRUD Operations
|
||||
async registerBakery(bakeryData: BakeryRegistration): Promise<TenantResponse> {
|
||||
return apiClient.post<TenantResponse>(`${this.baseUrl}/register`, bakeryData);
|
||||
}
|
||||
|
||||
async getTenant(tenantId: string): Promise<TenantResponse> {
|
||||
return apiClient.get<TenantResponse>(`${this.baseUrl}/${tenantId}`);
|
||||
}
|
||||
|
||||
async getTenantBySubdomain(subdomain: string): Promise<TenantResponse> {
|
||||
return apiClient.get<TenantResponse>(`${this.baseUrl}/subdomain/${subdomain}`);
|
||||
}
|
||||
|
||||
async getUserTenants(userId: string): Promise<TenantResponse[]> {
|
||||
return apiClient.get<TenantResponse[]>(`${this.baseUrl}/users/${userId}`);
|
||||
}
|
||||
|
||||
async getUserOwnedTenants(userId: string): Promise<TenantResponse[]> {
|
||||
return apiClient.get<TenantResponse[]>(`${this.baseUrl}/user/${userId}/owned`);
|
||||
}
|
||||
|
||||
async updateTenant(tenantId: string, updateData: TenantUpdate): Promise<TenantResponse> {
|
||||
return apiClient.put<TenantResponse>(`${this.baseUrl}/${tenantId}`, updateData);
|
||||
}
|
||||
|
||||
async deactivateTenant(tenantId: string): Promise<{ success: boolean; message: string }> {
|
||||
return apiClient.post<{ success: boolean; message: string }>(`${this.baseUrl}/${tenantId}/deactivate`);
|
||||
}
|
||||
|
||||
async activateTenant(tenantId: string): Promise<{ success: boolean; message: string }> {
|
||||
return apiClient.post<{ success: boolean; message: string }>(`${this.baseUrl}/${tenantId}/activate`);
|
||||
}
|
||||
|
||||
// Access Control
|
||||
async verifyTenantAccess(tenantId: string, userId: string): Promise<TenantAccessResponse> {
|
||||
return apiClient.get<TenantAccessResponse>(`${this.baseUrl}/${tenantId}/access/${userId}`);
|
||||
}
|
||||
|
||||
// Search & Discovery
|
||||
async searchTenants(params: TenantSearchParams): Promise<TenantResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (params.search_term) queryParams.append('search_term', params.search_term);
|
||||
if (params.business_type) queryParams.append('business_type', params.business_type);
|
||||
if (params.city) queryParams.append('city', params.city);
|
||||
if (params.skip !== undefined) queryParams.append('skip', params.skip.toString());
|
||||
if (params.limit !== undefined) queryParams.append('limit', params.limit.toString());
|
||||
|
||||
return apiClient.get<TenantResponse[]>(`${this.baseUrl}/search?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
async getNearbyTenants(params: TenantNearbyParams): Promise<TenantResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
queryParams.append('latitude', params.latitude.toString());
|
||||
queryParams.append('longitude', params.longitude.toString());
|
||||
if (params.radius_km !== undefined) queryParams.append('radius_km', params.radius_km.toString());
|
||||
if (params.limit !== undefined) queryParams.append('limit', params.limit.toString());
|
||||
|
||||
return apiClient.get<TenantResponse[]>(`${this.baseUrl}/nearby?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
// Model Management
|
||||
async updateModelStatus(
|
||||
tenantId: string,
|
||||
modelTrained: boolean,
|
||||
lastTrainingDate?: string
|
||||
): Promise<TenantResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('model_trained', modelTrained.toString());
|
||||
if (lastTrainingDate) queryParams.append('last_training_date', lastTrainingDate);
|
||||
|
||||
return apiClient.put<TenantResponse>(`${this.baseUrl}/${tenantId}/model-status?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
// Team Management
|
||||
async addTeamMember(
|
||||
tenantId: string,
|
||||
userId: string,
|
||||
role: string
|
||||
): Promise<TenantMemberResponse> {
|
||||
return apiClient.post<TenantMemberResponse>(`${this.baseUrl}/${tenantId}/members`, {
|
||||
user_id: userId,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamMembers(tenantId: string, activeOnly: boolean = true): Promise<TenantMemberResponse[]> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('active_only', activeOnly.toString());
|
||||
|
||||
return apiClient.get<TenantMemberResponse[]>(`${this.baseUrl}/${tenantId}/members?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
async updateMemberRole(
|
||||
tenantId: string,
|
||||
memberUserId: string,
|
||||
newRole: string
|
||||
): Promise<TenantMemberResponse> {
|
||||
return apiClient.put<TenantMemberResponse>(
|
||||
`${this.baseUrl}/${tenantId}/members/${memberUserId}/role`,
|
||||
{ new_role: newRole }
|
||||
);
|
||||
}
|
||||
|
||||
async removeTeamMember(tenantId: string, memberUserId: string): Promise<{ success: boolean; message: string }> {
|
||||
return apiClient.delete<{ success: boolean; message: string }>(`${this.baseUrl}/${tenantId}/members/${memberUserId}`);
|
||||
}
|
||||
|
||||
// Admin Operations
|
||||
async getTenantStatistics(): Promise<TenantStatistics> {
|
||||
return apiClient.get<TenantStatistics>(`${this.baseUrl}/statistics`);
|
||||
}
|
||||
|
||||
// Context Management (Frontend-only operations)
|
||||
setCurrentTenant(tenant: TenantResponse): void {
|
||||
// Set tenant context in API client
|
||||
apiClient.setTenantId(tenant.id);
|
||||
}
|
||||
|
||||
clearCurrentTenant(): void {
|
||||
// Clear tenant context from API client
|
||||
apiClient.setTenantId(null);
|
||||
}
|
||||
}
|
||||
|
||||
export const tenantService = new TenantService();
|
||||
37
frontend/src/api/services/user.ts
Normal file
37
frontend/src/api/services/user.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* User Service - Mirror backend user endpoints
|
||||
*/
|
||||
import { apiClient } from '../client';
|
||||
import { UserResponse, UserUpdate } from '../types/auth';
|
||||
import { AdminDeleteRequest, AdminDeleteResponse } from '../types/user';
|
||||
|
||||
export class UserService {
|
||||
private readonly baseUrl = '/users';
|
||||
|
||||
async getCurrentUser(): Promise<UserResponse> {
|
||||
return apiClient.get<UserResponse>(`${this.baseUrl}/me`);
|
||||
}
|
||||
|
||||
async updateUser(userId: string, updateData: UserUpdate): Promise<UserResponse> {
|
||||
return apiClient.put<UserResponse>(`${this.baseUrl}/${userId}`, updateData);
|
||||
}
|
||||
|
||||
async deleteUser(userId: string): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(`${this.baseUrl}/${userId}`);
|
||||
}
|
||||
|
||||
// Admin operations
|
||||
async adminDeleteUser(deleteRequest: AdminDeleteRequest): Promise<AdminDeleteResponse> {
|
||||
return apiClient.post<AdminDeleteResponse>(`${this.baseUrl}/admin/delete`, deleteRequest);
|
||||
}
|
||||
|
||||
async getAllUsers(): Promise<UserResponse[]> {
|
||||
return apiClient.get<UserResponse[]>(`${this.baseUrl}/admin/all`);
|
||||
}
|
||||
|
||||
async getUserById(userId: string): Promise<UserResponse> {
|
||||
return apiClient.get<UserResponse>(`${this.baseUrl}/admin/${userId}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const userService = new UserService();
|
||||
Reference in New Issue
Block a user