import React, { useState, useEffect } from 'react'; const defaultWorkBreakdown = [ { task: 'Forberedelse og opstilling', hours: '', allocatedHours: '', description: 'Afdækning, værktøj, sikkerhed' }, { task: 'Nedrivning af eksisterende', hours: '', allocatedHours: '', description: 'Fjernelse af gamle materialer' }, { task: 'Montering af nye lægter', hours: '', allocatedHours: '', description: 'C18 taglægter efter forskrifter' }, { task: 'Montering af tagplader', hours: '', allocatedHours: '', description: 'Nye tagplader inkl. skæring' }, { task: 'Rygning og afslutning', hours: '', allocatedHours: '', description: 'Ventileret rygning og finish' }, { task: 'Oprydning og bortkørsel', hours: '', allocatedHours: '', description: 'Rengøring og affaldsbortkørsel' } ]; const LaborInput = ({ apiBaseUrl, project, geometry, existingLabor, onComplete }) => { const [formData, setFormData] = useState({ carpenterCount: 2, totalWorkHours: '', totalAllocatedHours: '', hourlyRate: 580, // Fixed rate workBreakdown: defaultWorkBreakdown, specialConditions: '', notes: '' }); const [historicalData, setHistoricalData] = useState(null); const [showHistorical, setShowHistorical] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); // Load historical data for similar projects useEffect(() => { const loadHistoricalData = async () => { try { const response = await fetch(`${apiBaseUrl}/api/customer-projects/${project.id}/historical-data?category=tagreparation&roofType=${geometry?.roof_type || ''}`); const data = await response.json(); if (data.success) { setHistoricalData(data); } } catch (error) { console.error('Error loading historical labor data:', error); } }; if (project?.id) { loadHistoricalData(); } }, [apiBaseUrl, project?.id, geometry?.roof_type]); // Load existing labor data if available useEffect(() => { if (existingLabor) { const workBreakdown = existingLabor.work_breakdown ? JSON.parse(existingLabor.work_breakdown) : defaultWorkBreakdown; const allocatedBreakdown = existingLabor.allocated_hours_breakdown ? JSON.parse(existingLabor.allocated_hours_breakdown) : null; // Merge allocated hours into work breakdown if available if (allocatedBreakdown) { workBreakdown.forEach((item, index) => { if (allocatedBreakdown[index]) { item.allocatedHours = allocatedBreakdown[index].allocatedHours || ''; } }); } setFormData({ carpenterCount: existingLabor.carpenter_count || 2, totalWorkHours: existingLabor.total_work_hours || '', totalAllocatedHours: existingLabor.allocated_hours_breakdown ? JSON.parse(existingLabor.allocated_hours_breakdown).reduce((sum, item) => sum + (parseInt(item.allocatedHours) || 0), 0) : '', hourlyRate: existingLabor.hourly_rate || 580, workBreakdown: workBreakdown, specialConditions: existingLabor.special_conditions || '', notes: existingLabor.notes || '' }); } }, [existingLabor]); // Calculate estimated hours based on geometry when component loads useEffect(() => { if (geometry && !existingLabor) { const area = parseFloat(geometry.total_area); const complexity = parseFloat(geometry.complexity_factor); if (area && complexity) { const baseHoursPerSqm = 0.8; const estimatedTotal = Math.round(area * baseHoursPerSqm * complexity); // Distribute hours across tasks const distributedBreakdown = [ { task: 'Forberedelse og opstilling', hours: Math.round(estimatedTotal * 0.10), allocatedHours: '', description: 'Afdækning, værktøj, sikkerhed' }, { task: 'Nedrivning af eksisterende', hours: Math.round(estimatedTotal * 0.20), allocatedHours: '', description: 'Fjernelse af gamle materialer' }, { task: 'Montering af nye lægter', hours: Math.round(estimatedTotal * 0.25), allocatedHours: '', description: 'C18 taglægter efter forskrifter' }, { task: 'Montering af tagplader', hours: Math.round(estimatedTotal * 0.30), allocatedHours: '', description: 'Nye tagplader inkl. skæring' }, { task: 'Rygning og afslutning', hours: Math.round(estimatedTotal * 0.10), allocatedHours: '', description: 'Ventileret rygning og finish' }, { task: 'Oprydning og bortkørsel', hours: Math.round(estimatedTotal * 0.05), allocatedHours: '', description: 'Rengøring og affaldsbortkørsel' } ]; setFormData(prev => ({ ...prev, totalWorkHours: estimatedTotal, workBreakdown: distributedBreakdown })); } } }, [geometry, existingLabor]); const handleInputChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleBreakdownChange = (index, field, value) => { const updatedBreakdown = [...formData.workBreakdown]; updatedBreakdown[index] = { ...updatedBreakdown[index], [field]: (field === 'hours' || field === 'allocatedHours') ? parseInt(value) || 0 : value }; setFormData(prev => ({ ...prev, workBreakdown: updatedBreakdown })); // Update total hours when breakdown changes if (field === 'hours') { const totalFromBreakdown = updatedBreakdown.reduce((sum, item) => sum + (parseInt(item.hours) || 0), 0); setFormData(prev => ({ ...prev, totalWorkHours: totalFromBreakdown })); } // Update total allocated hours when allocated breakdown changes if (field === 'allocatedHours') { const totalAllocatedFromBreakdown = updatedBreakdown.reduce((sum, item) => sum + (parseInt(item.allocatedHours) || 0), 0); setFormData(prev => ({ ...prev, totalAllocatedHours: totalAllocatedFromBreakdown })); } }; const calculateTotalCost = () => { return (parseInt(formData.totalWorkHours) || 0) * formData.hourlyRate; }; const applyHistoricalData = (historicalProject) => { // Auto-fill form with data from historical project const area = parseFloat(geometry?.total_area || 0); const historicalArea = parseFloat(historicalProject.roof_area || 0); // Scale hours based on area difference if applicable let scaledHours = parseFloat(historicalProject.total_hours || 0); if (area && historicalArea && historicalArea > 0) { scaledHours = (scaledHours * area) / historicalArea; } setFormData(prev => ({ ...prev, totalWorkHours: Math.round(scaledHours), notes: `Baseret på lignende projekt: ${historicalProject.project_name} (${historicalProject.roof_area}m²)` })); }; const calculateDurationDays = () => { const totalHours = parseInt(formData.totalWorkHours) || 0; const carpenters = parseInt(formData.carpenterCount) || 1; const hoursPerDay = 8; return Math.ceil(totalHours / (carpenters * hoursPerDay)); }; // AI-baseret timeestimering const getAIWorkHourEstimate = async () => { if (!geometry?.total_area || !geometry?.roof_type) { setError('Geometri data mangler for AI estimering'); return; } setLoading(true); setError(''); try { // Først hent statistik for lignende projekter const statsResponse = await fetch( `${apiBaseUrl}/api/work-hour-statistics/${geometry.roof_type}?areaMin=${geometry.total_area * 0.7}&areaMax=${geometry.total_area * 1.3}` ); const statsData = await statsResponse.json(); if (statsData.success && statsData.data.hasData) { // Brug statistik data til at foreslå timer const suggestedHours = Math.round( parseFloat(statsData.data.averageHoursPerM2) * parseFloat(geometry.total_area) ); setFormData(prev => ({ ...prev, totalWorkHours: Math.max(suggestedHours, 8), // Minimum 8 timer carpenterCount: statsData.data.averageCarpenters || 2 })); alert(`🤖 AI Forslag baseret på ${statsData.data.projectCount} lignende projekter:\n` + `• Anbefalede timer: ${Math.max(suggestedHours, 8)}\n` + `• Gennemsnit timer/m²: ${statsData.data.averageHoursPerM2}\n` + `• Anbefalede tømrere: ${statsData.data.averageCarpenters || 2}`); } else { // Fallback til API baseret estimering hvis ingen historisk data const estimateResponse = await fetch(`${apiBaseUrl}/api/estimate-work-hours`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ roof_type: geometry.roof_type, total_area: parseFloat(geometry.total_area), complexity_factor: parseFloat(geometry.complexity_factor || 1.0) }) }); const estimateData = await estimateResponse.json(); if (estimateData.success) { setFormData(prev => ({ ...prev, totalWorkHours: estimateData.data.estimatedHours })); alert(`🤖 AI Estimering (standard beregning):\n` + `• Anbefalede timer: ${estimateData.data.estimatedHours}\n` + `• Baseret på: ${geometry.total_area} m² ${geometry.roof_type}`); } else { throw new Error('AI estimering fejlede'); } } } catch (error) { console.error('Error getting AI work hour estimate:', error); setError('Fejl ved AI timeestimering: ' + error.message); } finally { setLoading(false); } }; const handleSubmit = async (e) => { e.preventDefault(); if (!formData.totalWorkHours) { setError('Total arbejdstimer er påkrævet'); return; } setLoading(true); setError(''); try { // Convert workBreakdown to laborEntries format that backend expects const laborEntries = formData.workBreakdown .filter(item => item.hours && parseInt(item.hours) > 0) .map(item => ({ taskDescription: item.task, estimatedHours: parseInt(item.hours), allocatedHours: parseInt(item.allocatedHours) || 0, hourlyRate: formData.hourlyRate, notes: item.description || '' })); const laborData = { laborEntries: laborEntries, carpenterCount: parseInt(formData.carpenterCount), specialConditions: formData.specialConditions, totalAllocatedHours: parseInt(formData.totalAllocatedHours) || 0 }; console.log('📦 Sending labor data:', laborData); const response = await fetch(`${apiBaseUrl}/api/customer-projects/${project.id}/labor`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(laborData) }); console.log('📡 Response status:', response.status); const data = await response.json(); console.log('📥 Response data:', data); if (data.success) { console.log('✅ Labor saved successfully!'); onComplete(data.labor); } else { console.log('❌ Server returned error:', data.error); setError(data.error || 'Fejl ved gemning af arbejdstimer'); } } catch (error) { console.error('💥 Network/JavaScript error:', error); let errorMessage = 'Netværksfejl ved gemning af arbejdstimer'; if (error.name === 'TypeError' && error.message.includes('fetch')) { errorMessage = 'Kan ikke forbinde til serveren. Tjek din internetforbindelse.'; } else if (error.name === 'SyntaxError') { errorMessage = 'Serveren returnerede ugyldige data. Prøv igen.'; } else if (error.message) { errorMessage = `Fejl: ${error.message}`; } setError(errorMessage); } finally { setLoading(false); } }; return (
Registrer arbejdstimer og tømrere til projektet
{project.task_name} - {project.roof_area}m² {project.roof_type}