import React, { useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Card, CardHeader, CardBody } from '../../ui/Card'; import { Button } from '../../ui/Button'; import { Input } from '../../ui/Input'; import { Textarea } from '../../ui'; import { Badge } from '../../ui/Badge'; import { Modal } from '../../ui/Modal'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../ui/Tabs'; import { Camera, CheckCircle, XCircle, AlertTriangle, Upload, Star, Target, FileText, Clock, User, Package } from 'lucide-react'; import { useCurrentTenant } from '../../../stores/tenant.store'; export interface InspectionCriteria { id: string; category: string; name: string; description: string; type: 'visual' | 'measurement' | 'taste' | 'texture' | 'temperature'; required: boolean; weight: number; acceptableCriteria: string; minValue?: number; maxValue?: number; unit?: string; } export interface InspectionResult { criteriaId: string; value: number | string | boolean; score: number; notes?: string; photos?: File[]; pass: boolean; timestamp: string; } export interface QualityInspectionData { batchId: string; productName: string; inspectionType: string; inspector: string; startTime: string; criteria: InspectionCriteria[]; results: InspectionResult[]; overallScore: number; overallPass: boolean; finalNotes: string; photos: File[]; correctiveActions: string[]; } export interface QualityInspectionProps { className?: string; batchId?: string; productName?: string; inspectionType?: string; criteria?: InspectionCriteria[]; onComplete?: (data: QualityInspectionData) => void; onCancel?: () => void; onSaveDraft?: (data: Partial) => void; } const DEFAULT_CRITERIA: InspectionCriteria[] = [ { id: 'color_uniformity', category: 'Visual', name: 'Color Uniformity', description: 'Evaluate the consistency of color across the product', type: 'visual', required: true, weight: 15, acceptableCriteria: 'Score 7 or higher', minValue: 1, maxValue: 10 }, { id: 'shape_integrity', category: 'Visual', name: 'Shape Integrity', description: 'Check if the product maintains its intended shape', type: 'visual', required: true, weight: 20, acceptableCriteria: 'Score 7 or higher', minValue: 1, maxValue: 10 }, { id: 'surface_texture', category: 'Texture', name: 'Surface Texture', description: 'Evaluate surface texture quality', type: 'texture', required: true, weight: 15, acceptableCriteria: 'Score 7 or higher', minValue: 1, maxValue: 10 }, { id: 'weight_accuracy', category: 'Measurement', name: 'Weight Accuracy', description: 'Measure actual weight vs target weight', type: 'measurement', required: true, weight: 20, acceptableCriteria: 'Within �5% of target', unit: 'g' }, { id: 'internal_texture', category: 'Texture', name: 'Internal Texture', description: 'Evaluate crumb structure and texture', type: 'texture', required: false, weight: 15, acceptableCriteria: 'Score 7 or higher', minValue: 1, maxValue: 10 }, { id: 'taste_quality', category: 'Taste', name: 'Taste Quality', description: 'Overall flavor and taste assessment', type: 'taste', required: false, weight: 15, acceptableCriteria: 'Score 7 or higher', minValue: 1, maxValue: 10 } ]; const QualityInspection: React.FC = ({ className, batchId = 'PROD-2024-0123-001', productName = 'Pan de Molde Integral', inspectionType = 'Final Quality Check', criteria = DEFAULT_CRITERIA, onComplete, onCancel, onSaveDraft }) => { const { t } = useTranslation(); const currentTenant = useCurrentTenant(); const [activeTab, setActiveTab] = useState('inspection'); const [inspectionData, setInspectionData] = useState>({ batchId, productName, inspectionType, inspector: currentTenant?.name || 'Inspector', startTime: new Date().toISOString(), criteria, results: [], finalNotes: '', photos: [], correctiveActions: [] }); const [currentCriteriaIndex, setCurrentCriteriaIndex] = useState(0); const [showPhotoModal, setShowPhotoModal] = useState(false); const [tempPhotos, setTempPhotos] = useState([]); const updateResult = useCallback((criteriaId: string, updates: Partial) => { setInspectionData(prev => { const existingResults = prev.results || []; const existingIndex = existingResults.findIndex(r => r.criteriaId === criteriaId); let newResults; if (existingIndex >= 0) { newResults = [...existingResults]; newResults[existingIndex] = { ...newResults[existingIndex], ...updates }; } else { newResults = [...existingResults, { criteriaId, value: '', score: 0, pass: false, timestamp: new Date().toISOString(), ...updates }]; } return { ...prev, results: newResults }; }); }, []); const getCriteriaResult = useCallback((criteriaId: string): InspectionResult | undefined => { return inspectionData.results?.find(r => r.criteriaId === criteriaId); }, [inspectionData.results]); const calculateOverallScore = useCallback((): number => { if (!inspectionData.results || inspectionData.results.length === 0) return 0; const totalWeight = criteria.reduce((sum, c) => sum + c.weight, 0); const weightedScore = inspectionData.results.reduce((sum, result) => { const criterion = criteria.find(c => c.id === result.criteriaId); return sum + (result.score * (criterion?.weight || 0)); }, 0); return totalWeight > 0 ? weightedScore / totalWeight : 0; }, [inspectionData.results, criteria]); const isInspectionComplete = useCallback((): boolean => { const requiredCriteria = criteria.filter(c => c.required); const completedRequired = requiredCriteria.filter(c => inspectionData.results?.some(r => r.criteriaId === c.id) ); return completedRequired.length === requiredCriteria.length; }, [criteria, inspectionData.results]); const handlePhotoUpload = useCallback((event: React.ChangeEvent) => { const files = Array.from(event.target.files || []); setTempPhotos(prev => [...prev, ...files]); }, []); const handleComplete = useCallback(() => { const overallScore = calculateOverallScore(); const overallPass = overallScore >= 7.0; // Configurable threshold const completedData: QualityInspectionData = { ...inspectionData as QualityInspectionData, overallScore, overallPass, photos: tempPhotos }; onComplete?.(completedData); }, [inspectionData, calculateOverallScore, tempPhotos, onComplete]); const currentCriteria = criteria[currentCriteriaIndex]; const currentResult = currentCriteria ? getCriteriaResult(currentCriteria.id) : undefined; const overallScore = calculateOverallScore(); const isComplete = isInspectionComplete(); const renderCriteriaInput = (criterion: InspectionCriteria) => { const result = getCriteriaResult(criterion.id); if (criterion.type === 'measurement') { return (
{ const value = parseFloat(e.target.value); const score = !isNaN(value) ? Math.min(10, Math.max(1, 8)) : 0; // Simplified scoring updateResult(criterion.id, { value: e.target.value, score, pass: score >= 7 }); }} /> {criterion.unit && (

Unit: {criterion.unit}

)}
); } // For visual, texture, taste types - use 1-10 scale return (
{[...Array(10)].map((_, i) => { const score = i + 1; const isSelected = result?.score === score; return ( ); })}

1-3: Poor | 4-6: Fair | 7-8: Good | 9-10: Excellent

Acceptable: {criterion.acceptableCriteria}

); }; return (

{t('quality.inspection.title', 'Quality Inspection')}

{productName} " {batchId} " {inspectionType}

{isComplete ? t('quality.status.complete', 'Complete') : t('quality.status.in_progress', 'In Progress')} {overallScore > 0 && ( = 7 ? 'success' : overallScore >= 5 ? 'warning' : 'error'}> {overallScore.toFixed(1)}/10 )}
{t('quality.tabs.inspection', 'Inspection')} {t('quality.tabs.photos', 'Photos')} {t('quality.tabs.summary', 'Summary')} {/* Progress Indicator */}
{criteria.map((criterion, index) => { const result = getCriteriaResult(criterion.id); const isCompleted = !!result; const isCurrent = index === currentCriteriaIndex; return ( ); })}
{/* Current Criteria */} {currentCriteria && (

{currentCriteria.name}

{currentCriteria.required ? t('quality.required', 'Required') : t('quality.optional', 'Optional')}

{currentCriteria.description}

Weight: {currentCriteria.weight}% Category: {currentCriteria.category} Type: {currentCriteria.type}
{/* Input Section */} {renderCriteriaInput(currentCriteria)} {/* Notes Section */}