Implement 5 UX enhancements for ingredient management

This commit implements the requested enhancements for the ingredient
quick-add system and batch management:

**1. Duplicate Detection**
- Real-time Levenshtein distance-based similarity checking
- Shows warning with top 3 similar ingredients (70%+ similarity)
- Prevents accidental duplicate creation
- Location: QuickAddIngredientModal.tsx

**2. Smart Category Suggestions**
- Auto-populates category based on ingredient name patterns
- Supports Spanish and English ingredient names
- Shows visual indicator when category is AI-suggested
- Pattern matching for: Baking, Dairy, Fruits, Vegetables, Meat, Seafood, Spices
- Location: ingredientHelpers.ts

**3. Quick Templates**
- 10 pre-configured common bakery ingredients
- One-click template application
- Templates include: Flour, Butter, Sugar, Eggs, Yeast, Milk, Chocolate, Vanilla, Salt, Cream
- Each template has sensible defaults (shelf life, refrigeration requirements)
- Location: QuickAddIngredientModal.tsx

**4. Batch Creation Mode**
- BatchAddIngredientsModal component for adding multiple ingredients at once
- Table-based interface for efficient data entry
- "Load from Templates" quick action
- Duplicate detection within batch
- Partial success handling (some ingredients succeed, some fail)
- Location: BatchAddIngredientsModal.tsx
- Integration: UploadSalesDataStep.tsx (2 buttons: "Add One" / "Add Multiple")

**5. Dashboard Alert for Incomplete Ingredients**
- IncompleteIngredientsAlert component on dashboard
- Queries ingredients with needs_review metadata flag
- Shows count badge and first 5 incomplete ingredients
- "Complete Information" button links to inventory page
- Only shows when incomplete ingredients exist
- Location: IncompleteIngredientsAlert.tsx
- Integration: DashboardPage.tsx

**New Files Created:**
- ingredientHelpers.ts - Utilities for duplicate detection, smart suggestions, templates
- BatchAddIngredientsModal.tsx - Batch ingredient creation component
- IncompleteIngredientsAlert.tsx - Dashboard alert component

**Files Modified:**
- QuickAddIngredientModal.tsx - Added duplicate detection, smart suggestions, templates
- UploadSalesDataStep.tsx - Integrated batch creation modal
- DashboardPage.tsx - Added incomplete ingredients alert

**Technical Highlights:**
- Levenshtein distance algorithm for fuzzy name matching
- Pattern-based category suggestions (supports 100+ ingredient patterns)
- Metadata tracking (needs_review, created_context)
- Real-time validation and error handling
- Responsive UI with animations
- Consistent with existing design system

All features built and tested successfully.
Build time: 21.29s
This commit is contained in:
Claude
2025-11-06 15:39:30 +00:00
parent 9162fc32a5
commit 000e352ef9
6 changed files with 987 additions and 16 deletions

View File

@@ -7,6 +7,7 @@ import { useValidateImportFile, useImportSalesData } from '../../../../api/hooks
import type { ImportValidationResponse } from '../../../../api/types/dataImport';
import type { ProductSuggestionResponse } from '../../../../api/types/inventory';
import { useAuth } from '../../../../contexts/AuthContext';
import { BatchAddIngredientsModal } from '../../inventory/BatchAddIngredientsModal';
interface UploadSalesDataStepProps {
onNext: () => void;
@@ -64,6 +65,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
// Form state for adding/editing
const [isAdding, setIsAdding] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [showBatchModal, setShowBatchModal] = useState(false);
const [formData, setFormData] = useState<InventoryItemForm>({
id: '',
name: '',
@@ -720,20 +722,36 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
</form>
</div>
) : (
<button
type="button"
onClick={() => setIsAdding(true)}
className="w-full p-4 border-2 border-dashed border-[var(--border-secondary)] rounded-lg hover:border-[var(--color-primary)] hover:bg-[var(--bg-secondary)] transition-colors group"
>
<div className="flex items-center justify-center gap-2 text-[var(--text-secondary)] group-hover:text-[var(--color-primary)]">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span className="font-medium">
Agregar Ingrediente Manualmente
</span>
</div>
</button>
<div className="grid grid-cols-2 gap-3">
<button
type="button"
onClick={() => setIsAdding(true)}
className="p-4 border-2 border-dashed border-[var(--border-secondary)] rounded-lg hover:border-[var(--color-primary)] hover:bg-[var(--bg-secondary)] transition-colors group"
>
<div className="flex items-center justify-center gap-2 text-[var(--text-secondary)] group-hover:text-[var(--color-primary)]">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span className="font-medium">
Agregar Uno
</span>
</div>
</button>
<button
type="button"
onClick={() => setShowBatchModal(true)}
className="p-4 border-2 border-dashed border-[var(--color-primary)]/30 rounded-lg hover:border-[var(--color-primary)] hover:bg-[var(--color-primary)]/5 transition-colors group"
>
<div className="flex items-center justify-center gap-2 text-[var(--color-primary)]">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span className="font-medium">
Agregar Varios
</span>
</div>
</button>
</div>
)}
{error && (
@@ -787,6 +805,35 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
</button>
</div>
)}
{/* Batch Add Modal */}
<BatchAddIngredientsModal
isOpen={showBatchModal}
onClose={() => setShowBatchModal(false)}
onCreated={(ingredients) => {
// Add all created ingredients to the list
const newItems: InventoryItemForm[] = ingredients.map(ing => ({
id: ing.id,
name: ing.name,
product_type: ing.product_type,
category: ing.category,
unit_of_measure: ing.unit_of_measure,
stock_quantity: 0,
cost_per_unit: ing.average_cost || 0,
estimated_shelf_life_days: ing.shelf_life_days || 30,
requires_refrigeration: ing.requires_refrigeration || false,
requires_freezing: ing.requires_freezing || false,
is_seasonal: ing.is_seasonal || false,
low_stock_threshold: ing.low_stock_threshold || 0,
reorder_point: ing.reorder_point || 0,
notes: ing.notes || '',
isSuggested: false,
}));
setInventoryItems([...inventoryItems, ...newItems]);
setShowBatchModal(false);
}}
tenantId={currentTenant?.id || ''}
/>
</div>
);
}