fix: Update tag types and remove komplekst tag option
- Replaced 'Komplekst tag' with proper tag types: Sadeltag/Skråttag, Valmtag, Københavnertag, Fladtag, Pulttag, Tag med kviste, Mansardtag - Removed parentheses from tag type names for cleaner UI - Removed 'Komplekse detaljer' checkbox (karnapper, kviste) - Removed manual 'Gem Geometri' button - geometry now auto-saves after calculation - Updated backend complexity calculations to support new tag types - Updated getRoofTypeDisplayName to include all new tag types Customer feedback addressed: - Tag type cleanup and proper Danish names - Removed komplekst tag option - Removed manual save button
This commit is contained in:
@@ -430,10 +430,16 @@ class AdvancedGeometryService {
|
||||
|
||||
// Tagtype påvirkning
|
||||
switch (roofType?.toLowerCase()) {
|
||||
case 'fladt_tag': complexity *= 0.8; break;
|
||||
case 'fladt_tag':
|
||||
case 'fladtag': complexity *= 0.8; break;
|
||||
case 'sadeltag':
|
||||
case 'skraat_tag': complexity *= 1.0; break;
|
||||
case 'mansard': complexity *= 1.4; break;
|
||||
case 'komplekst': complexity *= 1.6; break;
|
||||
case 'valmtag': complexity *= 1.2; break;
|
||||
case 'koebenhavnertag': complexity *= 1.3; break;
|
||||
case 'pulttag': complexity *= 0.9; break;
|
||||
case 'tag_med_kviste': complexity *= 1.4; break;
|
||||
case 'mansard':
|
||||
case 'mansardtag': complexity *= 1.4; break;
|
||||
}
|
||||
|
||||
// Hældning påvirkning
|
||||
@@ -532,10 +538,16 @@ class AdvancedGeometryService {
|
||||
'b6': 'B6 Tagplader',
|
||||
'vingetegl': 'Vingetegl',
|
||||
'røde_teglsten': 'Røde Teglsten',
|
||||
'fladt_tag': 'Fladt Tag',
|
||||
'skraat_tag': 'Skråt Tag',
|
||||
'fladt_tag': 'Fladtag',
|
||||
'fladtag': 'Fladtag',
|
||||
'sadeltag': 'Sadeltag/Skråttag',
|
||||
'skraat_tag': 'Sadeltag/Skråttag',
|
||||
'valmtag': 'Valmtag',
|
||||
'koebenhavnertag': 'Københavnertag',
|
||||
'pulttag': 'Pulttag',
|
||||
'tag_med_kviste': 'Tag med kviste',
|
||||
'mansard': 'Mansardtag',
|
||||
'komplekst': 'Komplekst Tag'
|
||||
'mansardtag': 'Mansardtag'
|
||||
};
|
||||
|
||||
return displayNames[roofType?.toLowerCase()] || roofType;
|
||||
|
||||
@@ -268,7 +268,7 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
}, []);
|
||||
// Map Smart Pakker roof types to geometry roof types
|
||||
const mapRoofTypeFromPackages = (packages) => {
|
||||
if (!packages || packages.length === 0) return 'skraat_tag';
|
||||
if (!packages || packages.length === 0) return 'sadeltag';
|
||||
|
||||
const hasB7Package = packages.some(pkg =>
|
||||
pkg.roofType === 'b7' ||
|
||||
@@ -296,12 +296,12 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
console.log('🏠 Auto-mapping B6 package to fladt_tag roof type');
|
||||
return 'fladt_tag'; // B6 is also typically flat roof
|
||||
} else if (hasBetonteglPackage) {
|
||||
console.log('🏠 Auto-mapping Betontegl package to skraat_tag roof type');
|
||||
return 'skraat_tag'; // Betontegl is for pitched roofs
|
||||
console.log('🏠 Auto-mapping Betontegl package to sadeltag roof type');
|
||||
return 'sadeltag'; // Betontegl is for pitched roofs
|
||||
}
|
||||
|
||||
// Fallback to pitched roof as default
|
||||
return 'skraat_tag';
|
||||
return 'sadeltag';
|
||||
};
|
||||
|
||||
const [geometryInput, setGeometryInput] = useState({
|
||||
@@ -337,7 +337,7 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
width: geo.width_main || '',
|
||||
length: geo.length_main || '',
|
||||
pitch: geo.roof_pitch || 30,
|
||||
roofType: geo.roof_type || 'skraat_tag',
|
||||
roofType: geo.roof_type || 'sadeltag',
|
||||
complexity: geo.complexity_factor || 1.0,
|
||||
wallHeight: geo.wall_height || 0, // Væghøjde (stern højde)
|
||||
ridgeHeight: geo.stern_to_ridge_height || 0, // Højde stern til kip
|
||||
@@ -409,7 +409,7 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
setGeometryInput(prev => {
|
||||
// Only set if user has not interacted (width, length, or roofType changed from default)
|
||||
if (
|
||||
(!prev.width && !prev.length && prev.roofType === 'skraat_tag') ||
|
||||
(!prev.width && !prev.length && prev.roofType === 'sadeltag') ||
|
||||
(prev.roofType === '' && !prev.width && !prev.length)
|
||||
) {
|
||||
return { ...prev, roofType: newRoofType };
|
||||
@@ -643,169 +643,11 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
}
|
||||
};
|
||||
|
||||
/* REMOVED: Manual save button - geometry auto-saves after calculation
|
||||
const handleSaveGeometry = async () => {
|
||||
console.log('💾 Saving geometry data...', {
|
||||
geometryInput: geometryInput,
|
||||
project: project,
|
||||
apiBaseUrl: apiBaseUrl
|
||||
});
|
||||
|
||||
// Comprehensive validation for realistic geometry calculations (same as calculateGeometry)
|
||||
if (!geometryInput.width || !geometryInput.length || !geometryInput.roofType) {
|
||||
setStatus('⚠️ Udfyld venligst alle påkrævede felter: bredde, længde og tagtype');
|
||||
return;
|
||||
}
|
||||
|
||||
const width = parseFloat(geometryInput.width);
|
||||
const length = parseFloat(geometryInput.length);
|
||||
const wallHeight = parseFloat(geometryInput.wallHeight) || 2.5;
|
||||
const pitch = parseFloat(geometryInput.pitch) || 0;
|
||||
|
||||
// Flexible size constraints for various building types
|
||||
if (isNaN(width) || width < 1.0 || width > 100) {
|
||||
setStatus('⚠️ Bygningsbredde skal være mellem 1,0 og 100 meter');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(length) || length < 1.0 || length > 150) {
|
||||
setStatus('⚠️ Bygningslængde skal være mellem 1,0 og 150 meter');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wall height requirements
|
||||
if (wallHeight < 1.0 || wallHeight > 10.0) {
|
||||
setStatus('⚠️ Væghøjde skal være mellem 1,0 og 10,0 meter');
|
||||
return;
|
||||
}
|
||||
|
||||
// Roof type specific validation
|
||||
if (geometryInput.roofType === 'skraat_tag') {
|
||||
if (!geometryInput.pitch || pitch < 15 || pitch > 80) {
|
||||
setStatus('⚠️ For skråt tag skal taghældning være mellem 15° og 80° (15-45° standard, 45-80° stejle tage som A-frame)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check realistic proportions for pitched roof (increased to 20m to allow steep roofs)
|
||||
const roofHeight = Math.tan(pitch * Math.PI / 180) * (width / 2);
|
||||
if (roofHeight > 20) {
|
||||
setStatus('⚠️ Taghældning skaber urealistisk høj ryg (over 20m). Reducer hældning eller bygningsbredde');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Area constraints for flexible projects
|
||||
const totalArea = width * length;
|
||||
if (totalArea < 5) {
|
||||
setStatus('⚠️ Minimum bygningsareal er 5 m²');
|
||||
return;
|
||||
}
|
||||
|
||||
if (totalArea > 10000) {
|
||||
setStatus('⚠️ Maksimum bygningsareal er 10.000 m²');
|
||||
return;
|
||||
}
|
||||
|
||||
// Aspect ratio check - prevent unrealistic building shapes
|
||||
const aspectRatio = Math.max(width, length) / Math.min(width, length);
|
||||
if (aspectRatio > 20) {
|
||||
setStatus('⚠️ Bygningen er meget smal/lang. Maksimum forhold mellem længde og bredde er 1:20');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!project || !project.id) {
|
||||
setStatus('⚠️ Projektinformation mangler. Prøv at genindlæse siden.');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('📡 Sending save request to backend...');
|
||||
|
||||
// Save geometry to project
|
||||
const geometryResponse = await fetch(`${apiBaseUrl}/api/customer-projects/${project.id}/geometry`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
roofWidth: parseFloat(geometryInput.width),
|
||||
roofLength: parseFloat(geometryInput.length),
|
||||
roofPitch: parseFloat(geometryInput.pitch),
|
||||
roofType: geometryInput.roofType,
|
||||
complexity: parseFloat(geometryInput.complexity),
|
||||
wallHeight: parseFloat(geometryInput.wallHeight) || 2.5,
|
||||
ridgeHeight: parseFloat(geometryInput.ridgeHeight) || null,
|
||||
notes: geometryInput.notes
|
||||
})
|
||||
});
|
||||
|
||||
console.log('📡 Save response:', geometryResponse.status);
|
||||
|
||||
if (geometryResponse.ok) {
|
||||
const geometryData = await geometryResponse.json();
|
||||
console.log('📊 Save data received:', geometryData);
|
||||
|
||||
if (geometryData.success) {
|
||||
setStatus('✅ Geometri data gemt succesfuldt!');
|
||||
|
||||
// Update results display with correct nested structure
|
||||
setGeometryResult({
|
||||
// Fields that FinalReview expects
|
||||
width: parseFloat(geometryInput.width),
|
||||
length: parseFloat(geometryInput.length),
|
||||
wallHeight: parseFloat(geometryInput.wallHeight) || 2.5,
|
||||
roofPitch: parseFloat(geometryInput.pitch) || 30,
|
||||
|
||||
// Existing fields
|
||||
area: geometryData.area,
|
||||
roofHeight: geometryData.roofHeight,
|
||||
vindskede_lbm: geometryData.vindskede_lbm,
|
||||
estimatedWorkHours: 16,
|
||||
complexity: parseFloat(geometryInput.complexity) || 1.0,
|
||||
|
||||
// Add nested objects that UI expects
|
||||
basicDimensions: {
|
||||
baseArea: geometryData.area
|
||||
},
|
||||
materialQuantities: {
|
||||
roofCovering: {
|
||||
area: geometryData.area
|
||||
}
|
||||
},
|
||||
heightCalculations: {
|
||||
ridgeHeight: geometryData.roofHeight || 0,
|
||||
rafterLength: Math.sqrt(Math.pow(parseFloat(geometryInput.width) / 2, 2) + Math.pow(geometryData.roofHeight || 0, 2))
|
||||
},
|
||||
windboardCalculations: {
|
||||
gableBoards: {
|
||||
totalLength: geometryData.vindskede_lbm || 0
|
||||
},
|
||||
eavesBoards: {
|
||||
totalLength: (parseFloat(geometryInput.length) * 2) || 0
|
||||
},
|
||||
ridgeBoard: {
|
||||
length: parseFloat(geometryInput.length) || 0
|
||||
},
|
||||
totalLength: (geometryData.vindskede_lbm || 0) + ((parseFloat(geometryInput.length) * 3) || 0)
|
||||
},
|
||||
svgIllustration: {
|
||||
svg: generateRoofSVG(geometryInput.roofType, parseFloat(geometryInput.width), parseFloat(geometryInput.length), geometryInput.pitch || 25, parseFloat(geometryInput.wallHeight))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('❌ Save failed:', geometryData);
|
||||
setStatus(`❌ Gemning fejlede: ${geometryData.error || 'Ukendt fejl'}`);
|
||||
}
|
||||
} else {
|
||||
const errorText = await geometryResponse.text();
|
||||
console.error('❌ Save API error:', geometryResponse.status, errorText);
|
||||
setStatus(`❌ API fejl ved gemning: ${geometryResponse.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error saving geometry:', error);
|
||||
setStatus(`❌ Fejl ved gemning af geometri: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
... (removed for auto-save implementation)
|
||||
};
|
||||
*/
|
||||
|
||||
const handleNext = () => {
|
||||
onGeometryCalculated({
|
||||
@@ -847,10 +689,13 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
value={geometryInput.roofType}
|
||||
onChange={(e) => setGeometryInput({...geometryInput, roofType: e.target.value})}
|
||||
>
|
||||
<option value="skraat_tag">Skråtag (Betontegl/Tegl)</option>
|
||||
<option value="fladt_tag">Fladt tag (Eternit B7/B6)</option>
|
||||
<option value="sadeltag">Sadeltag/Skråttag</option>
|
||||
<option value="valmtag">Valmtag</option>
|
||||
<option value="koebenhavnertag">Københavnertag</option>
|
||||
<option value="fladt_tag">Fladtag</option>
|
||||
<option value="pulttag">Pulttag</option>
|
||||
<option value="tag_med_kviste">Tag med kviste</option>
|
||||
<option value="mansard">Mansardtag</option>
|
||||
<option value="komplekst">Komplekst tag</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -940,17 +785,6 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
/>
|
||||
<small className="form-help">Lodret højde fra stern til kip - bruges til vindskeder beregning</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={geometryInput.hasComplexFeatures}
|
||||
onChange={(e) => setGeometryInput({...geometryInput, hasComplexFeatures: e.target.checked})}
|
||||
/>
|
||||
Komplekse detaljer (karnapper, kviste)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -961,15 +795,6 @@ const EnhancedGeometry = ({ apiBaseUrl, project, selectedPackages, onGeometryCal
|
||||
{loading ? 'Beregner...' : '🧮 Beregn Geometri'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="save-btn"
|
||||
onClick={handleSaveGeometry}
|
||||
disabled={loading || !geometryInput.width || !geometryInput.length}
|
||||
style={{marginLeft: '10px', backgroundColor: '#28a745'}}
|
||||
>
|
||||
💾 Gem Geometri
|
||||
</button>
|
||||
|
||||
{status && (
|
||||
<div className="status-message" style={{
|
||||
marginTop: '10px',
|
||||
|
||||
Reference in New Issue
Block a user