Fix recipes step next button & start AI inventory redesign
🔧 Recipes Step Fix: - Add onComplete prop handling to RecipesSetupStep - Add "Next" button when minimum requirement met (recipes.length >= 1) - Show success indicator with recipe count - Button only visible when not in adding mode 🚧 AI Inventory Step Redesign (In Progress): - Updated InventoryItem interface to support both AI suggestions and manual entries - Added new fields: id, isSuggested, isExpanded, low_stock_threshold, reorder_point - Modified AI suggestion mapper to calculate inventory management defaults - Next: Need to redesign UI from checkbox-grid to expandable-card list - Next: Add manual ingredient addition form - Next: Move inventory creation from button to onComplete/onNext handler This is work in progress - UI redesign not yet complete.
This commit is contained in:
@@ -24,14 +24,15 @@ interface ProgressState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InventoryItem {
|
interface InventoryItem {
|
||||||
suggestion_id: string;
|
id: string; // Unique ID for UI tracking
|
||||||
original_name: string;
|
suggestion_id?: string; // Only for AI suggestions
|
||||||
suggested_name: string;
|
original_name?: string; // Only for AI suggestions
|
||||||
|
suggested_name: string; // The actual ingredient name
|
||||||
product_type: string;
|
product_type: string;
|
||||||
category: string;
|
category: string;
|
||||||
unit_of_measure: string;
|
unit_of_measure: string;
|
||||||
confidence_score: number;
|
confidence_score?: number; // Only for AI suggestions
|
||||||
estimated_shelf_life_days?: number;
|
estimated_shelf_life_days: number;
|
||||||
requires_refrigeration: boolean;
|
requires_refrigeration: boolean;
|
||||||
requires_freezing: boolean;
|
requires_freezing: boolean;
|
||||||
is_seasonal: boolean;
|
is_seasonal: boolean;
|
||||||
@@ -44,9 +45,12 @@ interface InventoryItem {
|
|||||||
frequency: number;
|
frequency: number;
|
||||||
};
|
};
|
||||||
// UI-specific fields
|
// UI-specific fields
|
||||||
selected: boolean;
|
isSuggested: boolean; // true for AI, false for manual
|
||||||
|
isExpanded: boolean; // for expand/collapse UI
|
||||||
stock_quantity: number;
|
stock_quantity: number;
|
||||||
cost_per_unit: number;
|
cost_per_unit: number;
|
||||||
|
low_stock_threshold: number;
|
||||||
|
reorder_point: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||||
@@ -170,7 +174,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
|||||||
setProgressState({ stage: 'preparing', progress: 90, message: 'Preparando sugerencias de inventario...' });
|
setProgressState({ stage: 'preparing', progress: 90, message: 'Preparando sugerencias de inventario...' });
|
||||||
|
|
||||||
// Convert API response to InventoryItem format - use exact backend structure plus UI fields
|
// Convert API response to InventoryItem format - use exact backend structure plus UI fields
|
||||||
const items: InventoryItem[] = classificationResponse.suggestions.map((suggestion: ProductSuggestionResponse) => {
|
const items: InventoryItem[] = classificationResponse.suggestions.map((suggestion: ProductSuggestionResponse, index: number) => {
|
||||||
// Calculate default stock quantity based on sales data
|
// Calculate default stock quantity based on sales data
|
||||||
const defaultStock = Math.max(
|
const defaultStock = Math.max(
|
||||||
Math.ceil((suggestion.sales_data?.average_daily_sales || 1) * 7), // 1 week supply
|
Math.ceil((suggestion.sales_data?.average_daily_sales || 1) * 7), // 1 week supply
|
||||||
@@ -182,8 +186,15 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
|||||||
suggestion.category === 'Baking Ingredients' ? 2.0 :
|
suggestion.category === 'Baking Ingredients' ? 2.0 :
|
||||||
3.0;
|
3.0;
|
||||||
|
|
||||||
|
// Calculate inventory management defaults
|
||||||
|
const minimumStock = Math.max(1, Math.ceil(defaultStock * 0.2));
|
||||||
|
const calculatedReorderPoint = Math.ceil(defaultStock * 0.3);
|
||||||
|
const reorderPoint = Math.max(minimumStock + 2, calculatedReorderPoint, minimumStock + 1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Exact backend fields
|
// UI tracking
|
||||||
|
id: `ai-${index}-${Date.now()}`,
|
||||||
|
// AI suggestion fields
|
||||||
suggestion_id: suggestion.suggestion_id,
|
suggestion_id: suggestion.suggestion_id,
|
||||||
original_name: suggestion.original_name,
|
original_name: suggestion.original_name,
|
||||||
suggested_name: suggestion.suggested_name,
|
suggested_name: suggestion.suggested_name,
|
||||||
@@ -191,7 +202,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
|||||||
category: suggestion.category,
|
category: suggestion.category,
|
||||||
unit_of_measure: suggestion.unit_of_measure,
|
unit_of_measure: suggestion.unit_of_measure,
|
||||||
confidence_score: suggestion.confidence_score,
|
confidence_score: suggestion.confidence_score,
|
||||||
estimated_shelf_life_days: suggestion.estimated_shelf_life_days,
|
estimated_shelf_life_days: suggestion.estimated_shelf_life_days || 30,
|
||||||
requires_refrigeration: suggestion.requires_refrigeration,
|
requires_refrigeration: suggestion.requires_refrigeration,
|
||||||
requires_freezing: suggestion.requires_freezing,
|
requires_freezing: suggestion.requires_freezing,
|
||||||
is_seasonal: suggestion.is_seasonal,
|
is_seasonal: suggestion.is_seasonal,
|
||||||
@@ -199,9 +210,12 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
|||||||
notes: suggestion.notes,
|
notes: suggestion.notes,
|
||||||
sales_data: suggestion.sales_data,
|
sales_data: suggestion.sales_data,
|
||||||
// UI-specific fields
|
// UI-specific fields
|
||||||
selected: suggestion.confidence_score > 0.7, // Auto-select high confidence items
|
isSuggested: true,
|
||||||
|
isExpanded: false, // Start collapsed
|
||||||
stock_quantity: defaultStock,
|
stock_quantity: defaultStock,
|
||||||
cost_per_unit: estimatedCost
|
cost_per_unit: estimatedCost,
|
||||||
|
low_stock_threshold: minimumStock,
|
||||||
|
reorder_point: reorderPoint
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface RecipeIngredientForm {
|
|||||||
ingredient_order: number;
|
ingredient_order: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Get tenant ID
|
// Get tenant ID
|
||||||
@@ -714,6 +714,26 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate }) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Navigation - Show Next button when minimum requirement met */}
|
||||||
|
{recipes.length >= 1 && !isAdding && (
|
||||||
|
<div className="flex items-center justify-between pt-6 border-t border-[var(--border-secondary)] mt-6">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-[var(--color-success)]">
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
{t('setup_wizard:recipes.minimum_met', '{{count}} recipe(s) added - Ready to continue!', { count: recipes.length })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => onComplete?.()}
|
||||||
|
className="px-6 py-2.5 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] transition-colors font-medium"
|
||||||
|
>
|
||||||
|
{t('common:next', 'Next')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user