diff --git a/frontend/src/api/client/index.ts b/frontend/src/api/client/index.ts index 533ccc88..fb96c043 100644 --- a/frontend/src/api/client/index.ts +++ b/frontend/src/api/client/index.ts @@ -392,6 +392,57 @@ private buildURL(endpoint: string): string { return this.request(endpoint, { ...config, method: 'DELETE' }); } + /** + * Raw request that returns the Response object for binary data + */ + async getRaw(endpoint: string, config?: RequestConfig): Promise { + const url = this.buildURL(endpoint); + const modifiedConfig = await this.applyRequestInterceptors(config || {}); + + const headers: Record = { + ...modifiedConfig.headers, + }; + + const fetchConfig: RequestInit = { + method: 'GET', + headers, + signal: AbortSignal.timeout(modifiedConfig.timeout || apiConfig.timeout), + }; + + // Add query parameters + const urlWithParams = new URL(url); + if (modifiedConfig.params) { + Object.entries(modifiedConfig.params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + urlWithParams.searchParams.append(key, String(value)); + } + }); + } + + const response = await fetch(urlWithParams.toString(), fetchConfig); + + if (!response.ok) { + const errorText = await response.text(); + let errorData: ApiError; + + try { + errorData = JSON.parse(errorText); + } catch { + errorData = { + message: `HTTP ${response.status}: ${response.statusText}`, + detail: errorText, + code: `HTTP_${response.status}`, + }; + } + + const error = new Error(errorData.message || 'Request failed'); + (error as any).response = { status: response.status, data: errorData }; + throw error; + } + + return response; + } + /** * File upload with progress tracking */ diff --git a/frontend/src/api/hooks/useInventory.ts b/frontend/src/api/hooks/useInventory.ts index c4412109..d7820c05 100644 --- a/frontend/src/api/hooks/useInventory.ts +++ b/frontend/src/api/hooks/useInventory.ts @@ -88,7 +88,7 @@ interface UseInventoryItemReturn { // ========== MAIN INVENTORY HOOK ========== export const useInventory = (autoLoad = true): UseInventoryReturn => { - const tenantId = useTenantId(); + const { tenantId } = useTenantId(); // State const [items, setItems] = useState([]); @@ -373,7 +373,7 @@ export const useInventory = (autoLoad = true): UseInventoryReturn => { // ========== DASHBOARD HOOK ========== export const useInventoryDashboard = (): UseInventoryDashboardReturn => { - const tenantId = useTenantId(); + const { tenantId } = useTenantId(); const [dashboardData, setDashboardData] = useState(null); const [alerts, setAlerts] = useState([]); const [isLoading, setIsLoading] = useState(false); @@ -419,7 +419,7 @@ export const useInventoryDashboard = (): UseInventoryDashboardReturn => { // ========== SINGLE ITEM HOOK ========== export const useInventoryItem = (itemId: string): UseInventoryItemReturn => { - const tenantId = useTenantId(); + const { tenantId } = useTenantId(); const [item, setItem] = useState(null); const [stockLevel, setStockLevel] = useState(null); const [recentMovements, setRecentMovements] = useState([]); diff --git a/frontend/src/api/hooks/useSuppliers.ts b/frontend/src/api/hooks/useSuppliers.ts index 9ea2132e..72ff2183 100644 --- a/frontend/src/api/hooks/useSuppliers.ts +++ b/frontend/src/api/hooks/useSuppliers.ts @@ -22,6 +22,23 @@ import { } from '../services/suppliers.service'; import { useAuth } from './useAuth'; +// Re-export types for component use +export type { + Supplier, + SupplierSummary, + CreateSupplierRequest, + UpdateSupplierRequest, + SupplierSearchParams, + SupplierStatistics, + PurchaseOrder, + CreatePurchaseOrderRequest, + PurchaseOrderSearchParams, + PurchaseOrderStatistics, + Delivery, + DeliverySearchParams, + DeliveryPerformanceStats +} from '../services/suppliers.service'; + const suppliersService = new SuppliersService(); // ============================================================================ @@ -196,13 +213,13 @@ export function useSuppliers(): UseSuppliers { // Create supplier const createSupplier = useCallback(async (data: CreateSupplierRequest): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setIsCreating(true); setError(null); - const supplier = await suppliersService.createSupplier(user.tenant_id, user.user_id, data); + const supplier = await suppliersService.createSupplier(user.tenant_id, user.id, data); // Refresh suppliers list await loadSuppliers(currentParams); @@ -217,17 +234,17 @@ export function useSuppliers(): UseSuppliers { } finally { setIsCreating(false); } - }, [user?.tenant_id, user?.user_id, loadSuppliers, loadStatistics, currentParams]); + }, [user?.tenant_id, user?.id, loadSuppliers, loadStatistics, currentParams]); // Update supplier const updateSupplier = useCallback(async (supplierId: string, data: UpdateSupplierRequest): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setIsUpdating(true); setError(null); - const updatedSupplier = await suppliersService.updateSupplier(user.tenant_id, user.user_id, supplierId, data); + const updatedSupplier = await suppliersService.updateSupplier(user.tenant_id, user.id, supplierId, data); // Update current supplier if it's the one being edited if (supplier?.id === supplierId) { @@ -246,7 +263,7 @@ export function useSuppliers(): UseSuppliers { } finally { setIsUpdating(false); } - }, [user?.tenant_id, user?.user_id, supplier?.id, loadSuppliers, currentParams]); + }, [user?.tenant_id, user?.id, supplier?.id, loadSuppliers, currentParams]); // Delete supplier const deleteSupplier = useCallback(async (supplierId: string): Promise => { @@ -277,12 +294,12 @@ export function useSuppliers(): UseSuppliers { // Approve/reject supplier const approveSupplier = useCallback(async (supplierId: string, action: 'approve' | 'reject', notes?: string): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setError(null); - const updatedSupplier = await suppliersService.approveSupplier(user.tenant_id, user.user_id, supplierId, action, notes); + const updatedSupplier = await suppliersService.approveSupplier(user.tenant_id, user.id, supplierId, action, notes); // Update current supplier if it's the one being approved/rejected if (supplier?.id === supplierId) { @@ -300,7 +317,7 @@ export function useSuppliers(): UseSuppliers { setError(errorMessage); return null; } - }, [user?.tenant_id, user?.user_id, supplier?.id, loadSuppliers, loadStatistics, currentParams]); + }, [user?.tenant_id, user?.id, supplier?.id, loadSuppliers, loadStatistics, currentParams]); // Clear error const clearError = useCallback(() => { @@ -504,13 +521,13 @@ export function usePurchaseOrders(): UsePurchaseOrders { }, [user?.tenant_id]); const createPurchaseOrder = useCallback(async (data: CreatePurchaseOrderRequest): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setIsCreating(true); setError(null); - const order = await suppliersService.createPurchaseOrder(user.tenant_id, user.user_id, data); + const order = await suppliersService.createPurchaseOrder(user.tenant_id, user.id, data); // Refresh orders list await loadPurchaseOrders(currentParams); @@ -525,15 +542,15 @@ export function usePurchaseOrders(): UsePurchaseOrders { } finally { setIsCreating(false); } - }, [user?.tenant_id, user?.user_id, loadPurchaseOrders, loadStatistics, currentParams]); + }, [user?.tenant_id, user?.id, loadPurchaseOrders, loadStatistics, currentParams]); const updateOrderStatus = useCallback(async (poId: string, status: string, notes?: string): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setError(null); - const updatedOrder = await suppliersService.updatePurchaseOrderStatus(user.tenant_id, user.user_id, poId, status, notes); + const updatedOrder = await suppliersService.updatePurchaseOrderStatus(user.tenant_id, user.id, poId, status, notes); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); @@ -548,15 +565,15 @@ export function usePurchaseOrders(): UsePurchaseOrders { setError(errorMessage); return null; } - }, [user?.tenant_id, user?.user_id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); + }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); const approveOrder = useCallback(async (poId: string, action: 'approve' | 'reject', notes?: string): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setError(null); - const updatedOrder = await suppliersService.approvePurchaseOrder(user.tenant_id, user.user_id, poId, action, notes); + const updatedOrder = await suppliersService.approvePurchaseOrder(user.tenant_id, user.id, poId, action, notes); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); @@ -572,15 +589,15 @@ export function usePurchaseOrders(): UsePurchaseOrders { setError(errorMessage); return null; } - }, [user?.tenant_id, user?.user_id, purchaseOrder?.id, loadPurchaseOrders, loadOrdersRequiringApproval, currentParams]); + }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, loadOrdersRequiringApproval, currentParams]); const sendToSupplier = useCallback(async (poId: string, sendEmail: boolean = true): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setError(null); - const updatedOrder = await suppliersService.sendToSupplier(user.tenant_id, user.user_id, poId, sendEmail); + const updatedOrder = await suppliersService.sendToSupplier(user.tenant_id, user.id, poId, sendEmail); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); @@ -595,15 +612,15 @@ export function usePurchaseOrders(): UsePurchaseOrders { setError(errorMessage); return null; } - }, [user?.tenant_id, user?.user_id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); + }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); const cancelOrder = useCallback(async (poId: string, reason: string): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setError(null); - const updatedOrder = await suppliersService.cancelPurchaseOrder(user.tenant_id, user.user_id, poId, reason); + const updatedOrder = await suppliersService.cancelPurchaseOrder(user.tenant_id, user.id, poId, reason); if (purchaseOrder?.id === poId) { setPurchaseOrder(updatedOrder); @@ -618,7 +635,7 @@ export function usePurchaseOrders(): UsePurchaseOrders { setError(errorMessage); return null; } - }, [user?.tenant_id, user?.user_id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); + }, [user?.tenant_id, user?.id, purchaseOrder?.id, loadPurchaseOrders, currentParams]); const clearError = useCallback(() => { setError(null); @@ -804,12 +821,12 @@ export function useDeliveries(): UseDeliveries { }, [user?.tenant_id]); const updateDeliveryStatus = useCallback(async (deliveryId: string, status: string, notes?: string): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setError(null); - const updatedDelivery = await suppliersService.updateDeliveryStatus(user.tenant_id, user.user_id, deliveryId, status, notes); + const updatedDelivery = await suppliersService.updateDeliveryStatus(user.tenant_id, user.id, deliveryId, status, notes); if (delivery?.id === deliveryId) { setDelivery(updatedDelivery); @@ -824,15 +841,15 @@ export function useDeliveries(): UseDeliveries { setError(errorMessage); return null; } - }, [user?.tenant_id, user?.user_id, delivery?.id, loadDeliveries, currentParams]); + }, [user?.tenant_id, user?.id, delivery?.id, loadDeliveries, currentParams]); const receiveDelivery = useCallback(async (deliveryId: string, receiptData: any): Promise => { - if (!user?.tenant_id || !user?.user_id) return null; + if (!user?.tenant_id || !user?.id) return null; try { setError(null); - const updatedDelivery = await suppliersService.receiveDelivery(user.tenant_id, user.user_id, deliveryId, receiptData); + const updatedDelivery = await suppliersService.receiveDelivery(user.tenant_id, user.id, deliveryId, receiptData); if (delivery?.id === deliveryId) { setDelivery(updatedDelivery); @@ -847,7 +864,7 @@ export function useDeliveries(): UseDeliveries { setError(errorMessage); return null; } - }, [user?.tenant_id, user?.user_id, delivery?.id, loadDeliveries, currentParams]); + }, [user?.tenant_id, user?.id, delivery?.id, loadDeliveries, currentParams]); const clearError = useCallback(() => { setError(null); diff --git a/frontend/src/api/services/inventory.service.ts b/frontend/src/api/services/inventory.service.ts index 8775ce3e..c9d7e8b7 100644 --- a/frontend/src/api/services/inventory.service.ts +++ b/frontend/src/api/services/inventory.service.ts @@ -43,6 +43,7 @@ export interface InventoryItem { supplier?: string; notes?: string; barcode?: string; + sku?: string; cost_per_unit?: number; is_active: boolean; created_at: string; diff --git a/frontend/src/api/services/onboarding.service.ts b/frontend/src/api/services/onboarding.service.ts index 5a78331f..ae439fad 100644 --- a/frontend/src/api/services/onboarding.service.ts +++ b/frontend/src/api/services/onboarding.service.ts @@ -211,7 +211,20 @@ export class OnboardingService { suggestions: suggestions.map(s => ({ suggestion_id: s.suggestion_id, approved: s.user_approved ?? true, - modifications: s.user_modifications || {} + modifications: s.user_modifications || {}, + // Include full suggestion data for backend processing + original_name: s.original_name, + suggested_name: s.suggested_name, + product_type: s.product_type, + category: s.category, + unit_of_measure: s.unit_of_measure, + confidence_score: s.confidence_score, + estimated_shelf_life_days: s.estimated_shelf_life_days, + requires_refrigeration: s.requires_refrigeration, + requires_freezing: s.requires_freezing, + is_seasonal: s.is_seasonal, + suggested_supplier: s.suggested_supplier, + notes: s.notes })) }); } diff --git a/frontend/src/api/services/recipes.service.ts b/frontend/src/api/services/recipes.service.ts index eb882b40..3dea2480 100644 --- a/frontend/src/api/services/recipes.service.ts +++ b/frontend/src/api/services/recipes.service.ts @@ -367,14 +367,14 @@ export class RecipesService { headers: { 'X-Tenant-ID': tenantId }, params }); - return response.data; + return response; } async getRecipe(tenantId: string, recipeId: string): Promise { const response = await apiClient.get(`${this.baseUrl}/recipes/${recipeId}`, { headers: { 'X-Tenant-ID': tenantId } }); - return response.data; + return response; } async createRecipe(tenantId: string, userId: string, data: CreateRecipeRequest): Promise { @@ -384,7 +384,7 @@ export class RecipesService { 'X-User-ID': userId } }); - return response.data; + return response; } async updateRecipe(tenantId: string, userId: string, recipeId: string, data: UpdateRecipeRequest): Promise { @@ -394,7 +394,7 @@ export class RecipesService { 'X-User-ID': userId } }); - return response.data; + return response; } async deleteRecipe(tenantId: string, recipeId: string): Promise { @@ -413,7 +413,7 @@ export class RecipesService { } } ); - return response.data; + return response; } async activateRecipe(tenantId: string, userId: string, recipeId: string): Promise { @@ -423,7 +423,7 @@ export class RecipesService { 'X-User-ID': userId } }); - return response.data; + return response; } async checkRecipeFeasibility(tenantId: string, recipeId: string, batchMultiplier: number = 1.0): Promise { @@ -431,21 +431,21 @@ export class RecipesService { headers: { 'X-Tenant-ID': tenantId }, params: { batch_multiplier: batchMultiplier } }); - return response.data; + return response; } async getRecipeStatistics(tenantId: string): Promise { const response = await apiClient.get(`${this.baseUrl}/recipes/statistics/dashboard`, { headers: { 'X-Tenant-ID': tenantId } }); - return response.data; + return response; } async getRecipeCategories(tenantId: string): Promise { const response = await apiClient.get<{ categories: string[] }>(`${this.baseUrl}/recipes/categories/list`, { headers: { 'X-Tenant-ID': tenantId } }); - return response.data.categories; + return response.categories; } // Production Management @@ -454,14 +454,14 @@ export class RecipesService { headers: { 'X-Tenant-ID': tenantId }, params }); - return response.data; + return response; } async getProductionBatch(tenantId: string, batchId: string): Promise { const response = await apiClient.get(`${this.baseUrl}/production/batches/${batchId}`, { headers: { 'X-Tenant-ID': tenantId } }); - return response.data; + return response; } async createProductionBatch(tenantId: string, userId: string, data: CreateProductionBatchRequest): Promise { @@ -471,7 +471,7 @@ export class RecipesService { 'X-User-ID': userId } }); - return response.data; + return response; } async updateProductionBatch(tenantId: string, userId: string, batchId: string, data: UpdateProductionBatchRequest): Promise { @@ -481,7 +481,7 @@ export class RecipesService { 'X-User-ID': userId } }); - return response.data; + return response; } async deleteProductionBatch(tenantId: string, batchId: string): Promise { @@ -494,7 +494,7 @@ export class RecipesService { const response = await apiClient.get(`${this.baseUrl}/production/batches/active/list`, { headers: { 'X-Tenant-ID': tenantId } }); - return response.data; + return response; } async startProductionBatch(tenantId: string, userId: string, batchId: string, data: { @@ -519,7 +519,7 @@ export class RecipesService { 'X-User-ID': userId } }); - return response.data; + return response; } async completeProductionBatch(tenantId: string, userId: string, batchId: string, data: { @@ -538,7 +538,7 @@ export class RecipesService { 'X-User-ID': userId } }); - return response.data; + return response; } async getProductionStatistics(tenantId: string, startDate?: string, endDate?: string): Promise { @@ -546,6 +546,6 @@ export class RecipesService { headers: { 'X-Tenant-ID': tenantId }, params: { start_date: startDate, end_date: endDate } }); - return response.data; + return response; } } \ No newline at end of file diff --git a/frontend/src/api/services/suppliers.service.ts b/frontend/src/api/services/suppliers.service.ts index ec4245b8..860d5d37 100644 --- a/frontend/src/api/services/suppliers.service.ts +++ b/frontend/src/api/services/suppliers.service.ts @@ -361,7 +361,7 @@ export class SuppliersService { `${this.baseUrl}?${searchParams.toString()}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getSupplier(tenantId: string, supplierId: string): Promise { @@ -369,7 +369,7 @@ export class SuppliersService { `${this.baseUrl}/${supplierId}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async createSupplier(tenantId: string, userId: string, data: CreateSupplierRequest): Promise { @@ -378,7 +378,7 @@ export class SuppliersService { data, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async updateSupplier(tenantId: string, userId: string, supplierId: string, data: UpdateSupplierRequest): Promise { @@ -387,7 +387,7 @@ export class SuppliersService { data, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async deleteSupplier(tenantId: string, supplierId: string): Promise { @@ -403,7 +403,7 @@ export class SuppliersService { { action, notes }, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } // Supplier Analytics & Lists @@ -412,7 +412,7 @@ export class SuppliersService { `${this.baseUrl}/statistics`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getActiveSuppliers(tenantId: string): Promise { @@ -420,7 +420,7 @@ export class SuppliersService { `${this.baseUrl}/active`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getTopSuppliers(tenantId: string, limit: number = 10): Promise { @@ -428,7 +428,7 @@ export class SuppliersService { `${this.baseUrl}/top?limit=${limit}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getSuppliersByType(tenantId: string, supplierType: string): Promise { @@ -436,7 +436,7 @@ export class SuppliersService { `${this.baseUrl}/types/${supplierType}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getSuppliersNeedingReview(tenantId: string, daysSinceLastOrder: number = 30): Promise { @@ -444,7 +444,7 @@ export class SuppliersService { `${this.baseUrl}/pending-review?days_since_last_order=${daysSinceLastOrder}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } // Purchase Orders @@ -463,7 +463,7 @@ export class SuppliersService { `/api/v1/purchase-orders?${searchParams.toString()}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getPurchaseOrder(tenantId: string, poId: string): Promise { @@ -471,7 +471,7 @@ export class SuppliersService { `/api/v1/purchase-orders/${poId}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async createPurchaseOrder(tenantId: string, userId: string, data: CreatePurchaseOrderRequest): Promise { @@ -480,7 +480,7 @@ export class SuppliersService { data, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async updatePurchaseOrderStatus(tenantId: string, userId: string, poId: string, status: string, notes?: string): Promise { @@ -489,7 +489,7 @@ export class SuppliersService { { status, notes }, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async approvePurchaseOrder(tenantId: string, userId: string, poId: string, action: 'approve' | 'reject', notes?: string): Promise { @@ -498,7 +498,7 @@ export class SuppliersService { { action, notes }, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async sendToSupplier(tenantId: string, userId: string, poId: string, sendEmail: boolean = true): Promise { @@ -507,7 +507,7 @@ export class SuppliersService { {}, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async cancelPurchaseOrder(tenantId: string, userId: string, poId: string, reason: string): Promise { @@ -516,7 +516,7 @@ export class SuppliersService { {}, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async getPurchaseOrderStatistics(tenantId: string): Promise { @@ -524,7 +524,7 @@ export class SuppliersService { '/api/v1/purchase-orders/statistics', { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getOrdersRequiringApproval(tenantId: string): Promise { @@ -532,7 +532,7 @@ export class SuppliersService { '/api/v1/purchase-orders/pending-approval', { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getOverdueOrders(tenantId: string): Promise { @@ -540,7 +540,7 @@ export class SuppliersService { '/api/v1/purchase-orders/overdue', { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } // Deliveries @@ -558,7 +558,7 @@ export class SuppliersService { `/api/v1/deliveries?${searchParams.toString()}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getDelivery(tenantId: string, deliveryId: string): Promise { @@ -566,7 +566,7 @@ export class SuppliersService { `/api/v1/deliveries/${deliveryId}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getTodaysDeliveries(tenantId: string): Promise { @@ -574,7 +574,7 @@ export class SuppliersService { '/api/v1/deliveries/today', { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async getOverdueDeliveries(tenantId: string): Promise { @@ -582,7 +582,7 @@ export class SuppliersService { '/api/v1/deliveries/overdue', { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } async updateDeliveryStatus(tenantId: string, userId: string, deliveryId: string, status: string, notes?: string): Promise { @@ -591,7 +591,7 @@ export class SuppliersService { { status, notes, update_timestamps: true }, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async receiveDelivery(tenantId: string, userId: string, deliveryId: string, receiptData: { @@ -605,7 +605,7 @@ export class SuppliersService { receiptData, { headers: { 'X-Tenant-ID': tenantId, 'X-User-ID': userId } } ); - return response.data; + return response; } async getDeliveryPerformanceStats(tenantId: string, daysBack: number = 30, supplierId?: string): Promise { @@ -617,6 +617,6 @@ export class SuppliersService { `/api/v1/deliveries/performance-stats?${params.toString()}`, { headers: { 'X-Tenant-ID': tenantId } } ); - return response.data; + return response; } } \ No newline at end of file diff --git a/frontend/src/api/types/common.ts b/frontend/src/api/types/common.ts index c0957f35..e4a64e3c 100644 --- a/frontend/src/api/types/common.ts +++ b/frontend/src/api/types/common.ts @@ -39,4 +39,16 @@ export interface BaseQueryParams { search?: string; sort?: string; order?: 'asc' | 'desc'; +} + +export interface CreateResponse { + data: T; + message?: string; + status: string; +} + +export interface UpdateResponse { + data: T; + message?: string; + status: string; } \ No newline at end of file diff --git a/frontend/src/api/types/data.ts b/frontend/src/api/types/data.ts index 9ebc816b..51ff8698 100644 --- a/frontend/src/api/types/data.ts +++ b/frontend/src/api/types/data.ts @@ -18,6 +18,15 @@ export interface SalesData { source: string; created_at: string; external_factors?: ExternalFactors; + // Additional properties used by components + sales_channel?: string; + is_validated?: boolean; + cost_of_goods?: number; + revenue?: number; + quantity_sold?: number; + inventory_product_id?: string; + discount_applied?: number; + weather_condition?: string; } export interface SalesValidationResult { @@ -53,6 +62,10 @@ export interface SalesDataQuery extends BaseQueryParams { max_quantity?: number; min_revenue?: number; max_revenue?: number; + search_term?: string; + sales_channel?: string; + inventory_product_id?: string; + is_validated?: boolean; } export interface SalesDataImport { diff --git a/frontend/src/api/types/tenant.ts b/frontend/src/api/types/tenant.ts index 0283f73b..4b3e0304 100644 --- a/frontend/src/api/types/tenant.ts +++ b/frontend/src/api/types/tenant.ts @@ -14,6 +14,8 @@ export interface TenantInfo { settings?: TenantSettings; subscription?: TenantSubscription; location?: TenantLocation; + business_type?: 'bakery' | 'coffee_shop' | 'pastry_shop' | 'restaurant'; + business_model?: 'individual_bakery' | 'central_baker_satellite' | 'retail_bakery' | 'hybrid_bakery'; } export interface TenantSettings { @@ -62,7 +64,8 @@ export interface TenantSubscription { export interface TenantCreate { name: string; address?: string; - business_type?: 'individual' | 'central_workshop'; + business_type?: 'bakery' | 'coffee_shop' | 'pastry_shop' | 'restaurant'; + business_model?: 'individual_bakery' | 'central_baker_satellite' | 'retail_bakery' | 'hybrid_bakery'; postal_code: string; phone: string; description?: string; diff --git a/frontend/src/api/websocket/hooks.ts b/frontend/src/api/websocket/hooks.ts index ba010dd2..3779d8c3 100644 --- a/frontend/src/api/websocket/hooks.ts +++ b/frontend/src/api/websocket/hooks.ts @@ -120,8 +120,8 @@ export const useTrainingWebSocket = (jobId: string, tenantId?: string) => { const config = { url: actualTenantId - ? `ws://localhost:8002/api/v1/ws/tenants/${actualTenantId}/training/jobs/${jobId}/live` - : `ws://localhost:8002/api/v1/ws/tenants/unknown/training/jobs/${jobId}/live`, + ? `ws://localhost:8000/api/v1/ws/tenants/${actualTenantId}/training/jobs/${jobId}/live` + : `ws://localhost:8000/api/v1/ws/tenants/unknown/training/jobs/${jobId}/live`, reconnect: true, reconnectInterval: 3000, maxReconnectAttempts: 10 diff --git a/frontend/src/components/onboarding/SmartHistoricalDataImport.tsx b/frontend/src/components/onboarding/SmartHistoricalDataImport.tsx index c7adf106..3cf8f064 100644 --- a/frontend/src/components/onboarding/SmartHistoricalDataImport.tsx +++ b/frontend/src/components/onboarding/SmartHistoricalDataImport.tsx @@ -16,7 +16,9 @@ import { CheckCircle2, XCircle, ArrowRight, - Lightbulb + Lightbulb, + Building2, + Truck } from 'lucide-react'; import toast from 'react-hot-toast'; @@ -236,6 +238,43 @@ const SmartHistoricalDataImport: React.FC = ({ const renderBusinessModelInsight = (analysis: BusinessModelAnalysis) => { const modelConfig = { + individual_bakery: { + icon: Factory, + title: 'Panadería Individual', + description: 'Producción completa desde ingredientes básicos (harina, levadura, etc.)', + color: 'blue', + bgColor: 'bg-blue-50', + borderColor: 'border-blue-200', + textColor: 'text-blue-900' + }, + central_baker_satellite: { + icon: Truck, + title: 'Punto de Venta - Obrador Central', + description: 'Recibe productos semi-elaborados y los finaliza (horneado, decoración)', + color: 'amber', + bgColor: 'bg-amber-50', + borderColor: 'border-amber-200', + textColor: 'text-amber-900' + }, + retail_bakery: { + icon: Store, + title: 'Panadería de Distribución', + description: 'Vende productos terminados de proveedores externos', + color: 'green', + bgColor: 'bg-green-50', + borderColor: 'border-green-200', + textColor: 'text-green-900' + }, + hybrid_bakery: { + icon: Settings2, + title: 'Modelo Mixto', + description: 'Combina producción propia con productos de proveedores', + color: 'purple', + bgColor: 'bg-purple-50', + borderColor: 'border-purple-200', + textColor: 'text-purple-900' + }, + // Legacy fallbacks production: { icon: Factory, title: 'Panadería de Producción', @@ -265,7 +304,8 @@ const SmartHistoricalDataImport: React.FC = ({ } }; - const config = modelConfig[analysis.model]; + // Provide fallback if analysis.model is not in modelConfig + const config = modelConfig[analysis.model as keyof typeof modelConfig] || modelConfig.hybrid_bakery; const IconComponent = config.icon; return ( diff --git a/frontend/src/components/sales/SalesAnalyticsDashboard.tsx b/frontend/src/components/sales/SalesAnalyticsDashboard.tsx index 4e10f9c8..0a9f4a64 100644 --- a/frontend/src/components/sales/SalesAnalyticsDashboard.tsx +++ b/frontend/src/components/sales/SalesAnalyticsDashboard.tsx @@ -43,8 +43,8 @@ const SalesAnalyticsDashboard: React.FC = () => { } = useSales(); const { - ingredients: products, - loadIngredients: loadProducts, + items: products, + loadItems: loadProducts, isLoading: inventoryLoading } = useInventory(); @@ -159,11 +159,14 @@ const SalesAnalyticsDashboard: React.FC = () => { // Top products const topProducts = Object.entries(productPerformance) - .map(([productId, data]) => ({ - productId, - ...data as any, - avgPrice: data.revenue / data.units - })) + .map(([productId, data]) => { + const perf = data as { revenue: number; units: number; orders: number }; + return { + productId, + ...perf, + avgPrice: perf.revenue / perf.units + }; + }) .sort((a, b) => b.revenue - a.revenue) .slice(0, 5); diff --git a/frontend/src/components/sales/SalesDashboardWidget.tsx b/frontend/src/components/sales/SalesDashboardWidget.tsx index 00bb1120..e3d12d3f 100644 --- a/frontend/src/components/sales/SalesDashboardWidget.tsx +++ b/frontend/src/components/sales/SalesDashboardWidget.tsx @@ -160,7 +160,7 @@ const SalesDashboardWidget: React.FC = ({

Ventas de Hoy

{onViewAll && ( - )} diff --git a/frontend/src/components/sales/SalesManagementPage.tsx b/frontend/src/components/sales/SalesManagementPage.tsx index 2eef46f2..0970d5fc 100644 --- a/frontend/src/components/sales/SalesManagementPage.tsx +++ b/frontend/src/components/sales/SalesManagementPage.tsx @@ -10,7 +10,7 @@ import { Package, ShoppingCart, MapPin, - Grid3X3, + Grid, List, AlertCircle, TrendingUp, @@ -51,8 +51,8 @@ const SalesManagementPage: React.FC = () => { } = useSales(); const { - ingredients: products, - loadIngredients: loadProducts, + items: products, + loadItems: loadProducts, isLoading: inventoryLoading } = useInventory(); @@ -89,7 +89,9 @@ const SalesManagementPage: React.FC = () => { const loadSalesData = async () => { if (!user?.tenant_id) return; - const query: SalesDataQuery = {}; + const query: SalesDataQuery = { + tenant_id: user.tenant_id + }; if (filters.search) { query.search_term = filters.search; @@ -155,7 +157,9 @@ const SalesManagementPage: React.FC = () => { const handleExport = async () => { if (!user?.tenant_id) return; - const query: SalesDataQuery = {}; + const query: SalesDataQuery = { + tenant_id: user.tenant_id + }; if (filters.date_from) query.start_date = filters.date_from; if (filters.date_to) query.end_date = filters.date_to; if (filters.channel) query.sales_channel = filters.channel; @@ -386,7 +390,7 @@ const SalesManagementPage: React.FC = () => { onClick={() => setViewMode('grid')} className={`p-2 rounded ${viewMode === 'grid' ? 'bg-blue-100 text-blue-600' : 'text-gray-400 hover:text-gray-600'}`} > - +
-
- -
- - +
+
+
+ +
+
+

+ 🤖 Detección automática del tipo de negocio +

+

+ Nuestro sistema de IA analizará automáticamente tus datos de ventas para identificar + si eres una panadería, cafetería, pastelería o restaurante. No necesitas seleccionar nada manualmente. +

+
@@ -724,360 +593,30 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => ); } - // Use Smart Import by default, with option to switch to traditional - if (useSmartImport) { - return ( -
- {/* Header with import mode toggle */} -
-
-

- Importación Inteligente de Datos 🧠 -

-

- Nuestra IA creará automáticamente tu inventario desde tus datos históricos -

-
- - -
- - {/* Smart Import Component */} - { - // Mark sales data as uploaded and proceed to training - completeStep('sales_data_uploaded', { - smart_import: true, - records_imported: result.successful_imports, - import_job_id: result.import_job_id, - tenant_id: tenantId, - user_id: user?.id - }).then(() => { - setBakeryData(prev => ({ ...prev, hasHistoricalData: true })); - startTraining(); - }).catch(() => { - // Continue even if step completion fails - setBakeryData(prev => ({ ...prev, hasHistoricalData: true })); - startTraining(); - }); - }} - onBack={() => setUseSmartImport(false)} - /> -
- ); - } - - // Traditional import fallback + // Smart Import only return (
- {/* Header with import mode toggle */} -
-
-

- Datos Históricos (Modo Tradicional) -

-

- Sube tus datos y configura tu inventario manualmente -

-
- - -
- -

- Para obtener predicciones precisas, necesitamos tus datos históricos de ventas. - Puedes subir archivos en varios formatos. -

- -
-
-
-
- ! -
-
-
-

- Formatos soportados y estructura de datos -

-
-

Formatos aceptados:

-
-
-

📊 Hojas de cálculo:

-
    -
  • .xlsx (Excel moderno)
  • -
  • .xls (Excel clásico)
  • -
-
-
-

📄 Datos estructurados:

-
    -
  • .csv (Valores separados por comas)
  • -
  • .json (Formato JSON)
  • -
-
-
-

Columnas requeridas (en cualquier idioma):

-
    -
  • Fecha: fecha, date, datum (formato: YYYY-MM-DD, DD/MM/YYYY, etc.)
  • -
  • Producto: producto, product, item, articulo, nombre
  • -
  • Cantidad: cantidad, quantity, cantidad_vendida, qty
  • -
-
-
-
-
- -
-
- -
- - { - const file = e.target.files?.[0]; - if (file) { - // Validate file size (10MB limit) - const maxSize = 10 * 1024 * 1024; - if (file.size > maxSize) { - toast.error('El archivo es demasiado grande. Máximo 10MB.'); - return; - } - - // Update bakery data with the selected file - setBakeryData(prev => ({ - ...prev, - csvFile: file, - hasHistoricalData: true - })); - - toast.success(`Archivo ${file.name} seleccionado correctamente`); - - // Auto-validate the file after upload if tenantId exists - if (tenantId) { - setValidationStatus({ status: 'validating' }); - - try { - const validationResult = await validateSalesData(tenantId, file); - - if (validationResult.is_valid) { - setValidationStatus({ - status: 'valid', - message: validationResult.message, - records: validationResult.details?.total_records || 0 - }); - toast.success('¡Archivo validado correctamente!'); - } else { - setValidationStatus({ - status: 'invalid', - message: validationResult.message - }); - toast.error(`Error en validación: ${validationResult.message}`); - } - } catch (error: any) { - setValidationStatus({ - status: 'invalid', - message: 'Error al validar el archivo' - }); - toast.error('Error al validar el archivo'); - } - } else { - // If no tenantId yet, set to idle and wait for manual validation - setValidationStatus({ status: 'idle' }); - } - } - }} - className="hidden" - /> -
-
- - {bakeryData.csvFile ? ( -
-
-
-
- -
-

- {bakeryData.csvFile.name} -

-

- {(bakeryData.csvFile.size / 1024).toFixed(1)} KB • {bakeryData.csvFile.type || 'Archivo de datos'} -

-
-
- -
-
- - {/* Validation Section */} -
-
-

- Validación de datos -

- {validationStatus.status === 'validating' ? ( - - ) : validationStatus.status === 'valid' ? ( - - ) : validationStatus.status === 'invalid' ? ( - - ) : ( - - )} -
- - {validationStatus.status === 'idle' && tenantId ? ( -
-

- Valida tu archivo para verificar que tiene el formato correcto. -

- -
- ) : ( -
- {!tenantId ? ( -
-

- ⚠️ No se ha encontrado la panadería registrada. -

-

- Ve al paso anterior para registrar tu panadería o espera mientras se carga desde el servidor. -

-
- ) : validationStatus.status !== 'idle' ? ( - - ) : null} -
- )} - - {validationStatus.status === 'validating' && ( -

- Validando archivo... Por favor espera. -

- )} - - {validationStatus.status === 'valid' && ( -
-

- ✅ Archivo validado correctamente -

- {validationStatus.records && ( -

- {validationStatus.records} registros encontrados -

- )} - {validationStatus.message && ( -

- {validationStatus.message} -

- )} -
- )} - - {validationStatus.status === 'invalid' && ( -
-

- ❌ Error en validación -

- {validationStatus.message && ( -

- {validationStatus.message} -

- )} - -
- )} -
-
- ) : ( -
-
- -

- Archivo requerido: Selecciona un archivo con tus datos históricos de ventas -

-
-
- )} -
- - {/* Show switch to smart import suggestion if traditional validation fails */} - {validationStatus.status === 'invalid' && ( -
-
- -
-

- 💡 ¿Problemas con la validación? -

-

- Nuestra IA puede manejar archivos con formatos más flexibles y ayudarte a solucionar problemas automáticamente. -

- -
-
-
- )} + {/* Smart Import Component */} + { + // Mark sales data as uploaded and proceed to training + completeStep('sales_data_uploaded', { + smart_import: true, + records_imported: result.successful_imports, + import_job_id: result.import_job_id, + tenant_id: tenantId, + user_id: user?.id + }).then(() => { + setBakeryData(prev => ({ ...prev, hasHistoricalData: true })); + startTraining(); + }).catch(() => { + // Continue even if step completion fails + setBakeryData(prev => ({ ...prev, hasHistoricalData: true })); + startTraining(); + }); + }} + />
); @@ -1369,7 +908,7 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => {/* Navigation with Enhanced Accessibility - Hidden during training (step 3), completion (step 4), and smart import */} - {currentStep < 3 && !(currentStep === 2 && useSmartImport) && ( + {currentStep < 3 && currentStep !== 2 && (
{/* Dynamic Next Button with contextual text */}