Add improved production UI 3

This commit is contained in:
Urtzi Alfaro
2025-09-23 19:24:22 +02:00
parent 7f871fc933
commit 7892c5a739
47 changed files with 6211 additions and 267 deletions

View File

@@ -19,11 +19,14 @@ import { statusColors } from '../../../styles/colors';
import { productionService, type QualityCheckResponse, QualityCheckStatus } from '../../../api';
import { ProductionBatchResponse } from '../../../api/types/production';
import { useCurrentTenant } from '../../../stores/tenant.store';
import { useQualityTemplatesForStage, useExecuteQualityCheck } from '../../../api/hooks/qualityTemplates';
import { ProcessStage, type QualityCheckTemplate, type QualityCheckExecutionRequest } from '../../../api/types/qualityTemplates';
export interface QualityCheckModalProps {
isOpen: boolean;
onClose: () => void;
batch: ProductionBatchResponse;
processStage?: ProcessStage;
onComplete?: (result: QualityCheckResult) => void;
}
@@ -220,54 +223,72 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
isOpen,
onClose,
batch,
processStage,
onComplete
}) => {
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const [activeTab, setActiveTab] = useState('inspection');
const [selectedTemplate, setSelectedTemplate] = useState<string>('visual_inspection');
const [selectedTemplate, setSelectedTemplate] = useState<QualityCheckTemplate | null>(null);
const [currentCriteriaIndex, setCurrentCriteriaIndex] = useState(0);
const [results, setResults] = useState<Record<string, QualityCheckResult>>({});
const [results, setResults] = useState<Record<string, any>>({});
const [finalNotes, setFinalNotes] = useState('');
const [photos, setPhotos] = useState<File[]>([]);
const [loading, setLoading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const template = QUALITY_CHECK_TEMPLATES[selectedTemplate as keyof typeof QUALITY_CHECK_TEMPLATES];
const currentCriteria = template?.criteria[currentCriteriaIndex];
const currentResult = currentCriteria ? results[currentCriteria.id] : undefined;
// Get available templates for the current stage
const currentStage = processStage || batch.current_process_stage;
const {
data: templatesData,
isLoading: templatesLoading,
error: templatesError
} = useQualityTemplatesForStage(tenantId, currentStage!, true, {
enabled: !!currentStage
});
const updateResult = useCallback((criterionId: string, updates: Partial<QualityCheckResult>) => {
// Execute quality check mutation
const executeQualityCheckMutation = useExecuteQualityCheck(tenantId);
// Initialize selected template
React.useEffect(() => {
if (templatesData?.templates && templatesData.templates.length > 0 && !selectedTemplate) {
setSelectedTemplate(templatesData.templates[0]);
}
}, [templatesData?.templates, selectedTemplate]);
const availableTemplates = templatesData?.templates || [];
const updateResult = useCallback((field: string, value: any, score?: number) => {
setResults(prev => ({
...prev,
[criterionId]: {
criterionId,
value: '',
score: 0,
pass: false,
timestamp: new Date().toISOString(),
...prev[criterionId],
...updates
[field]: {
value,
score: score !== undefined ? score : (typeof value === 'boolean' ? (value ? 10 : 0) : value),
pass_check: score !== undefined ? score >= 7 : (typeof value === 'boolean' ? value : value >= 7),
timestamp: new Date().toISOString()
}
}));
}, []);
const calculateOverallScore = useCallback((): number => {
if (!template || Object.keys(results).length === 0) return 0;
if (!selectedTemplate || Object.keys(results).length === 0) return 0;
const totalWeight = template.criteria.reduce((sum, c) => sum + c.weight, 0);
const weightedScore = Object.values(results).reduce((sum, result) => {
const criterion = template.criteria.find(c => c.id === result.criterionId);
return sum + (result.score * (criterion?.weight || 0));
}, 0);
const templateWeight = selectedTemplate.weight || 1;
const resultValues = Object.values(results);
return totalWeight > 0 ? weightedScore / totalWeight : 0;
}, [template, results]);
if (resultValues.length === 0) return 0;
const averageScore = resultValues.reduce((sum: number, result: any) => sum + (result.score || 0), 0) / resultValues.length;
return Math.min(10, averageScore * templateWeight);
}, [selectedTemplate, results]);
const getCriticalFailures = useCallback((): string[] => {
if (!template) return [];
if (!selectedTemplate) return [];
const failures: string[] = [];
template.criteria.forEach(criterion => {
selectedTemplate.criteria.forEach(criterion => {
if (criterion.isCritical && results[criterion.id]) {
const result = results[criterion.id];
if (criterion.type === 'boolean' && !result.value) {
@@ -279,7 +300,7 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
});
return failures;
}, [template, results]);
}, [selectedTemplate, results]);
const handlePhotoUpload = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
@@ -287,7 +308,7 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
}, []);
const handleComplete = async () => {
if (!template) return;
if (!selectedTemplate) return;
setLoading(true);
try {
@@ -297,7 +318,7 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
const qualityData: QualityCheckData = {
batchId: batch.id,
checkType: template.id,
checkType: selectedTemplate.id,
inspector: currentTenant?.name || 'Inspector',
startTime: new Date().toISOString(),
results: Object.values(results),
@@ -316,7 +337,7 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
// Create quality check via API
const checkData = {
batch_id: batch.id,
check_type: template.id,
check_type: selectedTemplate.id,
check_time: new Date().toISOString(),
quality_score: overallScore / 10, // Convert to 0-1 scale
pass_fail: passed,
@@ -434,7 +455,7 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
const overallScore = calculateOverallScore();
const criticalFailures = getCriticalFailures();
const isComplete = template?.criteria.filter(c => c.required).every(c => results[c.id]) || false;
const isComplete = selectedTemplate?.criteria.filter(c => c.required).every(c => results[c.id]) || false;
const statusIndicator: StatusIndicatorConfig = {
color: statusColors.inProgress.primary,
@@ -484,11 +505,11 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
</TabsList>
<TabsContent value="inspection" className="space-y-6">
{template && (
{selectedTemplate && (
<>
{/* Progress Indicator */}
<div className="grid grid-cols-6 gap-2">
{template.criteria.map((criterion, index) => {
{selectedTemplate.criteria.map((criterion, index) => {
const result = results[criterion.id];
const isCompleted = !!result;
const isCurrent = index === currentCriteriaIndex;
@@ -559,8 +580,8 @@ export const QualityCheckModal: React.FC<QualityCheckModalProps> = ({
</Button>
<Button
variant="primary"
onClick={() => setCurrentCriteriaIndex(Math.min(template.criteria.length - 1, currentCriteriaIndex + 1))}
disabled={currentCriteriaIndex === template.criteria.length - 1}
onClick={() => setCurrentCriteriaIndex(Math.min(selectedTemplate.criteria.length - 1, currentCriteriaIndex + 1))}
disabled={currentCriteriaIndex === selectedTemplate.criteria.length - 1}
>
Siguiente
</Button>