@@ -3,6 +3,7 @@ Vue.component('PreorderRimoTypeMap', {
rawRimoData : [ ] ,
mapMarkers : [ ] ,
fcpMarkers : [ ] ,
missingBuildingMarkers : [ ] , // For manually added faults
faults : { } ,
isLoading : false ,
window ,
@@ -19,12 +20,15 @@ Vue.component('PreorderRimoTypeMap', {
showOnlyFaults : false ,
showFcps : true ,
showFaultsModal : false ,
showMissingBuildingModal : false , // For new context menu modal
missingBuildingData : null , // For new context menu modal
userIdToNameMap : new Map ( ) ,
editingFault : null ,
faultReasons : [
{ value : 'building_type' , text : 'Gebäudetyp ist falsch' } ,
{ value : 'home_count' , text : 'Anzahl der Wohneinheiten ist falsch' } ,
{ value : 'not_existent' , text : 'Gebäude existiert nicht' } ,
{ value : 'missing_building' , text : 'Gebäude fehlt (manuell markiert)' } , // New reason
{ value : 'other' , text : 'Sonstiges/Bemerkung' }
] ,
rimoTypeDefs : {
@@ -44,6 +48,10 @@ Vue.component('PreorderRimoTypeMap', {
filterOptions ( ) {
return Object . entries ( this . rimoTypeDefs ) . map ( ( [ value , defs ] ) => ( { value , ... defs } ) ) ;
} ,
// Options for the new "missing building" modal
rimoTypeOptionsForMissing ( ) {
return Object . entries ( this . rimoTypeDefs ) . map ( ( [ value , defs ] ) => ( { value , text : defs . text } ) ) ;
} ,
filteredMapMarkers ( ) {
let rimoMarkers = this . mapMarkers ;
@@ -52,24 +60,48 @@ Vue.component('PreorderRimoTypeMap', {
const fault = this . faults [ marker . hausnummerId ] ;
return fault && ! fault . done ;
} ) ;
// Also filter missing building markers if showOnlyFaults is true
this . missingBuildingMarkers = this . missingBuildingMarkers . filter ( marker => {
const fault = this . faults [ marker . hausnummerId ] ;
return fault && ! fault . done ;
} ) ;
}
if ( this . activeFilters . length > 0 ) {
rimoMarkers = rimoMarkers . filter ( marker => this . activeFilters . includes ( marker . rimoType ) ) ;
// We don't filter missingBuildingMarkers by rimoType unless we want to
this . missingBuildingMarkers = this . missingBuildingMarkers . filter ( marker => {
const fault = this . faults [ marker . hausnummerId ] ;
return fault && this . activeFilters . includes ( fault . rimo _type ) ;
} ) ;
}
return this . showFcp s ? [ ... rimoMarkers , ... this . fcpMarkers ] : rimoMarkers ;
const allMarker s = [ ... rimoMarkers , ... this . missingBuildingMarkers ] ;
return this . showFcps ? [ ... allMarkers , ... this . fcpMarkers ] : allMarkers ;
} ,
faultsForModal ( ) {
if ( ! this . rawRimoData . length ) return [ ] ;
if ( ! this . rawRimoData && ! this . faults ) return [ ] ;
return Object . entries ( this . faults ) . map ( ( [ hausnummerId , faultData ] ) => {
cons t rimoItem = this . rawRimoData . find ( item => item . hausnummer _id == hausnummerId ) ;
if ( ! rimoItem ) return null ;
le t rimoItem = null ;
let address = '' ;
let rimo _id = '' ;
if ( hausnummerId . startsWith ( 'missing-' ) ) {
const typeDef = this . rimoTypeDefs [ faultData . rimo _type ] || this . rimoTypeDefs . other ;
address = ` Fehlendes Gebäude ( ${ typeDef . text } ) bei ${ faultData . lat . toFixed ( 5 ) } , ${ faultData . lng . toFixed ( 5 ) } ` ;
rimo _id = 'N/A (Fehlend)' ;
} else {
rimoItem = this . rawRimoData . find ( item => item . hausnummer _id == hausnummerId ) ;
if ( ! rimoItem ) return null ; // Don't show faults for buildings not in the current dataset
address = ` ${ rimoItem . strasse _name } ${ rimoItem . hausnummer } , ${ rimoItem . plz _name } ${ rimoItem . ortschaft _name } ` ;
rimo _id = rimoItem . rimo _id ;
}
return {
... faultData ,
hausnummerId ,
rimo _id : rimoItem . rimo _id ,
address : ` ${ rimoItem . strasse _name } ${ rimoItem . hausnummer } , ${ rimoItem . plz _name } ${ rimoItem . ortschaft _name } ` ,
rimo _id ,
address ,
translated _reasons : faultData . reasons . map ( r => this . faultReasons . find ( fr => fr . value === r ) ? . text || r ) ,
done _by _user : this . userIdToNameMap . get ( String ( faultData . done _by ) ) || ` User # ${ faultData . done _by } `
} ;
@@ -87,7 +119,7 @@ Vue.component('PreorderRimoTypeMap', {
this . fetchAllMapData ( ) ;
} else {
localStorage . removeItem ( 'rimoMapSelectedCampaign' ) ;
this . mapMarkers = [ ] ; this . fcpMarkers = [ ] ; this . faults = { } ; this . activeFilters = [ ] ;
this . mapMarkers = [ ] ; this . fcpMarkers = [ ] ; this . faults = { } ; this . activeFilters = [ ] ; this . missingBuildingMarkers = [ ] ;
}
} ,
showFcps ( newVal ) {
@@ -114,28 +146,45 @@ Vue.component('PreorderRimoTypeMap', {
}
const storedShowFcps = localStorage . getItem ( 'rimoMapShowFcps' ) ;
this . showFcps = storedShowFcps !== null ? JSON . parse ( storedShowFcps ) : true ;
// Register window functions for popup buttons
window . updateEditingFault = this . updateTempFault . bind ( this ) ;
window . saveEditingFault = this . saveFaults . bind ( this ) ;
window . saveEditingFault = this . saveEditing Fault . bind ( this ) ;
// --- FIX: Renamed function and window property to avoid reference errors ---
window . markFaultDonePopup = this . markFaultDonePopup . bind ( this ) ;
} ,
beforeDestroy ( ) {
// Unregister window functions
delete window . updateEditingFault ;
delete window . saveEditingFault ;
// --- FIX: Use matching name for deletion ---
delete window . markFaultDonePopup ;
} ,
methods : {
async fetchAllMapData ( ) {
if ( ! this . selectedCampaign ) return ;
this . isLoading = true ;
this . mapMarkers = [ ] ; this . fcpMarkers = [ ] ; this . faults = { } ; this . activeFilters = [ ] ;
this . mapMarkers = [ ] ; this . fcpMarkers = [ ] ; this . faults = { } ; this . activeFilters = [ ] ; this . missingBuildingMarkers = [ ] ;
try {
await this . fetchFaultData ( ) ;
await this . fetchFaultData ( ) ; // This will now also populate missingBuildingMarkers
await Promise . all ( [ this . fetchRimoData ( ) , this . fetchFCPData ( ) ] ) ;
} finally {
this . isLoading = false ;
}
} ,
async fetchFaultData ( ) {
this . missingBuildingMarkers = [ ] ; // Reset missing markers
const res = await axios . get ( ` ${ window . TT _CONFIG . BASE _PATH } /Preorder/RimoTypeMapGetFaults ` , { params : { preordercampaign _id : this . selectedCampaign } } ) ;
if ( res . data . success ) this . faults = res . data . faults && typeof res . data . faults === 'object' && ! Array . isArray ( res . data . faults ) ? res . data . faults : { } ;
if ( res . data . success ) {
this . faults = res . data . faults && typeof res . data . faults === 'object' && ! Array . isArray ( res . data . faults ) ? res . data . faults : { } ;
// Process missing building faults into markers
Object . entries ( this . faults ) . forEach ( ( [ faultId , faultData ] ) => {
if ( faultId . startsWith ( 'missing-' ) ) {
this . addMissingBuildingMarker ( faultId , faultData ) ;
}
} ) ;
}
} ,
async fetchRimoData ( ) {
const res = await axios . post ( this . fetchUrl , { campaignId : this . selectedCampaign } ) ;
@@ -224,24 +273,62 @@ Vue.component('PreorderRimoTypeMap', {
const otherText = faultData . other ? ` <li>Sonstiges: ${ faultData . other } </li> ` : '' ;
return ` <div class="fault-indicator-popup"><strong><i class="fas fa-exclamation-triangle"></i> Gemeldeter Fehler:</strong><ul> ${ reasonsList } ${ otherText } </ul></div> ` ;
} ,
_generateBuildingFaultFormHtml ( itemGroup , faultData ) {
_generateBuildingFaultFormHtml ( itemGroup , faultData , isExistingFault ) {
const formInputs = this . faultReasons . map ( reason => {
const isChecked = faultData . reasons . includes ( reason . value ) ;
const isDisabled = reason . value === 'missing_building' ; // Disable 'missing_building' checkbox
const otherInput = ( reason . value === 'other' ) ? ` <textarea class="fault-other-textarea ${ isChecked ? '' : 'hidden' } " oninput="window.updateEditingFault('other_text', this.value)"> ${ faultData . other || '' } </textarea> ` : '' ;
return ` <label><input type="checkbox" onchange="window.updateEditingFault(' ${ reason . value } ', this.checked)" ${ isChecked ? 'checked' : '' } > ${ reason . text } </label> ${ otherInput } ` ;
return ` <label><input type="checkbox" onchange="window.updateEditingFault(' ${ reason . value } ', this.checked)" ${ isChecked ? 'checked' : '' } ${ isDisabled ? 'disabled' : '' } > ${ reason . text } </label> ${ otherInput } ` ;
} ) . join ( '' ) ;
return ` <h6>Fehler melden/bearbeiten:</h6> ${ formInputs } <button class="btn btn-primary btn-sm mt-2" onclick="window.saveEditingFault()">Fehler Speichern</button> ` ;
// --- FIX: Updated logic to only show button if fault exists and is not done ---
const doneButton = isExistingFault && ! faultData . done
? ` <button class="btn btn-success btn-sm mt-2 ml-2" onclick="window.markFaultDonePopup()">Als erledigt markieren</button> `
: '' ;
return ` <h6>Fehler melden/bearbeiten:</h6> ${ formInputs } <button class="btn btn-primary btn-sm mt-2" onclick="window.saveEditingFault()">Änderungen Speichern</button> ${ doneButton } ` ;
} ,
generateBuildingPopupHtml ( itemGroup ) {
// --- FIX: Check if the fault actually exists ---
const isExistingFault = ! ! this . faults [ itemGroup . hausnummer _id ] ;
const currentFault = this . faults [ itemGroup . hausnummer _id ] || { reasons : [ ] , other : '' , done : false } ;
this . editingFault = { hausnummerId : itemGroup . hausnummer _id , data : JSON . parse ( JSON . stringify ( currentFault ) ) } ;
const detailsHtml = this . _generateBuildingDetailsHtml ( itemGroup ) ;
const faultFormHtml = this . _generateBuildingFaultFormHtml ( itemGroup , this . editingFault . data ) ;
const faultDisplay Html = this . editingFault . data . done ? ` <div class="alert alert-success"><i class="fas fa-check-circle"></i> Dieser Fehler wurde von <strong> ${ this . userIdToNameMap . get ( String ( this . editingFault . data . done _by ) ) || ` User # ${ this . editingFault . data . done _by } ` } </strong> am ${ new Date ( this . editingFault . data . done _at ) . toLocaleDateString ( ) } als erledigt markiert.</div> ` : this . _generateBuildingFaultDisplayHtml ( this . editingFault . data ) ;
// --- FIX: Pass the isExistingFault flag ---
const faultForm Html = this . _generateBuildingFaultFormHtml ( itemGroup , this . editingFault . data , isExistingFault ) ;
const faultDisplayHtml = this . editingFault . data . done
? ` <div class="alert alert-success"><i class="fas fa-check-circle"></i> Dieser Fehler wurde von <strong> ${ this . userIdToNameMap . get ( String ( this . editingFault . data . done _by ) ) || ` User # ${ this . editingFault . data . done _by } ` } </strong> am ${ new Date ( this . editingFault . data . done _at ) . toLocaleDateString ( ) } als erledigt markiert.</div> `
: this . _generateBuildingFaultDisplayHtml ( this . editingFault . data ) ;
return ` <div class="building-popup-content"> ${ detailsHtml } ${ faultDisplayHtml } <hr class="my-2"><div class="fault-reporting-form"> ${ faultFormHtml } </div></div> ` ;
} ,
// New method for "missing building" popups
generateMissingBuildingPopupHtml ( faultId ) {
const currentFault = this . faults [ faultId ] || { reasons : [ ] , other : '' , done : false } ;
const isExistingFault = ! ! this . faults [ faultId ] ;
this . editingFault = { hausnummerId : faultId , data : JSON . parse ( JSON . stringify ( currentFault ) ) } ;
const typeDef = this . rimoTypeDefs [ currentFault . rimo _type ] || this . rimoTypeDefs . other ;
const detailsHtml = `
<div class="mb-2">
<h5 class="mb-2 mt-1"><i class="fas fa-map-pin mr-2"></i>Fehlendes Gebäude</h5>
<strong>Gemeldeter Typ:</strong> ${ typeDef . text } <br>
<strong>Koordinaten:</strong> ${ currentFault . lat . toFixed ( 6 ) } , ${ currentFault . lng . toFixed ( 6 ) } <br>
<strong>Links:</strong>
<a href="https://www.google.com/maps?q= ${ currentFault . lat } , ${ currentFault . lng } " target="_blank" class="text-primary"><i class="fas fa-map-marker-alt mr-1"></i>Karte</a>
</div> ` ;
// --- FIX: Pass the isExistingFault flag ---
const faultFormHtml = this . _generateBuildingFaultFormHtml ( { } , this . editingFault . data , isExistingFault ) ; // Pass empty itemGroup
const faultDisplayHtml = this . editingFault . data . done
? ` <div class="alert alert-success"><i class="fas fa-check-circle"></i> Dieser Fehler wurde von <strong> ${ this . userIdToNameMap . get ( String ( this . editingFault . data . done _by ) ) || ` User # ${ this . editingFault . data . done _by } ` } </strong> am ${ new Date ( this . editingFault . data . done _at ) . toLocaleDateString ( ) } als erledigt markiert.</div> `
: this . _generateBuildingFaultDisplayHtml ( this . editingFault . data ) ;
return ` <div class="building-popup-content"> ${ detailsHtml } ${ faultDisplayHtml } <hr class="my-2"><div class="fault-reporting-form"> ${ faultFormHtml } </div></div> ` ; } ,
updateTempFault ( reason , value ) {
if ( ! this . editingFault ) return ;
// If user interacts with a "done" fault, mark it as "not done" again
if ( this . editingFault . data . done ) {
this . editingFault . data . done = false ; this . editingFault . data . done _by = null ; this . editingFault . data . done _at = null ;
}
@@ -251,6 +338,8 @@ Vue.component('PreorderRimoTypeMap', {
const index = fault . reasons . indexOf ( reason ) ;
if ( value && index === - 1 ) fault . reasons . push ( reason ) ;
else if ( ! value && index > - 1 ) fault . reasons . splice ( index , 1 ) ;
// Toggle visibility of 'other' textarea
if ( reason === 'other' ) {
const popup = this . $refs . ttMap ? . map ? . _popup ;
if ( popup ? . isOpen ( ) ) {
@@ -264,54 +353,202 @@ Vue.component('PreorderRimoTypeMap', {
}
}
} ,
async saveFaults ( hausnummerIdToUpdate ) {
// --- FIX: Renamed this method ---
async markFaultDonePopup ( ) {
if ( ! this . editingFault ) return ;
const hausnummerId = this . editingFault . hausnummerId ;
const faultData = {
... this . editingFault . data ,
done : true ,
done _by : window . TT _CONFIG . USER _ID ,
done _at : new Date ( ) . toISOString ( )
} ;
this . $set ( this . faults , hausnummerId , faultData ) ;
this . editingFault . data = faultData ; // Update local state for popup refresh
await this . saveFaults ( hausnummerId , true ) ; // Save and stay in place
} ,
// Modified saveFaults to handle "stayInPlace" and "missing" buildings
async saveFaults ( hausnummerIdToUpdate , stayInPlace = false ) {
const hausnummerId = hausnummerIdToUpdate || this . editingFault ? . hausnummerId ;
if ( ! hausnummerId ) { if ( this . editingFault ) this . editingFault = null ; return ; }
if ( this . editingFault && this . editingFault . hausnummerId === hausnummerId ) this . $set ( this . faults , hausnummerId , this . editingFault . data ) ;
// Store current view to prevent map from moving
const center = this . $refs . ttMap ? . map . getCenter ( ) ;
const zoom = this . $refs . ttMap ? . map . getZoom ( ) ;
if ( this . editingFault && this . editingFault . hausnummerId === hausnummerId ) {
this . $set ( this . faults , hausnummerId , this . editingFault . data ) ;
}
const res = await axios . post ( ` ${ window . TT _CONFIG . BASE _PATH } /Preorder/RimoTypeMapSaveFaults ` , { campaignId : this . selectedCampaign , faults : this . faults } ) ;
if ( res . data . success ) {
window . notify ( 'success' , 'Fehlerbericht gespeichert.' ) ;
this . $refs . ttMap ? . map . closePopup ( ) ;
// Refetch fault data to be in sync (backend might have changed something)
await this . fetchFaultData ( ) ;
// Update marker icon in place
const mapComponent = this . $refs . ttMap ;
if ( mapComponent && mapComponent . markerLayer ) {
mapComponent . markerLayer . eachLayer ( marker => {
if ( marker . tt _hausnummerId == hausnummerId ) {
const rimoItem = this . rawRimoData . find ( item => item . hausnummer _id == hausnummerId ) ;
if ( rimoItem ) {
const isNot2Connect = rimoItem . rimo _op _state === 'Not2Connect' ;
const rimoType = this . getNormalizedRimoType ( rimoItem . rimo _type ) ;
const fault = this . faults [ hausnummerId ] ;
const hasFault = fault && ! fault . done ;
const fault = this . faults [ hausnummerId ] ;
const hasFault = fault && ! fault . done ;
if ( marker . options . isMissingBuilding ) {
// Logic for missing building marker
const rimoType = fault . rimo _type || 'other' ;
const markerIconDef = this . getMarkerIcon ( rimoType ) ;
const newIcon = L . divIcon ( {
className : ` custom-div-icon marker- ${ rimoType } ${ hasFault ? 'marker-has-fault' : '' } ` ,
html : ` <div class="rimo-marker ${ markerIconDef . class } ${ isNot2Connect ? 'marker-not-to-connect' : '' } "><i class="${ markerIconDef . icon } rimo-icon"></i></div> ` ,
className : ` custom-div-icon marker- ${ rimoType } ${ hasFault ? 'marker-has-fault' : '' } marker-missing-building `,
html : ` <div class="rimo-marker ${ markerIconDef . class } "><i class=" ${ markerIconDef . icon } rimo-icon"></i></div> ` ,
iconSize : [ 30 , 30 ] ,
iconAnchor : [ 15 , 30 ]
} ) ;
marker . setIcon ( newIcon ) ;
} else {
// Logic for existing building marker
const rimoItem = this . rawRimoData . find ( item => item . hausnummer _id == hausnummerId ) ;
if ( rimoItem ) {
const isNot2Connect = rimoItem . rimo _op _state === 'Not2Connect' ;
const rimoType = this . getNormalizedRimoType ( rimoItem . rimo _type ) ;
const markerIconDef = this . getMarkerIcon ( rimoType ) ;
const newIcon = L . divIcon ( {
className : ` custom-div-icon marker- ${ rimoType } ${ hasFault ? 'marker-has-fault' : '' } ` ,
html : ` <div class="rimo-marker ${ markerIconDef . class } ${ isNot2Connect ? 'marker-not-to-connect' : '' } "><i class=" ${ markerIconDef . icon } rimo-icon"></i></div> ` ,
iconSize : [ 30 , 30 ] ,
iconAnchor : [ 15 , 30 ]
} ) ;
marker . setIcon ( newIcon ) ;
}
}
}
} ) ;
}
} else window . notify ( 'error' , 'Fehlerbericht konnte nicht gespeichert werden.' ) ;
this . editingFault = null ;
// Handle popup state
if ( stayInPlace ) {
const markerInstance = mapComponent ? . markerLayer ? . getLayers ( ) . find ( m => m . tt _hausnummerId == hausnummerId ) ;
if ( markerInstance && mapComponent . map . _popup && mapComponent . map . _popup . isOpen ( ) && mapComponent . map . _popup . _source === markerInstance ) {
let newPopupContent ;
if ( markerInstance . options . isMissingBuilding ) {
newPopupContent = await this . generateMissingBuildingPopupHtml ( hausnummerId ) ;
} else {
const rimoItem = this . rawRimoData . find ( item => item . hausnummer _id == hausnummerId ) ;
if ( rimoItem ) newPopupContent = await this . generateBuildingPopupHtml ( rimoItem ) ;
}
if ( newPopupContent ) {
markerInstance . setPopupContent ( newPopupContent ) ; // Refresh popup content
} else {
mapComponent . map . closePopup ( ) ; // Close if we can't refresh
}
} else if ( ! markerInstance ) {
mapComponent . map . closePopup ( ) ; // Close if marker somehow disappeared (e.g. filtered out)
}
} else {
this . $refs . ttMap ? . map . closePopup ( ) ;
}
// Restore view
if ( center && zoom ) {
this . $refs . ttMap ? . map . setView ( center , zoom , { animate : false , noMoveStart : true } ) ;
}
} else {
window . notify ( 'error' , 'Fehlerbericht konnte nicht gespeichert werden.' ) ;
}
if ( ! stayInPlace ) {
this . editingFault = null ;
}
} ,
async markFaultAsDone ( hausnummerId ) {
if ( ! hausnummerId || ! this . faults [ hausnummerId ] ) return ;
this . $set ( this . faults , hausnummerId , { ... this . faults [ hausnummerId ] , done : true , done _by : window . TT _CONFIG . USER _ID , done _at : new Date ( ) . toISOString ( ) } ) ;
await this . saveFaults ( hausnummerId ) ;
await this . saveFaults ( hausnummerId , false ) ; // Save, don't stay in place (closes modal)
} ,
zoomToFaultMarker ( hausnummerId ) {
const map = this . $refs . ttMap ? . map ;
const markerLayer = this . $refs . ttMap ? . markerLayer ;
if ( ! map || ! markerLayer ) return window . notify ( 'error' , 'Kartenkomponente ist nicht bereit.' ) ;
// Find marker (works for normal and missing building markers)
const markerInstance = markerLayer . getLayers ( ) . find ( m => m . tt _hausnummerId == hausnummerId ) ;
if ( ! markerInstance ) return window . notify ( 'warning' , 'Marker konnte nicht auf der Karte gefunden werden.' ) ;
this . showFaultsModal = false ;
map . flyTo ( markerInstance . getLatLng ( ) , 19 , { duration : 1 } ) ;
setTimeout ( ( ) => markerLayer . zoomToShowLayer ( markerInstance , ( ) => markerInstance . openPopup ( ) ) , 1100 ) ;
} ,
// New method to handle right-click context menu
handleMapContextMenu ( e ) {
if ( ! this . selectedCampaign ) return ; // Don't allow if no campaign is selected
e . originalEvent . preventDefault ( ) ;
this . missingBuildingData = { lat : e . latlng . lat , lng : e . latlng . lng , rimo _type : null } ;
this . showMissingBuildingModal = true ;
} ,
// New method to save the missing building
async saveMissingBuilding ( ) {
if ( ! this . missingBuildingData || ! this . missingBuildingData . rimo _type ) {
window . notify ( 'warning' , 'Bitte einen RIMO-Typ auswählen.' ) ;
return ;
}
const newFaultId = 'missing-' + Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) ;
const newFaultData = {
reasons : [ 'missing_building' ] ,
other : 'Vom Benutzer als fehlend markiert.' ,
done : false ,
created _by : window . TT _CONFIG . USER _ID ,
created _at : new Date ( ) . toISOString ( ) ,
lat : this . missingBuildingData . lat ,
lng : this . missingBuildingData . lng ,
rimo _type : this . missingBuildingData . rimo _type
} ;
this . $set ( this . faults , newFaultId , newFaultData ) ;
this . addMissingBuildingMarker ( newFaultId , newFaultData ) ; // Add marker locally
this . showMissingBuildingModal = false ;
this . missingBuildingData = null ;
await this . saveFaults ( newFaultId , false ) ; // Save to backend
} ,
// New method to add marker for missing building
addMissingBuildingMarker ( faultId , faultData ) {
const rimoType = faultData . rimo _type || 'other' ;
const markerIconDef = this . getMarkerIcon ( rimoType ) ;
const hasFault = faultData && ! faultData . done ;
const newIcon = L . divIcon ( {
className : ` custom-div-icon marker-missing-building marker- ${ rimoType } ${ hasFault ? 'marker-has-fault' : '' } ` ,
html : ` <div class="rimo-marker ${ markerIconDef . class } "><i class=" ${ markerIconDef . icon } rimo-icon"></i></div> ` ,
iconSize : [ 30 , 30 ] ,
iconAnchor : [ 15 , 30 ]
} ) ;
const newMarker = {
lat : faultData . lat ,
lng : faultData . lng ,
hausnummerId : faultId , // Root property for tt-map to find
options : {
icon : newIcon ,
isMissingBuilding : true , // Custom flag
asyncPopupContent : ( ) => this . generateMissingBuildingPopupHtml ( faultId )
}
} ;
// Avoid duplicates - update existing if found
const existingIndex = this . missingBuildingMarkers . findIndex ( m => m . hausnummerId === faultId ) ;
if ( existingIndex > - 1 ) {
this . $set ( this . missingBuildingMarkers , existingIndex , newMarker ) ;
} else {
this . missingBuildingMarkers . push ( newMarker ) ;
}
} ,
getNormalizedRimoType ( type ) {
const lowerType = ( type || '' ) . toLowerCase ( ) ;
if ( lowerType . includes ( 'greenfield' ) ) return 'greenfield' ;
@@ -336,10 +573,20 @@ Vue.component('PreorderRimoTypeMap', {
const color = this . rimoTypeDefs [ filterValue ] ? . color || '#6c757d' ;
return this . isFilterActive ( filterValue ) ? { backgroundColor : color , borderColor : color , color : 'white' } : { color : color , backgroundColor : 'white' , borderColor : color } ;
} ,
saveEditingFault ( ) {
this . saveFaults ( ) ;
} ,
} ,
template : `
<div id="PreorderRimoTypeMap">
<tt-map ref="ttMap" :markers-data="filteredMapMarkers" :loading="isLoading" :config="mapConfig" :show-logo="true">
<tt-map ref="ttMap"
:markers-data="filteredMapMarkers"
:loading="isLoading"
:config="mapConfig"
:show-logo="true"
:contextmenu="true"
@contextmenu="handleMapContextMenu"
>
<template v-slot:tools>
<div class="main-filter-container">
<div class="map-filter-container">
@@ -387,6 +634,12 @@ Vue.component('PreorderRimoTypeMap', {
</div>
<span class="legend-text">Not2Connect</span>
</div>
<div class="legend-item">
<div class="legend-icon">
<div class="rimo-marker marker-residential marker-missing-building" style="width: 24px; height: 24px; border-width: 2px;"><i class="fas fa-home rimo-icon" style="font-size: 12px;"></i></div>
</div>
<span class="legend-text">Fehlendes Gebäude</span>
</div>
<div class="mobile-only-legend-buttons mt-3" v-if="selectedCampaign">
<tt-button :href="window.TT_CONFIG.BASE_PATH + '/Preorder/RimoTypeMapFaultsPDFAction?preordercampaign_id=' + selectedCampaign"
@@ -397,6 +650,7 @@ Vue.component('PreorderRimoTypeMap', {
</template>
</tt-map>
<!-- Faults List Modal -->
<div v-if="showFaultsModal" class="modal fade show" style="display: block; background-color: rgba(0,0,0,0.5);" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
@@ -411,7 +665,7 @@ Vue.component('PreorderRimoTypeMap', {
<div class="d-flex justify-content-between align-items-center">
<div class="pr-3">
<strong :class="{ 'text-muted': fault.done }">{{ fault.address }}</strong>
<div class="text-muted mb-1">Rimo ID: {{ fault.rimo_id }}</div>
<div class="text-muted mb-1" v-if="!fault.hausnummerId.startsWith('missing-')" >Rimo ID: {{ fault.rimo_id }}</div>
<div v-if="!fault.done" class="mt-1">
<ul class="mb-0 small pl-3 text-danger font-weight-bold">
<li v-for="reason in fault.translated_reasons" :key="reason">{{ reason }}</li>
@@ -437,6 +691,34 @@ Vue.component('PreorderRimoTypeMap', {
</div>
</div>
</div>
<!-- New Missing Building Modal -->
<div v-if="showMissingBuildingModal" class="modal fade show" style="display: block; background-color: rgba(0,0,0,0.5);" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Fehlendes Gebäude markieren</h5>
<button type="button" class="close" @click="showMissingBuildingModal = false; missingBuildingData = null;" aria-label="Close"><span aria-hidden="true">× </span></button>
</div>
<div class="modal-body">
<p>Markieren Sie ein neues, fehlendes Gebäude an der Position:</p>
<p v-if="missingBuildingData"><strong>Lat:</strong> {{ missingBuildingData.lat.toFixed(6) }}, <strong>Lng:</strong> {{ missingBuildingData.lng.toFixed(6) }}</p>
<tt-select v-if="missingBuildingData"
v-model="missingBuildingData.rimo_type"
:options="rimoTypeOptionsForMissing"
label="RIMO-Typ des fehlenden Gebäudes"
:row="false"
/>
</div>
<div class="modal-footer">
<tt-button text="Abbrechen" @click="showMissingBuildingModal = false; missingBuildingData = null;" additional-class="btn-secondary"/>
<tt-button text="Speichern" @click="saveMissingBuilding" additional-class="btn-primary" icon="fas fa-save"/>
</div>
</div>
</div>
</div>
</div>
`
} ) ;
} ) ;