From 1d3d47486585747667e78aae985639a5b7b48406 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 11 Apr 2024 13:29:30 +0200 Subject: [PATCH 001/109] feat: optimization mode add mode for Optimization request to ORS API from maps client --- src/config-examples/app-config-example.js | 1 + src/pages/maps/maps.route.js | 19 ++++ src/resources/constants.js | 11 ++- src/support/app-modes/app-mode.js | 3 +- .../app-modes/strategies/optimization-mode.js | 63 ++++++++++++ .../ors-response-extractor-builder.js | 6 +- .../response-extractors/v2/optimization.js | 99 +++++++++++++++++++ .../route-data.js | 2 + src/support/ors-api-runner.js | 30 ++++++ src/support/routes-resolver.js | 3 + 10 files changed, 232 insertions(+), 5 deletions(-) create mode 100644 src/support/app-modes/strategies/optimization-mode.js create mode 100644 src/support/map-data-services/ors-response-data-extractors/response-extractors/v2/optimization.js diff --git a/src/config-examples/app-config-example.js b/src/config-examples/app-config-example.js index ba25bcb96..771e3b615 100755 --- a/src/config-examples/app-config-example.js +++ b/src/config-examples/app-config-example.js @@ -40,6 +40,7 @@ const appConfig = { disabledActionsForPlacesAndDirections: [], // // Possible values: `addPlaceInput`, `clearPlaces`, `reverseRoute`, `roundtrip`, `routeImporter` supportsPlacesAndDirections: true, // If the whole places and directions feature is supported/enabled in the application supportsIsochrones: true, // If isochrones is supported/enabled in the application + supportsOptimization: true, // If optimization is enabled supportsMapFiltersOnSidebar: true, // if the filters options box is present/enabled in the app supportsDirections: true, // If the directions functionality is available sidebarStartsOpenInHighResolution: false, // if the sidebar must start open in high resolution diff --git a/src/pages/maps/maps.route.js b/src/pages/maps/maps.route.js index 7017a28d2..dd1b8b1e9 100755 --- a/src/pages/maps/maps.route.js +++ b/src/pages/maps/maps.route.js @@ -10,6 +10,7 @@ const placePath = RoutesResolver.place() const directionsPath = RoutesResolver.directions() const searchPath = RoutesResolver.search() const isochronesPath = RoutesResolver.isochronesPath() +const optimizationPath = RoutesResolver.optimizationPath() const embedParameters = '/:embed?/:locale?' // Build the optional placesNames Path @@ -103,6 +104,24 @@ const mapRoutes = [ next() } }, + { + path: `${optimizationPath}:placeName1${optionalPlaceNamesPath}/data/:data${embedParameters}`, + name: 'MapOptimization', + component: Maps, + beforeEnter: (to, from, next) => { + store.commit('mode', constants.modes.optimization) + next() + } + }, + { + path: `${optimizationPath}`, + name: 'MapOptimizationHome', + component: Maps, + beforeEnter: (to, from, next) => { + store.commit('mode', constants.modes.optimization) + next() + } + }, { path: '/settings', name: 'MapSettings', diff --git a/src/resources/constants.js b/src/resources/constants.js index e2de6f990..b555af045 100644 --- a/src/resources/constants.js +++ b/src/resources/constants.js @@ -8,7 +8,8 @@ const constants = { geocodeSearch: 'pgeocode/search', autocomplete: 'pgeocode/autocomplete', pois: 'ppois', - reverseGeocode: 'pgeocode/reverse' + reverseGeocode: 'pgeocode/reverse', + optimization: 'poptimization' }, endpoints: { directions: 'directions', @@ -16,7 +17,8 @@ const constants = { geocodeSearch: 'geocode/search', autocomplete: 'geocode/autocomplete', pois: 'pois', - reverseGeocode: 'geocode/reverse' + reverseGeocode: 'geocode/reverse', + optimization: 'optimization' }, roundTripFilterName: 'round_trip', avoidPolygonsFilterName: 'avoid_polygons', @@ -28,6 +30,7 @@ const constants = { dataOrigins: { directions: '/directions', isochrones: '/isochrones', + optimization: '/optimization', fileImporter: 'fileImporter' }, modes: { @@ -36,6 +39,7 @@ const constants = { place: 'place', search: 'search', isochrones: 'isochrones', + optimization: 'optimization', pageNotFound: 'pageNotFound' }, importableModes: { @@ -48,7 +52,8 @@ const constants = { geocodeSearch: 'geocodeSearch', autocomplete: 'autocomplete', reverseGeocode: 'reverseGeocode', - isochrones: 'isochrones' + isochrones: 'isochrones', + optimization: 'optimization' }, filterTypes: { wrapper: 'wrapper', diff --git a/src/support/app-modes/app-mode.js b/src/support/app-modes/app-mode.js index 39e8494c6..27ed5a64b 100644 --- a/src/support/app-modes/app-mode.js +++ b/src/support/app-modes/app-mode.js @@ -12,6 +12,7 @@ import roundtripMode from './strategies/roundtrip-mode' import searchMode from './strategies/search-mode' import placeMode from './strategies/place-mode' import isochronesMode from './strategies/isochrones-mode' +import optimizationMode from './strategies/optimization-mode' /** * AppState @@ -23,7 +24,7 @@ class AppMode { */ constructor (modeTo) { this.modeTo = modeTo - const modes = { directionsMode, placeMode, roundtripMode, searchMode, isochronesMode } + const modes = { directionsMode, placeMode, roundtripMode, searchMode, isochronesMode , optimizationMode} const mode = `${this.modeTo}Mode` this.targetMode = new modes[mode]() diff --git a/src/support/app-modes/strategies/optimization-mode.js b/src/support/app-modes/strategies/optimization-mode.js new file mode 100644 index 000000000..b8f308375 --- /dev/null +++ b/src/support/app-modes/strategies/optimization-mode.js @@ -0,0 +1,63 @@ +import OrsParamsParser from '@/support/map-data-services/ors-params-parser' +import OrsMapFilters from '@/config/ors-map-filters' +import AppRouteData from '@/models/app-route-data' +import RouteUtils from '@/support/route-utils' +import constants from '@/resources/constants' +import appConfig from '@/config/app-config' +import AppLoader from '@/app-loader' +import Utils from '@/support/utils' +import store from '@/store/store' + +/** + * App mode for fleet scheduling using the optimization endpoint + */ +class OptimizationMode { + buildAppRouteData (places, options = {}) { + const appRouteData = store.getters.appRouteData || new AppRouteData() + appRouteData.places = places + + OrsParamsParser.setFilters(options, OrsMapFilters, constants.services.optimization) + appRouteData.options = options + appRouteData.options.zoom = appConfig.initialMapMaxZoom + return appRouteData + } + + /** + * Build an optimization route + * @param {*} appRouteData + * @returns {Object} route like {name: 'MapDirections', params: {...} } + */ + getRoute = (appRouteData, options = null) => { + options = options || appRouteData.options + const params = RouteUtils.buildRouteParams(appRouteData, options) + // Build and return the route object + const route = { name: 'MapOptimization', params: params } + return route + } + + /** + * Decode single place path + * @param {*} currentRoute + * @returns {AppRouteData} + */ + decodePath = (currentRoute) => { + const appRouteData = new AppRouteData() + const data = RouteUtils.decodeDataParam(currentRoute.params.data) + + // Get coordinates and options from the param + appRouteData.options = data.options + + // In the 'directions' mode, the options parameter may contain an options object + // that is expected to be used as the ORS API request options (avoid_polygons, avoid_features etc.) + // So, as they are stringified on the url, we try to parse them back to an object + if (appRouteData.options && appRouteData.options.options) { + appRouteData.options.options = Utils.tryParseJson(appRouteData.options.options) || appRouteData.options.options + } + + appRouteData.places = RouteUtils.getRoutePlaces(currentRoute) + AppLoader.getInstance().appHooks.run('afterOptimizationPathDecoded', appRouteData) + return appRouteData + } +} +// export the class +export default OptimizationMode diff --git a/src/support/map-data-services/ors-response-data-extractors/ors-response-extractor-builder.js b/src/support/map-data-services/ors-response-data-extractors/ors-response-extractor-builder.js index 0dd8e536f..a877da304 100644 --- a/src/support/map-data-services/ors-response-data-extractors/ors-response-extractor-builder.js +++ b/src/support/map-data-services/ors-response-data-extractors/ors-response-extractor-builder.js @@ -3,6 +3,7 @@ import GeocodeSearchBuilderV2 from './response-extractors/v2/geocode-search' import GeocodeReverseBuilderV2 from './response-extractors/v2/geocode-reverse' import IsochronesBuilderV2 from './response-extractors/v2/isochrones' import PoisBuilderV2 from './response-extractors/v2/pois' +import OptimizationBuilderV2 from './response-extractors/v2/optimization' /** * Map data Builder class @@ -45,6 +46,8 @@ class OrsResponseExtractorBuilder { return OrsResponseExtractorBuilder.getMapDataExtractor('IsochronesBuilder', data, apiVersion) case 'pois': return OrsResponseExtractorBuilder.getMapDataExtractor('PoisBuilder', data, apiVersion) + case 'optimization': + return OrsResponseExtractorBuilder.getMapDataExtractor('OptimizationBuilder', data, apiVersion) } } @@ -70,7 +73,8 @@ class OrsResponseExtractorBuilder { GeocodeSearchBuilderV2, GeocodeReverseBuilderV2, IsochronesBuilderV2, - PoisBuilderV2 + PoisBuilderV2, + OptimizationBuilderV2 // we can add more versions here in the future } diff --git a/src/support/map-data-services/ors-response-data-extractors/response-extractors/v2/optimization.js b/src/support/map-data-services/ors-response-data-extractors/response-extractors/v2/optimization.js new file mode 100644 index 000000000..6c9dcebc9 --- /dev/null +++ b/src/support/map-data-services/ors-response-data-extractors/response-extractors/v2/optimization.js @@ -0,0 +1,99 @@ +import MapViewData from '@/models/map-view-data' +import Utils from '@/support/utils' +import GeoUtils from '@/support/geo-utils' + +/** + * DirectionsJSONBuilder Map data Builder class + * @param {*} data {responseData: {}, translations: {}} + */ +class OptimizationBuilder { + constructor (data) { + this.responseData = data.responseData + } + + + /** + * Build the map data for directions json response + * @returns {Promise} that returns in the resolve mapData object + */ + buildMapData = () => { + const mapViewData = new MapViewData() + const context = this + return new Promise((resolve) => { + mapViewData.rawData = Utils.clone(context.responseData) + mapViewData.routes = context.buildRoutes() + mapViewData.isRouteData = mapViewData.hasRoutes() + resolve(mapViewData) + }) + } + + buildRoutes = () => { + for (const key in this.responseData.routes) { + this.responseData.routes[key].properties = { + id: key + 1 + } + this.responseData.routes[key].geometry = { + coordinates: GeoUtils.switchLatLonIndex(this.decodePolyline(this.responseData.routes[key].geometry, false)), + type: 'Polyline' + } + } + return this.responseData.routes + } + + /** + * Decode an x,y or x,y,z encoded polyline + * @param {*} encodedPolyline + * @param {Boolean} includeElevation - true for x,y,z polyline + * @returns {Array} of coordinates + */ + decodePolyline = (encodedPolyline, includeElevation) => { + // array that holds the points + let points = [] + let index = 0 + const len = encodedPolyline.length + let lat = 0 + let lng = 0 + let ele = 0 + while (index < len) { + let b + let shift = 0 + let result = 0 + do { + b = encodedPolyline.charAt(index++).charCodeAt(0) - 63 // finds ascii + // and subtract it by 63 + result |= (b & 0x1f) << shift + shift += 5 + } while (b >= 0x20) + + lat += ((result & 1) !== 0 ? ~(result >> 1) : (result >> 1)) + shift = 0 + result = 0 + do { + b = encodedPolyline.charAt(index++).charCodeAt(0) - 63 + result |= (b & 0x1f) << shift + shift += 5 + } while (b >= 0x20) + lng += ((result & 1) !== 0 ? ~(result >> 1) : (result >> 1)) + + if (includeElevation) { + shift = 0 + result = 0 + do { + b = encodedPolyline.charAt(index++).charCodeAt(0) - 63 + result |= (b & 0x1f) << shift + shift += 5 + } while (b >= 0x20) + ele += ((result & 1) !== 0 ? ~(result >> 1) : (result >> 1)) + } + try { + let location = [(lat / 1E5), (lng / 1E5)] + if (includeElevation) location.push((ele / 100)) + points.push(location) + } catch (e) { + console.log(e) + } + } + return points + } +} +export default OptimizationBuilder diff --git a/src/support/map-data-services/ors-response-data-extractors/route-data.js b/src/support/map-data-services/ors-response-data-extractors/route-data.js index c6bc5f7a0..564016084 100644 --- a/src/support/map-data-services/ors-response-data-extractors/route-data.js +++ b/src/support/map-data-services/ors-response-data-extractors/route-data.js @@ -69,6 +69,8 @@ const routeData = { return 'pois' case '/matrix': return 'matrix' + case '/optimization': + return 'optimization' } } } diff --git a/src/support/ors-api-runner.js b/src/support/ors-api-runner.js index 0639b82b0..a914ac908 100644 --- a/src/support/ors-api-runner.js +++ b/src/support/ors-api-runner.js @@ -345,9 +345,39 @@ const Isochrones = (places) => { }) } +/** + * Optimize a list of Jobs for Vehicles + * @returns {Promise} + * @param jobs + * @param vehicles + */ +const Optimization = (jobs, vehicles = []) => { + const mapSettings = store.getters.mapSettings + + const optimization = new OrsApiClient.Optimization({ + api_key: mapSettings.apiKey, + host: mapSettings.apiBaseUrl, + service: mapSettings.endpoints.optimization + }) + return new Promise((resolve, reject) => { + OrsParamsParser.buildOptimizationArgs(jobs, vehicles).then(args => { + optimization.calculate(args).then((response) => { + const data = { options: { origin: constants.dataOrigins.optimization, apiVersion: constants.apiVersion }, content: response } + resolve(data) + }).catch((err) => { + err.response.json().then((error) => { + const result = { response: error, args: args } + reject(result) + }) + }) + }) + }) +} + export { Directions } export { Geocode } export { Pois } export { PlacesSearch } export { ReverseGeocode } export { Isochrones } +export { Optimization } diff --git a/src/support/routes-resolver.js b/src/support/routes-resolver.js index acc41e029..04cd109f7 100644 --- a/src/support/routes-resolver.js +++ b/src/support/routes-resolver.js @@ -26,6 +26,9 @@ const resolver = { }, isochronesPath: () => { return '/reach/' + }, + optimizationPath: () => { + return '/optimize/' } } From f5b8e24da28f2e0e0bd9aab3c3860d6ad9e6efd1 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 11 Apr 2024 13:32:08 +0200 Subject: [PATCH 002/109] refactor: remove place setId --- src/models/place.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/models/place.js b/src/models/place.js index 21daf63f0..b72596b2b 100644 --- a/src/models/place.js +++ b/src/models/place.js @@ -103,14 +103,6 @@ class Place { this.suggestions = places } - /** - * Set the place id - * @param {*} id - */ - setId(id) { - this.placeId = id - } - isEmpty() { return !this.lat || this.lat === 0 || !this.lng || this.lng === 0 } From 98c92cf63bfc0d6879b0bd9f4050337aff55d0ff Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 11 Apr 2024 13:33:07 +0200 Subject: [PATCH 003/109] feat: job, vehicle and skill models for optimization add classes for job and vehicle objects add class for skill objects to be used in jobs and vehicles --- src/models/job.js | 171 ++++++++++++++++++++++++++++++++++++++ src/models/skill.js | 57 +++++++++++++ src/models/vehicle.js | 187 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 415 insertions(+) create mode 100644 src/models/job.js create mode 100644 src/models/skill.js create mode 100644 src/models/vehicle.js diff --git a/src/models/job.js b/src/models/job.js new file mode 100644 index 000000000..0041437be --- /dev/null +++ b/src/models/job.js @@ -0,0 +1,171 @@ +import Place from '@/models/place' +import Skill from '@/models/skill' + +class Job extends Place { + constructor(lng = null, lat = null, placeName = '', options = {}) { + super(lng, lat, placeName, options) + + this.location = this.coordinates + + this.id = options.id || null + this.service = options.service || 0 // time spent at job + this.skills = options.skills || [] + this.priority = options.priority || 0 + this.delivery = options.delivery || [1] + this.pickup = options.pickup || [0] + this.time_windows = options.time_windows || [] + } + + static fromPlace(place) { + return new Job(place.lng, place.lat, place.placeName, { + id: place.placeId + }) + } + + /** + * @param {String} jobJSONString + */ + static fromJSON(jobJSONString) { + let job = JSON.parse(jobJSONString) + return this.fromObject(job) + } + + static fromObject(jobObject) { + let skillObjects = [] + if (jobObject.skills) { + for (let id of jobObject.skills) { + skillObjects.push(Skill.getName(id)) + } + } + return new Job(jobObject.location[0], jobObject.location[1], jobObject.placeName, { + id: jobObject.id, + service: jobObject.service, + skills: skillObjects, + priority: jobObject.priority, + delivery: jobObject.delivery, + pickup: jobObject.pickup, + time_windows: jobObject.time_windows, + resolve: true + }) + } + + static fromCsv(csv) { + const lines = csv.split('\n') + const header = lines[0].split(',') + + let jobs = [] + for (let i=1; i < lines.length; i++) { + let obj = {} + let currentLine = lines[i].split(',') + + for (const k in header){ + let parsedLine = Number(currentLine[k]) + + if (header[k] === 'location_lng') { + obj['location'] = [currentLine[k]] + } else if (header[k] === 'location_lat') { + obj['location'].push(parsedLine) + } else if (['skills', 'delivery', 'pickup', 'time_window'].includes(header[k])) { + obj[header[k]] = [parsedLine] + } else { + obj[header[k]] = parsedLine + } + } + jobs.push(Job.fromObject(obj)) + } + + return jobs + } + + clone() { + return Job.fromJSON(this.toJSON(true)) + } + + /** + * Set the job id + * @param {*} id + */ + setId(id) { + this.id = id + } + + toJSON(stringify = false) { + let out = { + 'id': this.id, + 'location': this.location, + 'service': this.service, + 'delivery': this.delivery, + 'pickup': this.pickup + } + + if (this.skills.length) { + let skillIds = [] + for (const skill of this.skills) { + skillIds.push(skill.id) + } + skillIds.sort() + out['skills'] = skillIds + } + if (this.time_windows.length) { + out['time_windows'] = this.time_windows + } + return stringify ? JSON.stringify(out) : out + } + + toGeoJSON(stringify = false) { + let props = { + id: this.id, + service: this.service, + delivery: this.delivery, + pickup: this.pickup + } + + if (this.skills) { + let skillIds = [] + for (const skill of this.skills) { + skillIds.push(skill.id) + } + skillIds.sort() + props.skills = skillIds + } + if (this.time_windows) { + props.time_windows = this.time_windows + } + + const geoJsonData = { type: 'Feature', geometry: { type: 'Point', coordinates: this.location }, properties: props } + return stringify ? JSON.stringify(geoJsonData) : geoJsonData + } + + static toCsv(jobs) { + const csvKeys = ['id', 'location', 'service', 'delivery', 'pickup', 'skills', 'time_windows'] + const header = ['id', 'location_lng', 'location_lat', 'service', 'delivery', 'pickup', 'skills', 'time_windows'] + let data = header.join() + for (let job of jobs) { + job = job.toJSON() + let jobValues = [] + for (const key of csvKeys) { + if (key in job) { + jobValues.push(job[key].toString()) + } else { + jobValues.push('') + } + } + data = data + '\n' + jobValues.join() + } + return data + } + + static jobsFromFeatures(jobs) { + const out = [] + for (const job of jobs) { + jobs.push(Job.fromJSON(job)) + } + return out + } + + setLngLat (lng, lat) { + super.setLngLat(lng, lat) + this.location = this.coordinates + } +} +export default Job diff --git a/src/models/skill.js b/src/models/skill.js new file mode 100644 index 000000000..b47b2591e --- /dev/null +++ b/src/models/skill.js @@ -0,0 +1,57 @@ +class Skill { + constructor(skill_name = '', skill_id = null) { + this.name = skill_name + this.id = skill_id + } + + static fromJSON(skillJSONString) { + let skill = JSON.parse(skillJSONString) + return this.fromObject(skill) + } + + static fromObject(skillObject) { + return new Skill(skillObject.name, skillObject.id) + } + + static getName(id) { + const storedSkills = localStorage.getItem('skills') + let skillObjects = [] + let skillIds = [] + for (const skill of JSON.parse(storedSkills)) { + const thisSkill = this.fromObject(skill) + skillObjects.push(thisSkill) + skillIds.push(thisSkill['id']) + } + + if (skillIds.includes(id)) { + return skillObjects[skillIds.indexOf(id)] + } else { + const newSkill = new Skill('Enter skill name', id) + skillObjects.push(newSkill) + const jsonSkills = [] + for (const skill of skillObjects) { + jsonSkills.push(skill.toJSON()) + } + localStorage.setItem('skills', JSON.stringify(jsonSkills)) + return newSkill + } + } + + clone() { + return Skill.fromJSON(this.toJSON(true)) + } + + setId(id) { + this.id = id + } + + toJSON(stringify = false) { + let out = { + 'name': this.name, + 'id': this.id + } + return stringify ? JSON.stringify(out) : out + } +} + +export default Skill diff --git a/src/models/vehicle.js b/src/models/vehicle.js new file mode 100644 index 000000000..94bf54bcd --- /dev/null +++ b/src/models/vehicle.js @@ -0,0 +1,187 @@ +import Place from '@/models/place' +import Skill from '@/models/skill' + +class Vehicle extends Place { + constructor(lng = null, lat = null, placeName = '', options = {}) { + super(lng, lat, placeName, options) + + this.start = options.start || this.coordinates + this.end = options.end || this.coordinates + + this.id = options.id || null + this.description = options.description || '' + this.profile = options.profile || 'driving-car' + this.capacity = options.capacity || [5] + this.skills = options.skills || [] + this.time_window = options.time_window || [] + } + + static fromPlace(place) { + return new Vehicle(place.lng, place.lat, place.placeName, { + id: place.placeId, + }) + } + + /** + * @param {String} vehicleJSONString + */ + static fromJSON(vehicleJSONString) { + let vehicle = JSON.parse(vehicleJSONString) + return this.fromObject(vehicle) + } + + static fromObject(vObject) { + let skillObjects = [] + if (vObject.skills) { + for (let id of vObject.skills) { + skillObjects.push(Skill.getName(id)) + } + } + return new Vehicle( + vObject.start[0] || vObject.end[0], + vObject.start[1] || vObject.end[1], + vObject.placeName, + { + id: vObject.id, + description: vObject.description, + profile: vObject.profile, + start: vObject.start, + end: vObject.end, + capacity: vObject.capacity, + skills: skillObjects, + time_window: vObject.time_window, + resolve: true, + } + ) + } + + static fromCsv(csv) { + const lines = csv.split('\n') + const header = lines[0].split(',') + + let vehicles = [] + for (let i=1; i < lines.length; i++) { + let obj = {} + let currentLine = lines[i].split(',') + + for (const k in header){ + let parsedLine + if (header[k] !== 'description' || header[k] !== 'profile') { + parsedLine = Number(currentLine[k]) + } else { + parsedLine = currentLine[k] + } + + if (header[k] === 'start_lng' || header[k] === 'end_lng') { + obj[header[k].split('_')[0]] = [parsedLine] + } else if (header[k] === 'start_lat' || header[k] === 'end_lat') { + obj[header[k].split('_')[0]].push(parsedLine) + } else if (['skills', 'capacity', 'time_window'].includes(header[k])) { + obj[header[k]] = [parsedLine] + } else { + obj[header[k]] = parsedLine + } + } + vehicles.push(Vehicle.fromObject(obj)) + } + + return vehicles + } + + clone() { + return Vehicle.fromJSON(this.toJSON(true)) + } + + /** + * Set the vehicle id + * @param {*} id + */ + setId(id) { + this.id = id + } + + toJSON(stringify = false) { + let out = { + id: this.id, + description: this.description, + profile: this.profile, + start: this.start, + end: this.end, + capacity: this.capacity, + } + + if (this.skills.length) { + let skillIds = [] + for (const skill of this.skills) { + skillIds.push(skill.id) + } + skillIds.sort() + out.skills = skillIds + } + if (this.time_window.length) { + out.time_window = this.time_window + } + return stringify ? JSON.stringify(out) : out + } + + toGeoJSON(stringify = false) { + let props = { + id: this.id, + description: this.description, + profile: this.profile, + capacity: this.capacity, + } + + if (this.skills) { + let skillIds = [] + for (const skill of this.skills) { + skillIds.push(skill.id) + } + skillIds.sort() + props.skills = skillIds + } + if (this.time_windows) { + props.time_windows = this.time_windows + } + + const geoJsonData = { type: 'FeatureCollection', features: [ + { type: 'Feature', geometry: {type: 'Point', coordinates: this.start }, }, + { type: 'Feature', geometry: { type: 'Point', coordinates: this.end }, }], properties: props } + return stringify ? JSON.stringify(geoJsonData) : geoJsonData + } + + static toCsv(vehicles) { + const csvKeys = ['id', 'start', 'end', 'description', 'profile', 'capacity', 'skills', 'time_windows'] + const header = ['id', 'start_lng', 'start_lat', 'end_lng', 'end_lat', 'description', 'profile', 'capacity', 'skills', 'time_windows'] + let data = header.join() + for (let v of vehicles) { + v = v.toJSON() + let vehicleValues = [] + for (const key of csvKeys) { + if (key in v) { + vehicleValues.push(v[key].toString()) + } else { + vehicleValues.push('') + } + } + data = data + '\n' + vehicleValues.join() + } + return data + } + + static vehiclesFromFeatures(vehicles) { + const out = [] + for (const v of vehicles) { + vehicles.push(Vehicle.fromJSON(v)) + } + return out + } + + setLngLat (lng, lat) { + super.setLngLat(lng, lat) + this.start = this.coordinates + this.end = this.coordinates + } +} + +export default Vehicle From c67126f16cbcd09b6c82d075afd5021227adc430 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 11 Apr 2024 13:34:52 +0200 Subject: [PATCH 004/109] feat: notImplemented warning available --- src/i18n/translations/en-us/global.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/translations/en-us/global.js b/src/i18n/translations/en-us/global.js index de57cd8df..6c51444f5 100755 --- a/src/i18n/translations/en-us/global.js +++ b/src/i18n/translations/en-us/global.js @@ -102,6 +102,7 @@ export default { tollways: 'Tollways', traildifficulty: 'Trail difficulty', osmid: 'OSM ID', - countryinfo: 'Country info' + countryinfo: 'Country info', + notImplemented: 'Not implemented yet' } } From b2cc073c98f73a2c0b75fa79df85c5cc1b53cab7 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 11 Apr 2024 13:40:00 +0200 Subject: [PATCH 005/109] feat: optimization tab add mode tab for optimization --- src/fragments/forms/map-form/MapForm.vue | 8 +- .../components/optimization/Optimization.vue | 35 ++ .../i18n/optimization.i18n.de-de.js | 48 ++ .../i18n/optimization.i18n.en-us.js | 49 ++ .../components/optimization/optimization.css | 33 ++ .../components/optimization/optimization.js | 453 ++++++++++++++++++ .../map-form/i18n/map-form.i18n.de-de.js | 1 + .../map-form/i18n/map-form.i18n.en-us.js | 1 + src/fragments/forms/map-form/map-form.js | 26 +- src/fragments/html-marker/html-marker.js | 8 +- .../map-view-markers/MapViewMarkers.vue | 20 +- .../map-view/i18n/map-view.i18n.en-us.js | 1 + src/fragments/map-view/map-view.js | 16 +- src/models/map-view-data.js | 22 +- src/pages/maps/Maps.vue | 5 +- src/pages/maps/maps.js | 6 + src/resources/constants.js | 7 + src/support/geo-utils.js | 94 +++- .../map-data-services/ors-params-parser.js | 20 + 19 files changed, 823 insertions(+), 30 deletions(-) create mode 100644 src/fragments/forms/map-form/components/optimization/Optimization.vue create mode 100755 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js create mode 100755 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js create mode 100644 src/fragments/forms/map-form/components/optimization/optimization.css create mode 100644 src/fragments/forms/map-form/components/optimization/optimization.js diff --git a/src/fragments/forms/map-form/MapForm.vue b/src/fragments/forms/map-form/MapForm.vue index 1ffd5e84f..a68cee3ca 100644 --- a/src/fragments/forms/map-form/MapForm.vue +++ b/src/fragments/forms/map-form/MapForm.vue @@ -7,14 +7,20 @@ {{$t('mapForm.isochrones')}} + + {{$t('mapForm.optimization')}} + - + + + + diff --git a/src/fragments/forms/map-form/components/optimization/Optimization.vue b/src/fragments/forms/map-form/components/optimization/Optimization.vue new file mode 100644 index 000000000..7c2b568cf --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/Optimization.vue @@ -0,0 +1,35 @@ + + + + diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js new file mode 100755 index 000000000..6a926dbc5 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js @@ -0,0 +1,48 @@ +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + optimizeJobs: 'optimizeJobs', + couldNotResolveTheJobLocation: 'Could not resolve the job location', + couldNotResolveTheVehicleLocation: 'Could not resolve the vehicle location', + vehicleMaxWarning: 'A maximum of 3 vehicles can be considered with the live API', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + keepEdits: 'Keep edits', + skills: 'Skills', + manageSkills: 'Manage Skills', + importSkillFile: 'Import skills', + exportSkillFile: 'Export skills', + saveSkills: 'Save skills', + addSkill: 'Add skill', + removeSkill: 'Remove skill', + editSkill: 'Edit skill', + time_window: 'Time window', + jobs: 'Jobs', + manageJobs: 'Manage jobs', + noJobsToManage: 'No jobs available. Import jobs or use button to add from map', + importJobFile: 'Import jobs', + exportJobFile: 'Export jobs', + saveJobs: 'Save jobs', + addJob: 'Add job', + addJobFromMap: 'Add job from map', + removeJob: 'Remove job', + editJob: 'Edit job', + duplicateJob: 'Duplicate job', + vehicles: 'Vehicles', + manageVehicles: 'Manage vehicles', + noVehiclesToManage: 'No vehicles available. Import vehicles or use button to add from map', + importVehicleFile: 'Import vehicles', + exportVehicleFile: 'Export vehicles', + saveVehicles: 'Save vehicles', + addVehicle: 'Add vehicle', + addVehicleFromMap: 'Add vehicle from map', + removeVehicle: 'Remove vehicle', + editVehicle: 'Edit vehicle', + duplicateVehicle: 'Duplicate vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js new file mode 100755 index 000000000..5f01722eb --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js @@ -0,0 +1,49 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + optimizeJobs: 'optimizeJobs', + couldNotResolveTheJobLocation: 'Could not resolve the job location', + couldNotResolveTheVehicleLocation: 'Could not resolve the vehicle location', + jobMaxWarning: 'A maximum of 50 jobs can be considered with the live API', + vehicleMaxWarning: 'A maximum of 3 vehicles can be considered with the live API', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + skills: 'Skills', + manageSkills: 'Manage Skills', + importSkillFile: 'Import skills', + exportSkillFile: 'Export skills', + saveSkills: 'Save skills', + addSkill: 'Add skill', + removeSkill: 'Remove skill', + editSkill: 'Edit skill', + time_window: 'Time window', + jobs: 'Jobs', + manageJobs: 'Manage jobs', + noJobsToManage: 'No jobs available. Import jobs or use button to add from map', + importJobFile: 'Import jobs', + exportJobFile: 'Export jobs', + saveJobs: 'Save jobs', + addJob: 'Add job', + addJobFromMap: 'Add job from map', + removeJob: 'Remove job', + editJob: 'Edit job', + duplicateJob: 'Duplicate job', + vehicles: 'Vehicles', + manageVehicles: 'Manage vehicles', + noVehiclesToManage: 'No vehicles available. Import vehicles or use button to add from map', + importVehicleFile: 'Import vehicles', + exportVehicleFile: 'Export vehicles', + saveVehicles: 'Save vehicles', + addVehicle: 'Add vehicle', + addVehicleFromMap: 'Add vehicle from map', + removeVehicle: 'Remove vehicle', + editVehicle: 'Edit vehicle', + duplicateVehicle: 'Duplicate vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/optimization.css b/src/fragments/forms/map-form/components/optimization/optimization.css new file mode 100644 index 000000000..6ec7650c6 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/optimization.css @@ -0,0 +1,33 @@ +.optimization-heading { + font-size: 1rem; + padding-left: 5px; + font-weight: bold; +} + +ul.job-inputs li { + list-style: none; +} + +ul.job-inputs { + padding: 10px 0; +} + +.route-btn { + float: right; + margin-right: 8px; + margin-top: 4px; +} + +.skill-opt-btn { + float: left; + margin-left: 10px; + margin-right: 10px; + margin-top: 3px +} +.skill-btn-legend { + font-size: 9px; + text-align: center; + word-break: break-word; + max-width: 58px; + margin-left: 0; +} diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js new file mode 100644 index 000000000..25ff18209 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -0,0 +1,453 @@ +import MapFormMixin from '../map-form-mixin' +import MapViewDataBuilder from '@/support/map-data-services/map-view-data-builder' +import FieldsContainer from '@/fragments/forms/fields-container/FieldsContainer' +import OrsFilterUtil from '@/support/map-data-services/ors-filter-util' +import FormActions from '@/fragments/forms/map-form/components/form-actions/FormActions' +import {EventBus} from '@/common/event-bus' +import { Optimization } from '@/support/ors-api-runner' +import AppMode from '@/support/app-modes/app-mode' +import MapViewData from '@/models/map-view-data' +import constants from '@/resources/constants' +import appConfig from '@/config/app-config' +import Job from '@/models/job' +import Vehicle from '@/models/vehicle' +import Skill from '@/models/skill' + +// Local components +import OptimizationDetails from './components/optimization-details/OptimizationDetails' +import JobList from './components/job-list/JobList.vue' +import VehicleList from './components/vehicle-list/VehicleList.vue' + +export default { + mixins: [MapFormMixin], + data: () => ({ + mode: constants.modes.optimization, + mapViewData: new MapViewData(), + skills: [], + jobs: [], + vehicles: [], + pickPlaceSupported: true + }), + components: { + FieldsContainer, + FormActions, + JobList, + VehicleList, + }, + computed: { + jobsJSON () { + const jsonJobs = [] + for (const job of this.jobs) { + jsonJobs.push(job.toJSON()) + } + return jsonJobs + }, + vehiclesJSON () { + const jsonVehicles = [] + for (const v of this.vehicles) { + jsonVehicles.push(v.toJSON()) + } + return jsonVehicles + }, + skillsJSON () { + const jsonSkills = [] + for (const skill of this.skills) { + jsonSkills.push(skill.toJSON()) + } + return jsonSkills + }, + disabledActions () { + return appConfig.disabledActionsForOptimization + } + }, + created () { + let storedSkills = localStorage.getItem('skills') + // load skills from local storage if there, otherwise set example skill + if (storedSkills) { + const skills = [] + for (const s of JSON.parse(storedSkills)) { + skills.push(Skill.fromObject(s)) + } + this.skills = skills + } else { + this.skills = [Skill.fromJSON('{"name":"length over 1.5m", "id":1}')] + localStorage.setItem('skills', JSON.stringify(this.skillsJSON)) + } + // TODO: remove defaults + this.jobs = [Job.fromJSON('{"id":1,"service":300,"skills":[1],"amount":[1],"location":[8.68525,49.420822]}')] + this.vehicles = [Vehicle.fromJSON('{"id":1,"profile":"driving-car","start":[ 8.675863, 49.418477 ],"end":[ 8.675863, 49.418477 ],"capacity":[4],"skills":[1]}')] + this.loadData() + + const context = this + // When the filters object has changed externally, reprocess the app route + EventBus.$on('filtersChangedExternally', () => { + if (context.active) { + context.updateAppRoute() + } + }) + // When the user click on a marker to remove it + EventBus.$on('removePlace', (data) => { + if (context.active) { + context.removePlace(data) + } + }) + + /** + * Update local object when a mapViewData is uploaded + */ + EventBus.$on('mapViewDataUploaded', (mapViewData) => { + if (context.active) { + context.mapViewData = mapViewData + context.jobs = mapViewData.jobs + context.vehicles = mapViewData.vehicles + } + }) + + /** + * If the map data view has changed and this component + * is not active, then reset its data to the initial state + */ + EventBus.$on('mapViewDataChanged', () => { + if (!context.active) { + context.mapViewData = new MapViewData() + context.jobs = [] + context.vehicles = [] + } + }) + + // On map right click -> addJob + EventBus.$on('addJob', (data) => { + context.addJob(data) + }) + + // On map right click -> addVehicle + EventBus.$on('addVehicle', (data) => { + context.addVehicle(data) + }) + + // When a marker drag finishes, update + // the place coordinates and re-render the map + EventBus.$on('markerDragged', (marker) => { + if (context.active) { + if (marker.text.startsWith('V')) { + let vehicle = context.vehicles[parseInt(marker.text.slice(1))-1] + vehicle.setLngLat(marker.position.lng, marker.position.lat) + context.updateAppRoute() + } else { + let job = context.jobs[parseInt(marker.text)-1] + job.setLngLat(marker.position.lng, marker.position.lat) + context.updateAppRoute() + } + } + }) + + // place is picked from Map + EventBus.$on('setInputPlace', (data) => { + if (context.active) { + // pickEditSource indicates which property the place should fill + if (data.pickEditSource === 'jobs') { + let job = Job.fromPlace(data.place) + let id = data.placeInputId + job.setId(id) + + context.jobs.push(job) + context.manageJobs(id) + } else if (data.pickEditSource === 'vehicleStart') { + let v = Vehicle.fromPlace(data.place) + let id = data.placeInputId + v.setId(id) + + context.vehicles.push(v) + context.manageVehicles(id) + } else if (data.pickEditSource === 'vehicleEnd') { + this.vehicles[data.pickPlaceIndex].end = data.place.coordinates + + context.manageVehicles(data.placeInputId) + } + } else { + context.setSidebarIsOpen(true) + context.$forceUpdate() + } + }) + }, + watch: { + $route: function () { + if (this.$store.getters.mode === constants.modes.optimization) { + this.loadData() + } else { + this.skills = [] + this.jobs = [] + this.vehicles = [] + } + } + }, + methods: { + /** + * When the user click on the map and select a point as the route start + * @param {*} data {latLng: ..., place:...} + */ + addJob (data) { + const job = Job.fromPlace(data.place) + job.setId(this.jobs.length + 1) + const context = this + job.resolve().then(() => { + context.jobs.push(job) + context.manageJobs(job.id) + context.updateAppRoute() + }).catch((err) => { + console.log(err) + context.showError(this.$t('optimization.couldNotResolveTheJobLocation'), { timeout: 0 }) + }) + }, + // when there are no jobs and button in sidebar is clicked + addJobFromMap() { + this.showInfo(this.$t('placeInput.clickOnTheMapToSelectAPlace')) + this.setPickPlaceSource(this.jobs) + }, + + // when the user clicks on the map and selects a point as the route start + addVehicle (data) { + const vehicle = Vehicle.fromPlace(data.place) + vehicle.setId(this.vehicles.length + 1) + const context = this + vehicle.resolve().then(() => { + if (this.vehicles.length > 3) { + this.showError(this.$t('optimization.vehicleMaxWarning'), {timeout: 3000}) + } + context.vehicles.push(vehicle) + context.manageVehicles(vehicle.id) + context.updateAppRoute() + }).catch((err) => { + console.log(err) + context.showError(this.$t('optimization.couldNotResolveTheVehicleLocation'), { timeout: 0 }) + }) + }, + // when there are no vehicles and button in sidebar is clicked + addVehicleFromMap() { + this.showInfo(this.$t('placeInput.clickOnTheMapToSelectAPlace')) + this.setPickPlaceSource(this.vehicles) + }, + + // Set the pick place input source + setPickPlaceSource (source) { + if (this.pickPlaceSupported) { + this.$store.commit('pickPlaceIndex', source.length) + this.$store.commit('pickPlaceId', source.length + 1) + if (source === this.jobs) { + this.$store.commit('pickEditSource', 'jobs') + } else if (source === this.vehicles) { + this.$store.commit('pickEditSource', 'vehicleStart') + } + } + }, + // remove job or vehicle when marker is deleted from map view + removePlace (data) { + if (data.job) { + let index = data.job.id - 1 + this.jobs.splice(index,1) + for (const i in this.jobs) { + this.jobs[i].setId(parseInt(i)+1) + } + } else if (data.vehicle) { + let index = data.vehicle.id - 1 + this.vehicles.splice(index,1) + for (const i in this.vehicles) { + this.vehicles[i].setId(parseInt(i)+1) + } + } + this.updateAppRoute() + }, + /** + * After each change on the map search we redirect the user to the built target app route + * The data will be loaded from the path and the map will be updated, keeping the + * url synchronized with the current map status + */ + updateAppRoute () { + let localCoords = this.jobs.map(j => j.coordinates) + const appRouteData = this.$store.getters.appRouteData + let urlCoords = appRouteData.places.map(urlJ => urlJ.coordinates) + if (JSON.stringify(localCoords) !== JSON.stringify(urlCoords) || JSON.stringify(this.vehiclesJSON) !== JSON.stringify(appRouteData.options.vehicles)) { + const jobs = this.jobs + const vehicles = this.vehiclesJSON + this.$store.commit('mode', constants.modes.optimization) + const appMode = new AppMode(this.$store.getters.mode) + const route = appMode.getRoute(jobs, {vehicles: vehicles}) + if (Object.keys(route.params).length > 1) {// params contains data and placeName? props + this.$router.push(route) + } + } + }, + // return start and end if they differ, otherwise only the start location of vehicle + vehicleLocations (vehicle) { + if (vehicle.end) { + return [vehicle.start, vehicle.end] + } else { + return vehicle.start + } + }, + /** + * Update the value in the filter when a parameter is updated in form-fields + * and then change the app route + * + * @param {*} data + */ + filterUpdated (data) { + if (data.value !== undefined) { + if (data.parentIndex !== undefined) { + const parent = OrsFilterUtil.getFilterByAncestryAndItemIndex(data.parentIndex, data.index) + parent.value = data.value + } else { + this.OrsMapFiltersAccessor[data.index].value = data.value + } + } + this.updateAppRoute() + }, + /** + * Request and draw a route based on the value of multiples places input + * @returns {Promise} + */ + optimizeJobs () { + localStorage.setItem('jobs', JSON.stringify(this.jobsJSON)) + localStorage.setItem('vehicles', JSON.stringify(this.vehiclesJSON)) + const context = this + return new Promise((resolve) => { + if (context.jobs.length) { + if (context.vehicles.length) { + context.showInfo(context.$t('optimization.optimizeJobs'), { timeout: 0 }) + EventBus.$emit('showLoading', true) + + // Calculate the optimized routes + Optimization(context.jobs, context.vehicles).then(data => { + data.options.translations = context.$t('global.units') + + data = context.$root.appHooks.run('beforeBuildOptimizationMapViewData', data) + if (data) { + MapViewDataBuilder.buildMapData(data, context.$store.getters.appRouteData).then((mapViewData) => { + context.mapViewData = mapViewData + context.mapViewData.jobs = context.jobs + context.mapViewData.vehicles = context.vehicles + EventBus.$emit('mapViewDataChanged', mapViewData) + EventBus.$emit('newInfoAvailable') + context.showSuccess(context.$t('optimization.optimizationResultReady'), { timeout: 2000 }) + context.setSidebarIsOpen() + resolve(mapViewData) + }) + } + }).catch(result => { + context.handleOptimizeJobsError(result) + }).finally(() => { + EventBus.$emit('showLoading', false) + }) + } else { + context.showError('No vehicles given. Please add a Vehicle to optimize') + } + } else { + context.showError('No jobs given. Please add some Jobs to optimize') + // There are no enough places or round trip to be routed + resolve({}) + } + }) + }, + /** + * Handle the route places error response displaying the correct message + * @param {*} result + * @param {*} args + */ + handleOptimizeJobsError (result) { + this.$root.appHooks.run('beforeHandleOptimizationError', result) + + const errorCode = this.lodash.get(result, constants.responseErrorCodePath) + if (errorCode) { + const errorKey = `optimization.apiError.${errorCode}` + let errorMsg = this.$t(errorKey) + if (errorMsg === errorKey) { + errorMsg = this.$t('optimization.genericErrorMsg') + } + this.showError(errorMsg, { timeout: 0, mode: 'multi-line' }) + console.error(result.response.error) + } else { + this.showError(this.$t('optimization.notPossible'), { timeout: 0 }) + console.error(result) + } + }, + + /** + * Load the map data from the url + * rebuilding the place inputs, and its values + * and render the map with this data (place or route) + */ + loadData () { + if (this.$store.getters.mode === constants.modes.optimization) { + // Empty the array and populate it with the + // places from the appRoute without changing the + // object reference because it is a prop + const defaultJobs = this.jobs + const defaultVehicles = this.vehicles + this.jobs = this.$store.getters.appRouteData.jobs + const urlVehicles = this.$store.getters.appRouteData.options.vehicles + let places = this.$store.getters.appRouteData.places + let storedJobs = localStorage.getItem('jobs') + let storedVehicles = localStorage.getItem('vehicles') + // prioritise data from url, then data from local storage + if (urlVehicles) { + const vehicles = [] + for (let v of urlVehicles) { + vehicles.push(Vehicle.fromObject(v)) + } + this.vehicles = vehicles + } else if (this.vehicles === undefined && storedVehicles) { + const vehicles = [] + for (const v of JSON.parse(storedVehicles)) { + vehicles.push(Vehicle.fromObject(v)) + } + this.vehicles = vehicles + } else if (this.vehicles === undefined || !this.vehicles.length) { + this.vehicles = defaultVehicles + } + const jobs = [] + if (places.length > 0) { + for (const [i, place] of places.entries()) { + const job = Job.fromPlace(place) + job.setId(i + 1) + jobs.push(job) + } + } else if (this.jobs === undefined && storedJobs) { + for (const job of JSON.parse(storedJobs)) { + jobs.push(Job.fromObject(job)) + } + } + this.jobs = jobs + if (!this.jobs.length) { + this.jobs = defaultJobs + } + this.optimizeJobs() + } + }, + // when jobs are changed update jobs and generate new route + jobsChanged(editedJobs) { + let newJobs = [] + for (const job of editedJobs) { + newJobs.push(job.clone()) + } + this.jobs = newJobs + this.updateAppRoute() + }, + // when vehicles are changed update vehicles and generate new route + vehiclesChanged(editedVehicles) { + let newVehicles = [] + for (const vehicle of editedVehicles) { + newVehicles.push(vehicle.clone()) + } + this.vehicles = newVehicles + this.updateAppRoute() + }, + // when skills are changed update skills + skillsChanged(editedSkills) { + let newSkills = [] + for (const skill of editedSkills) { + newSkills.push(skill.clone()) + } + this.skills = newSkills + }, + } +} diff --git a/src/fragments/forms/map-form/i18n/map-form.i18n.de-de.js b/src/fragments/forms/map-form/i18n/map-form.i18n.de-de.js index aa3bb5a94..06499c058 100755 --- a/src/fragments/forms/map-form/i18n/map-form.i18n.de-de.js +++ b/src/fragments/forms/map-form/i18n/map-form.i18n.de-de.js @@ -3,6 +3,7 @@ export default { mapForm: { placesAndDirections: 'Suche & Los', isochrones: 'Erreichbarkeit', + optimization: 'Optimize (Preview)', uploadedContentRendered: 'Hochgeladene Inhalte gerendert', errorRenderingContentUploaded: 'Fehler beim Hochladen der Inhalte. Bitte überprüfen Sie das Dateiformat und den Inhalt.' } diff --git a/src/fragments/forms/map-form/i18n/map-form.i18n.en-us.js b/src/fragments/forms/map-form/i18n/map-form.i18n.en-us.js index 9c29761e6..b2154af93 100755 --- a/src/fragments/forms/map-form/i18n/map-form.i18n.en-us.js +++ b/src/fragments/forms/map-form/i18n/map-form.i18n.en-us.js @@ -3,6 +3,7 @@ export default { mapForm: { placesAndDirections: 'Find & go', isochrones: 'Reach', + optimization: 'Optimize (Preview)', uploadedContentRendered: 'Uploaded content rendered', errorRenderingContentUploaded: 'Error while rendering the content uploaded. Check the file format and content' } diff --git a/src/fragments/forms/map-form/map-form.js b/src/fragments/forms/map-form/map-form.js index 2885cf5e4..cb2c9a6f7 100644 --- a/src/fragments/forms/map-form/map-form.js +++ b/src/fragments/forms/map-form/map-form.js @@ -1,5 +1,6 @@ import PlacesAndDirections from './components/place-and-directions/PlacesAndDirections' import Isochrones from './components/isochrones/Isochrones' +import Optimization from './components/optimization/Optimization' import resolver from '@/support/routes-resolver' import constants from '@/resources/constants' import appConfig from '@/config/app-config' @@ -10,7 +11,8 @@ export default { }), components: { PlacesAndDirections, - Isochrones + Isochrones, + Optimization }, watch: { activeTab: function () { @@ -32,6 +34,9 @@ export default { }, hasIsochronesTab () { return appConfig.supportsIsochrones + }, + hasOptimizationTab () { + return appConfig.supportsOptimization } }, methods: { @@ -40,7 +45,7 @@ export default { */ tabChanged () { if (this.activeTab === 0) { - if (this.$store.getters.mode === constants.modes.isochrones) { + if ([constants.modes.isochrones, constants.modes.optimization].includes(this.$store.getters.mode)) { if (this.$route.fullPath.includes(resolver.directions())) { this.$store.commit('mode', constants.modes.directions) } else { @@ -49,6 +54,8 @@ export default { } } else if (this.activeTab === 1) { this.$store.commit('mode', constants.modes.isochrones) + } else if (this.activeTab === 2) { + this.$store.commit('mode', constants.modes.optimization) } }, /** @@ -57,14 +64,23 @@ export default { * and render the map with this data (place or route) */ setTab () { - if (!this.hasPlacesAndDirectionsTab) ( - this.$store.commit('mode', constants.modes.isochrones) - ) + if (!this.hasPlacesAndDirectionsTab) { + if (!this.hasIsochronesTab) { + this.$store.commit('mode', constants.modes.optimization) + } else { + this.$store.commit('mode', constants.modes.isochrones) + } + } if (this.hasIsochronesTab && this.$store.getters.mode === constants.modes.isochrones) { this.activeTab = 1 if (this.$mdAndUpResolution && !this.$store.getters.embed) { this.$store.commit('setLeftSideBarIsOpen', true) } + } else if (this.hasOptimizationTab && this.$store.getters.mode === constants.modes.optimization) { + this.activeTab = 2 + if (this.$mdAndUpResolution && !this.$store.getters.embed) { + this.$store.commit('setLeftSideBarIsOpen', true) + } } else { this.activeTab = 0 diff --git a/src/fragments/html-marker/html-marker.js b/src/fragments/html-marker/html-marker.js index 609079bed..65861c5a4 100644 --- a/src/fragments/html-marker/html-marker.js +++ b/src/fragments/html-marker/html-marker.js @@ -9,19 +9,19 @@ export default { required: false }, markerNumber: { - type: Number, + type: String, required: false } }, computed: { doubleDigitText () { - return this.markerNumber && this.markerNumber > 9 + return this.markerNumber.length === 2 }, tripleDigitText () { - return this.markerNumber && this.markerNumber > 99 + return this.markerNumber.length === 3 }, fourDigitText () { - return this.markerNumber && this.markerNumber > 999 + return this.markerNumber.length === 4 } } } diff --git a/src/fragments/map-view/components/map-view-markers/MapViewMarkers.vue b/src/fragments/map-view/components/map-view-markers/MapViewMarkers.vue index d2fbf2248..38ab39739 100644 --- a/src/fragments/map-view/components/map-view-markers/MapViewMarkers.vue +++ b/src/fragments/map-view/components/map-view-markers/MapViewMarkers.vue @@ -9,7 +9,7 @@ {{marker.label}}
+ @click="removePlace(marker, index)"> delete
{{marker.label}} +
delete + + edit + settings_ethernet diff --git a/src/fragments/map-view/i18n/map-view.i18n.en-us.js b/src/fragments/map-view/i18n/map-view.i18n.en-us.js index a22878c33..0623e112e 100644 --- a/src/fragments/map-view/i18n/map-view.i18n.en-us.js +++ b/src/fragments/map-view/i18n/map-view.i18n.en-us.js @@ -53,6 +53,7 @@ export default { yourLocation: 'Use your location', setMyLocationAsMapCenter: 'Do you want to center the map at your current location? This will improve place search precision. You will have to authorize it if prompted.', removePlace: 'Remove place', + editDetails: 'Edit details', viewOnORS: 'View on ORS', moveMapPositionToLeft: 'Move map center to the left', moveMapPositionToRight: 'Move map center to the right', diff --git a/src/fragments/map-view/map-view.js b/src/fragments/map-view/map-view.js index 22520415f..e07883133 100644 --- a/src/fragments/map-view/map-view.js +++ b/src/fragments/map-view/map-view.js @@ -933,7 +933,9 @@ export default { removePlace (markerIndex) { if (this.markers[markerIndex]) { let place = this.markers[markerIndex].place - let data = {place, index: markerIndex} + let job = this.markers[markerIndex].job + let vehicle = this.markers[markerIndex].vehicle + let data = {place, job, vehicle, index: markerIndex} this.$emit('removePlace', data) } }, @@ -1062,11 +1064,11 @@ export default { * */ loadMapData () { - if (this.localMapViewData.hasPlaces()) { + if (this.localMapViewData.hasPlaces() || this.localMapViewData.jobs.length || this.localMapViewData.vehicles.length) { this.defineActiveRouteIndex() this.updateMarkersLabel() if (this.hasOnlyOneMarker && this.fitBounds) { - this.setFocusedPlace(this.localMapViewData.places[0]) + this.setFocusedPlace(this.localMapViewData.places[0] || this.localMapViewData.jobs[0] || this.localMapViewData.vehicles[0]) } if (this.mode === constants.modes.place && this.hasOnlyOneMarker && appConfig.showAdminAreaPolygon) { this.loadAdminArea() @@ -1081,10 +1083,12 @@ export default { * @param {Place} place */ setFocusedPlace (place) { - let layer = place.layer || place.properties.layer - if (layer) { + if (place.layer || place.properties.layer) { + let layer = place.layer || place.properties.layer this.zoomLevel = GeoUtils.zoomLevelByLayer(layer) this.setMapCenter(place.getLatLng()) + } else { + this.setMapCenter(place.getLatLng()) } }, /** @@ -1134,6 +1138,8 @@ export default { if (this.localMapViewData.hasPlaces() || polylineData.length > 0) { let places = Place.getFilledPlaces(this.localMapViewData.places) this.dataBounds = GeoUtils.getBounds(places, polylineData) + } else if (this.localMapViewData.jobs.length || this.localMapViewData.vehicles.length) { + this.dataBounds = GeoUtils.getBounds([], polylineData) } else { this.dataBounds = null } diff --git a/src/models/map-view-data.js b/src/models/map-view-data.js index 64c100d22..1aa93edb5 100644 --- a/src/models/map-view-data.js +++ b/src/models/map-view-data.js @@ -1,6 +1,8 @@ import constants from '@/resources/constants' import utils from '@/support/utils' import Place from '@/models/place' +import Job from '@/models/job' +import Vehicle from '@/models/vehicle' /** * MapViewData class @@ -9,10 +11,12 @@ class MapViewData { /** * MapViewData constructor */ - constructor ({places = []} = {}) { + constructor ({places = [], jobs = [], vehicles = []} = {}) { this.polygons = [] this.options = {} // {origin: String, apiVersion: String, contentType: String, timestamp: timestamp, options: {avoid_polygons: Object, avoid_features: Array}}, this.places = places ? Place.placesFromFeatures(places) : [] // array of Place objects @see /src/models/place + this.jobs = jobs ? Job.jobsFromFeatures(jobs) : [] // array of Job objects @see /src/models/job + this.vehicles = vehicles ? Vehicle.vehiclesFromFeatures(vehicles) : [] // array of Vehicle objects @see /src/models/vehicle this.pois = [] // array of Place objects @see /src/models/place this.routes = [] // array of route objects containing route data and summary this.origin = 'response' // where the data comes from @@ -76,6 +80,20 @@ class MapViewData { } } + for (let i = 0; i < this.jobs.length; i++) { + if (this.jobs[i] instanceof Job) { + const job = this.jobs[i] + mapViewDataClone.jobs.push(job.clone()) + } + } + + for (let i = 0; i < this.vehicles.length; i++) { + if (this.vehicles[i] instanceof Vehicle) { + const vehicle = this.vehicles[i] + mapViewDataClone.vehicles.push(vehicle.clone()) + } + } + for (let k = 0; k < this.pois.length; k++) { if (this.pois[k] instanceof Place) { const place = this.pois[k] @@ -108,6 +126,7 @@ class MapViewData { case 'Point': { const lat = feature.geometry.coordinates[0] const lon = feature.geometry.coordinates[1] + // TODO: OPTIMIZATION - load jobs and vehicles const place = new Place(lat, lon, feature.properties.label, { properties: feature.properties }) feature.latlngs = feature.geometry.coordinates mapViewAta.places.push(place) @@ -141,6 +160,7 @@ class MapViewData { geoJsonData.features.push(routeFeature) } + // TODO: OPTIMIZATION - build Points with properties for jobs and vehicles // Build and add places/points features to the GeoJSON for (const plaKey in this.places) { const placeFeature = { diff --git a/src/pages/maps/Maps.vue b/src/pages/maps/Maps.vue index bf2441b0d..b7f5b6935 100644 --- a/src/pages/maps/Maps.vue +++ b/src/pages/maps/Maps.vue @@ -38,7 +38,10 @@ @directChanged="directChanged" @mapCenterChanged="mapCenterChanged" @setInputPlace="setInputPlace" - @markerClicked="markerClicked"> + @markerClicked="markerClicked" + @addJob="addJob" + @addVehicle="addVehicle" + > diff --git a/src/pages/maps/maps.js b/src/pages/maps/maps.js index a26c3f70b..e7f538d74 100644 --- a/src/pages/maps/maps.js +++ b/src/pages/maps/maps.js @@ -646,6 +646,12 @@ export default { polygons = PolygonUtils.splitMultiPolygonIntoPolygons(multiPolygon) } this.localAvoidPolygons = polygons + }, + addJob(data) { + EventBus.$emit('addJob', data) + }, + addVehicle(data) { + EventBus.$emit('addVehicle', data) } }, diff --git a/src/resources/constants.js b/src/resources/constants.js index b555af045..0d9608aa7 100644 --- a/src/resources/constants.js +++ b/src/resources/constants.js @@ -1,3 +1,5 @@ +import theme from '@/config/theme' + const constants = { apiVersion: '5.0', orsPublicHost: 'https://maps.openrouteservice.org', @@ -97,6 +99,11 @@ const constants = { osm: 'https://osm.org', disaster: 'https://disaster.openrouteservice.org', ors: 'https://openrouteservice.org' + }, + vehicleColors: { + 1: theme.primary, + 2: theme.info, + 3: theme.success } } diff --git a/src/support/geo-utils.js b/src/support/geo-utils.js index 78da1e1c4..9923c1deb 100644 --- a/src/support/geo-utils.js +++ b/src/support/geo-utils.js @@ -8,6 +8,8 @@ import HtmlMarker from '@/fragments/html-marker/HtmlMarker' // The import below will add some methods to Leaflet.GeometryUtil // Even if it is not accessed within this class, it is being used! import 'leaflet-geometryutil' +import constants from '@/resources/constants' +import theme from '@/config/theme' // noinspection GrazieInspection const geoUtils = { @@ -47,23 +49,23 @@ const geoUtils = { * @param index * @param lastIndexKey * @param {Boolean} isRoute - * @returns {Array} of markers + * @returns {String} of markers */ getMarkerColor: (index, lastIndexKey, isRoute) => { - let coloredMarkerName + let color if (isRoute) { if (index === 0) { - coloredMarkerName = 'green' + color = 'green' } else if (lastIndexKey === index) { - coloredMarkerName = 'red' + color = 'red' } else { - coloredMarkerName = '#206fe2' + color = '#206fe2' } } else { - coloredMarkerName = '#206fe2' + color = '#206fe2' } - return coloredMarkerName + return color }, /** @@ -82,20 +84,20 @@ const geoUtils = { if (place.lng && place.lat) { // Define the marker color const lastIndexKey = places.length - 1 - let coloredMarkerName = geoUtils.getMarkerColor(key, lastIndexKey, isRoute) + let color = geoUtils.getMarkerColor(key, lastIndexKey, isRoute) if (highlightedPlace) { if (place.equals(highlightedPlace)) { - coloredMarkerName = 'red' + color = 'red' } } else if (Number(key) === 0 && !isRoute || places.length === 1) { - coloredMarkerName = 'red' + color = 'red' } let buildAsRoute = isRoute && places.length > 1 // Build the marker - const markerIcon = geoUtils.buildMarkerIcon(coloredMarkerName, key, buildAsRoute) + const markerIcon = geoUtils.buildMarkerIcon(color, key, buildAsRoute) const marker = { position: { lng: place.lng, @@ -207,7 +209,7 @@ const geoUtils = { color: color } if (isRoute && index !== null) { - propsData.markerNumber = Number(index) + 1 + propsData.markerNumber = (Number(index) + 1).toString() } const htmlMarkerClass = Vue.extend(HtmlMarker) const htmlIconInstance = new htmlMarkerClass({ @@ -585,6 +587,74 @@ const geoUtils = { lng -= 360 } return lng + }, + buildOptimizationMarkers(jobs, vehicles) { + const markers = [] + for (const job of jobs) { + if (job.lng && job.lat) { + // Build the marker + let propsData = { + color: theme.dark, + markerNumber: job.id.toString() + } + const htmlMarkerClass = Vue.extend(HtmlMarker) + const htmlIconInstance = new htmlMarkerClass({ + propsData + }) + htmlIconInstance.$mount() + let markerHtml = htmlIconInstance.$el.innerHTML + + const markerIcon = Leaflet.divIcon({ + className: 'custom-div-icon', + html: markerHtml, + iconSize: [30, 42], + iconAnchor: [15, 42] + }) + const marker = { + position: { + lng: job.lng, + lat: job.lat + }, + icon: markerIcon, + label: `Job ${job.id} - ${job.lng},${job.lat}`, + job: job + } + markers.push(marker) + } + } + for (const v of vehicles) { + if (v.lng && v.lat) { + // Build the marker + let propsData = { + color: constants.vehicleColors[v.id], + markerNumber: `V ${v.id.toString()}` + } + const htmlMarkerClass = Vue.extend(HtmlMarker) + const htmlIconInstance = new htmlMarkerClass({ + propsData + }) + htmlIconInstance.$mount() + let markerHtml = htmlIconInstance.$el.innerHTML + + const markerIcon = Leaflet.divIcon({ + className: 'custom-div-icon', + html: markerHtml, + iconSize: [30, 42], + iconAnchor: [15, 42] + }) + const marker = { + position: { + lng: v.lng, + lat: v.lat + }, + icon: markerIcon, + label: `Vehicle ${v.id} - ${v.lng},${v.lat}`, + vehicle: v + } + markers.push(marker) + } + } + return markers } } export default geoUtils diff --git a/src/support/map-data-services/ors-params-parser.js b/src/support/map-data-services/ors-params-parser.js index 45b62cd31..24bb74245 100644 --- a/src/support/map-data-services/ors-params-parser.js +++ b/src/support/map-data-services/ors-params-parser.js @@ -179,6 +179,26 @@ const orsParamsParser = { }) }, + /** + * Build optimization search args + * @param {Array} jobs + * @param {Array} vehicles + * @returns {Object} args + */ + buildOptimizationArgs: (jobs, vehicles) => { + let jsonJobs = [] + let jsonVehicles = [] + for (const job of jobs) { + jsonJobs.push(job.toJSON()) + } + for (const v of vehicles) { + jsonVehicles.push(v.toJSON()) + } + return new Promise((resolve) => { + resolve({'jobs': jsonJobs, 'vehicles': jsonVehicles,'options':{'g':'true'}}) + }) + }, + /** * Build routing request args object * @param {Array} places From 7c9a11ce49b0ae169966ae8a63a280a3e5439826 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 11 Apr 2024 13:52:04 +0200 Subject: [PATCH 006/109] feat: adjust height of sidebar for optimization tab hide profile-selector in optimization tab, since it is not used for optimization --- src/fragments/sidebar/Sidebar.vue | 4 ++-- src/fragments/sidebar/sidebar.js | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/fragments/sidebar/Sidebar.vue b/src/fragments/sidebar/Sidebar.vue index cfe38305a..08105983f 100755 --- a/src/fragments/sidebar/Sidebar.vue +++ b/src/fragments/sidebar/Sidebar.vue @@ -13,7 +13,7 @@ :permanent="$store.getters.leftSideBarPinned" :class="{'auto-height': $lowResolution && !$store.getters.leftSideBarPinned, 'full-height': $store.getters.leftSideBarPinned}"> -
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ + +
+ diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index 054374b35..f033af141 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -1,5 +1,6 @@ import RouteImporter from '@/fragments/forms/route-importer/RouteImporter.vue' import MapFormBtn from '@/fragments/forms/map-form-btn/MapFormBtn.vue' +import PlaceAutocomplete from '@/fragments/forms/place-input/PlaceAutocomplete.vue' import {EventBus} from '@/common/event-bus' import Job from '@/models/job' import Vehicle from '@/models/vehicle' @@ -45,6 +46,7 @@ export default { RouteImporter, Download, MapFormBtn, + PlaceAutocomplete, EventBus }, computed: { @@ -83,6 +85,11 @@ export default { } } }, + // returns true if start and end point are the same + sameStartEndPoint () { + const id = this.editId - 1 + return this.editData[id].start[0] === this.editData[id].end[0] && this.editData[id].start[1] === this.editData[id].end[1] + }, }, created () { if (this.editProp === 'jobs') { @@ -176,12 +183,31 @@ export default { } } }, + // delete old location when switching to search to activate placeAutocomplete + switchToSearch (mode) { + if (this.jobsBox) { + this.editData[this.editId - 1].location = null + } else if (this.vehiclesBox) { + if (mode === 'start') { + if (!this.sameStartEndPoint) { + this.onlyStartPoint = true + } + this.editData[this.editId - 1].start = null + } else if (mode === 'end') { + this.newEndPoint = true + this.editData[this.editId - 1].end = null + } + } + }, removeItem (id) { this.editData.splice(id-1,1) for (const i in this.editData) { this.editData[i].setId(parseInt(i)+1) } }, + removeEndPoint (index) { + this.editData[index].end = this.editData[index].start + }, duplicateItem (index) { let newItem = this.editData[index-1].clone() let id = this.editData.length + 1 diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 74c7b6eda..005c47fde 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -160,25 +160,33 @@ export default { // place is picked from Map EventBus.$on('setInputPlace', (data) => { if (context.active) { + let id = data.placeInputId // pickEditSource indicates which property the place should fill if (data.pickEditSource === 'jobs') { - let job = Job.fromPlace(data.place) - let id = data.placeInputId - job.setId(id) - - context.jobs.push(job) + if (id > context.jobs.length) { + let job = Job.fromPlace(data.place) + job.setId(id) + context.jobs.push(job) + } else { + context.jobs[data.pickPlaceIndex].location = data.place.coordinates + } context.manageJobs(id) } else if (data.pickEditSource === 'vehicleStart') { - let v = Vehicle.fromPlace(data.place) - let id = data.placeInputId - v.setId(id) - - context.vehicles.push(v) + if (id > context.vehicles.length) { + let v = Vehicle.fromPlace(data.place) + v.setId(id) + context.vehicles.push(v) + } else { + const v = context.vehicles[data.pickPlaceIndex] + if (v.end[0] === v.start[0] && v.end[1] === v.start[1]) { + context.vehicles[data.pickPlaceIndex].end = data.place.coordinates + } + context.vehicles[data.pickPlaceIndex].start = data.place.coordinates + } context.manageVehicles(id) } else if (data.pickEditSource === 'vehicleEnd') { this.vehicles[data.pickPlaceIndex].end = data.place.coordinates - - context.manageVehicles(data.placeInputId) + context.manageVehicles(id) } } else { context.setSidebarIsOpen(true) diff --git a/src/fragments/forms/place-input/PlaceAutocomplete.vue b/src/fragments/forms/place-input/PlaceAutocomplete.vue new file mode 100644 index 000000000..7299c89ed --- /dev/null +++ b/src/fragments/forms/place-input/PlaceAutocomplete.vue @@ -0,0 +1,45 @@ + + + + diff --git a/src/fragments/forms/place-input/place-autocomplete.css b/src/fragments/forms/place-input/place-autocomplete.css new file mode 100644 index 000000000..f9d271dc1 --- /dev/null +++ b/src/fragments/forms/place-input/place-autocomplete.css @@ -0,0 +1,4 @@ +.append-input-btn { + margin-top: -7px; + padding-left: 15px; +} diff --git a/src/fragments/forms/place-input/place-autocomplete.js b/src/fragments/forms/place-input/place-autocomplete.js new file mode 100644 index 000000000..0b77bba65 --- /dev/null +++ b/src/fragments/forms/place-input/place-autocomplete.js @@ -0,0 +1,336 @@ +import {PlacesSearch, ReverseGeocode} from '@/support/ors-api-runner' +import Utils from '@/support/utils' +import GeoUtils from '@/support/geo-utils' +import appConfig from '@/config/app-config' +import {EventBus} from '@/common/event-bus' +import Job from '@/models/job' +import Vehicle from '@/models/vehicle' +import Place from '@/models/place' + +export default { + data: () => ({ + model: new Place(), + localModel: null, + editSource: null, + focused: false, + searching: false, + debounceTimeoutId: null, + pickPlaceSupported: true, + showEditBox: true + }), + props: { + editId: { + Type: Array, + Required: true + }, + jobs: { + Type: Array[Job], + Required: false + }, + vehicles: { + Type: Array[Vehicle], + Required: false + }, + newEndPoint: { + Type: Boolean, + Required: false + }, + onlyStartPoint: { + Type: Boolean, + Required: false + }, + }, + components: { + EventBus + }, + computed: { + // Return an array with the place's suggestion based on the model suggestion data + placeSuggestions () { + if (!this.focused) { + return [] + } + let suggestions = [] + if (this.localModel.nameIsNumeric()) { + const latLng = this.model.getLatLng() + const rawCoordinatesPlace = new Place(latLng.lng, latLng.lat, `${latLng.lng},${latLng.lat}`, { properties: { layer: 'rawCoordinate' } }) + rawCoordinatesPlace.rawCoordinate = true + suggestions.push(rawCoordinatesPlace) + } + suggestions = suggestions.concat(this.localModel.suggestions) + return suggestions + }, + appendBtn () { + if (this.$lowResolution || this.localModel.isEmpty()) { + return 'map' + } + } + }, + created() { + this.localModel = this.model.clone() + this.getImgSrc = Utils.getImgSrc + + this.setSource() + }, + methods: { + setFocus (data) { + // When the user clicks outside an input this method is called and is intended to + // set the focus as false in this case. To do so, we check if the was previously focused + // The parameters passed (automatically) by the click-outside is expected to be MouseEvent object and not a boolean. + if (typeof data === 'object' && data.clickedOutside) { + if (this.inputWasActiveAndLostFocus(data)) { + this.emptyPickPlaceSource() + this.focused = false + } + } else { + this.focused = data // data is boolean in this case + } + // If the job location is in search mode, then run the autocompleteSearch that will show the suggestions + if (this.focused) { + this.autocompleteSearch() + } + }, + // set the parent source by checking which property was handed into this component + setSource () { + if (this.jobs) { + this.editSource = 'jobs' + } else if (this.newEndPoint) { + this.editSource = 'vehicleEnd' + } else if (this.vehicles) { + this.editSource = 'vehicleStart' + } + }, + + // Handle the click on the pick a place from the map btn + pickPlaceMapClick (event) { + this.showInfo(this.$t('placeInput.clickOnTheMapToSelectAPlace')) + this.localModel = new Place() + + this.showEditBox = false + EventBus.$emit('pickAPlace') + + this.setPickPlaceSource() + event.stopPropagation() + event.preventDefault() + }, + // Set the pick place input source + setPickPlaceSource () { + if (this.pickPlaceSupported) { + this.$store.commit('pickPlaceIndex', this.editId - 1) + this.$store.commit('pickPlaceId', this.editId) + this.$store.commit('pickEditSource', this.editSource) + } + }, + // Empty the pick place source + emptyPickPlaceSource() { + this.$store.commit('pickPlaceIndex', null) + this.$store.commit('pickPlaceId', null) + this.$store.commit('pickEditSource', null) + }, + // highlight typed place name + highlightedName (placeName) { + let searchMask = this.localModel.placeName + const regEx = new RegExp(searchMask, 'ig') + let localPlaceName = this.localModel.placeName + let replaceMask + if ((placeName.toLowerCase()).indexOf(this.localModel.placeName.toLowerCase() + ' ') === 0) { + localPlaceName = localPlaceName[0].toUpperCase() + localPlaceName.substring(1) + ' ' + } else if ((placeName.toLowerCase()).indexOf(this.localModel.placeName.toLowerCase()) === 0 ) { + localPlaceName = localPlaceName[0].toUpperCase() + localPlaceName.substring(1) + } else if ((placeName.toLowerCase()).indexOf(this.localModel.placeName.toLowerCase()) > 0 ) { + localPlaceName = ' ' + localPlaceName[0].toUpperCase() + localPlaceName.substring(1) + } + replaceMask = `${localPlaceName}` + + placeName = placeName.replace(regEx, replaceMask) + return placeName.trim() + }, + showAreaIcon (place) { + return place.properties.layer === 'country' || place.properties.layer === 'region' + }, + // Get layer translation based on the layer name or fall back to a default one if not available + getLayerTranslation (layer) { + let transKey = 'global.layers.'+ layer + let translation = this.$t(transKey) + if (translation !== transKey) { + return translation + } else { + return this.$t('global.layers.notAvailable') + } + }, + // Get the distance between the current map center and a suggestion location + distance (suggestedPlace) { + // Set origin and destination + const fromLatLng = { lat: this.$store.getters.mapCenter.lat, lng: this.$store.getters.mapCenter.lng } + const toLatLng = { lat: suggestedPlace.lat, lng: suggestedPlace.lng } + + // calculate the distance between the two points + let distance = GeoUtils.calculateDistanceBetweenLocations(fromLatLng, toLatLng, this.$store.getters.mapSettings.unit) + + if (distance > 0) { + distance = distance.toFixed(1) + } else { + distance = 0 + } + return distance + }, + // Set a suggestion item clicked as selected and emit the selected event + selectSuggestion (suggestedPlace) { + // Only proceed if it is being selected + // a place different from the current one + if (!suggestedPlace.equals(this.model)) { + // If the suggested place is a ra coordinate, remove the layer attribute + // because it is a placeholder, not a valid layer + if (suggestedPlace.rawCoordinate) { + delete suggestedPlace.properties.layer + } + this.selectPlace(suggestedPlace) + this.$forceUpdate() + this.selected() + } + }, + // Set a suggested place as the selected one for a given place input + selectPlace (place) { + // We shall not reassign an external object, so we update each property + this.model.placeName = place.properties.label || place.placeName + this.model.placeId = place.properties.id + this.model.setLngLat(place.lng, place.lat) + this.model.properties = place.properties + this.model.suggestions = [] + this.searching = false + // If a place is selected from a suggestion then no current location must be active. + this.$store.commit('currentLocation', null) + }, + // Run the place selection hook and emit the selected event + selected () { + this.focused = false + if (this.editSource === 'jobs') { + this.jobs[this.editId-1].location = this.model.coordinates + } else if (['vehicleStart', 'vehicleEnd'].includes(this.editSource)) { + if (!this.newEndPoint) { + if (this.onlyStartPoint) { + this.vehicles[this.editId-1].start = this.model.coordinates + this.onlyStartPoint = false + } else { + this.vehicles[this.editId-1].start = this.model.coordinates + this.vehicles[this.editId-1].end = this.model.coordinates + } + } else { + this.vehicles[this.editId-1].end = this.model.coordinates + this.newEndPoint = false + } + } + }, + // Handles the input change with a debounce-timeout + locationInputChanged (event = null) { + this.localModel = this.model.clone() + if (event) { + const isPasteEvent = event instanceof ClipboardEvent + // In case of a ClipboardEvent (ctr + v) we must just ignore, since the input + // model has not changed yet, and it will trigger another change event when it changes + if (!isPasteEvent) { + event.preventDefault() + event.stopPropagation() + clearTimeout(this.debounceTimeoutId) + const context = this + + // Resolve the model using a debounce to avoid unnecessary sequential requests + // Make sure that the changes in the input are debounced + this.debounceTimeoutId = setTimeout(function () { + if (context.localModel.nameIsNumeric()) { + let latLng = context.localModel.getLatLng() + context.model.setLngLat(latLng.lng, latLng.lat) + } + if (event.key === 'Enter') { + context.focused = false + // We can only try to auto select the first result if the inputted text is not a coordinate + if (!context.localModel.nameIsNumeric()) { + if (appConfig.autoSelectFirstExactAddressMatchOnSearchEnter) { + EventBus.$emit('showLoading', true) + PlacesSearch(context.localModel.placeName, 10).then(places => { + // If the first result is an address and the match_type is exact, then we auto select the first item on the enter/return action + const addresses = context.lodash.filter(places, (p) => { + return (p.properties.layer === 'address' || p.properties.layer === 'postalcode') && p.properties.match_type === 'exact' + }) + + if (addresses.length === 1) { + context.selectSuggestion(addresses[0]) + } else { // if not call the search handler + context.autocompleteSearch() + } + }).catch(response => { + console.log(response) + // In case of any fail, call the search mode handler + context.autocompleteSearch() + }).finally(() => { + EventBus.$emit('showLoading', false) + }) + } else { + context.autocompleteSearch() + } + } else { // If a coordinate was inputted, call the auto complete + context.autocompleteSearch() + } + } else { + context.autocompleteSearch() + } + }, 1000) + } + } + }, + // Search for a place based on the place input value + autocompleteSearch () { + // Make sure that the local model is up-to-date + if (!this.localModel || this.localModel.placeName.length === 0) { + this.localModel = this.model.clone() + } + if (this.localModel.nameIsNumeric()) { + const latLng = this.model.getLatLng() + EventBus.$emit('showLoading', true) + const context = this + ReverseGeocode(latLng.lat, latLng.lng, 10).then(places => { + const place = new Place(latLng.lng, latLng.lat) + place.setSuggestions(places) + context.localModel = place + context.focused = true + this.focusIsAutomatic = false + if (places.length > 1) { + Utils.hideMobileKeyboard() + } + context.$emit('autocompleted') + }).catch(response => { + console.log(response) + }).finally(() => { + context.searching = false + EventBus.$emit('showLoading', false) + }) + } else { + this.searching = true + if (!this.localModel.placeName || this.model.placeName.length === 0) { + this.localModel = new Place() + this.searching = false + } else { + const context = this + // Run the place search + EventBus.$emit('showLoading', true) + PlacesSearch(this.localModel.placeName, 10).then(places => { + context.localModel.setSuggestions(places) + context.focused = true + this.focusIsAutomatic = false + if (places.length === 0) { + context.showInfo(context.$t('placeInput.noPlaceFound')) + } else if (places.length > 1) { + Utils.hideMobileKeyboard() + } + context.$emit('autocompleted') + }).catch(response => { + console.log(response) + context.showError(context.$t('placeInput.unknownSearchPlaceError')) + }).finally(() => { + context.searching = false + EventBus.$emit('showLoading', false) + }) + } + } + }, + } +} From 3c6abf42105d39024749c952e33d55db44e77584 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 25 Apr 2024 16:40:13 +0200 Subject: [PATCH 016/109] feat: profile selection for vehicles --- .../components/edit-dialog/EditDialog.vue | 10 ++++++ .../components/edit-dialog/edit-dialog.js | 32 +++++++++++++++++++ .../ProfileSelectorOption.vue | 6 ++-- .../profile-selector-option.js | 8 ++++- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 2cc63e9b1..be883f206 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -66,6 +66,16 @@ + + + + + + + + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index f033af141..3f46f9bc6 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -1,6 +1,9 @@ import RouteImporter from '@/fragments/forms/route-importer/RouteImporter.vue' import MapFormBtn from '@/fragments/forms/map-form-btn/MapFormBtn.vue' +import OrsFilterUtil, {vehicleIcon} from '@/support/map-data-services/ors-filter-util' +import constants from '@/resources/constants' import PlaceAutocomplete from '@/fragments/forms/place-input/PlaceAutocomplete.vue' +import ProfileSelectorOption from '@/fragments/forms/profile-selector/components/profile-selector-option/ProfileSelectorOption' import {EventBus} from '@/common/event-bus' import Job from '@/models/job' import Vehicle from '@/models/vehicle' @@ -21,6 +24,8 @@ export default { focused: false, searching: false, debounceTimeoutId: null, + onlyStartPoint: false, + newEndPoint: false, activeProfileSlug: null, activeVehicleType: null, }), @@ -47,6 +52,7 @@ export default { Download, MapFormBtn, PlaceAutocomplete, + ProfileSelectorOption, EventBus }, computed: { @@ -90,6 +96,10 @@ export default { const id = this.editId - 1 return this.editData[id].start[0] === this.editData[id].end[0] && this.editData[id].start[1] === this.editData[id].end[1] }, + profilesMapping () { + const filter = OrsFilterUtil.getFilterRefByName(constants.profileFilterName) + return filter.mapping + }, }, created () { if (this.editProp === 'jobs') { @@ -226,5 +236,27 @@ export default { } return names }, + + // when profile selected in selector, update vehicle properties + profileSelected (data) { + this.activeProfileSlug = data.profileSlug + this.activeVehicleType = data.vehicleTypeSlug + + OrsFilterUtil.setFilterValue(constants.profileFilterName, data.profileSlug) + + this.editData[this.editId - 1].profile = data.profileSlug + }, + // return currently active vehicle profile property + vehicleProfile(id) { + const vt = this.editData[id].profile + let profile + if (vt !== 'wheelchair') { + const profilePart = vt.split('-')[0] + profile = profilePart.concat('-*') + } else { + profile = vt + } + return profile + }, } } diff --git a/src/fragments/forms/profile-selector/components/profile-selector-option/ProfileSelectorOption.vue b/src/fragments/forms/profile-selector/components/profile-selector-option/ProfileSelectorOption.vue index 5b277c360..047e587ad 100644 --- a/src/fragments/forms/profile-selector/components/profile-selector-option/ProfileSelectorOption.vue +++ b/src/fragments/forms/profile-selector/components/profile-selector-option/ProfileSelectorOption.vue @@ -1,12 +1,12 @@ - keyboard_arrow_down diff --git a/src/fragments/forms/profile-selector/components/profile-selector-option/profile-selector-option.js b/src/fragments/forms/profile-selector/components/profile-selector-option/profile-selector-option.js index fc9632b8b..531e2dce4 100644 --- a/src/fragments/forms/profile-selector/components/profile-selector-option/profile-selector-option.js +++ b/src/fragments/forms/profile-selector/components/profile-selector-option/profile-selector-option.js @@ -17,6 +17,10 @@ export default { activeVehicleType: { type: String, required: false + }, + isVehicle: { + type: Boolean, + required: false } }, created() { @@ -29,7 +33,9 @@ export default { }, computed: { rootProfileActive () { - if (this.$store.getters.mapSettings.skipAllSegments) { + if (this.isVehicle && this.activeVehicleType === this.profile.slug) { + return true + } else if (this.$store.getters.mapSettings.skipAllSegments) { return false } if (this.localActiveProfileSlug === this.profile.slug) { From 98981b4a3fe04a3451a31a46f74a974fafa9eb01 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 25 Apr 2024 16:47:10 +0200 Subject: [PATCH 017/109] feat: edit skills of jobs/vehicles select skills for jobs/vehicles in edit dialog, edit skills in separate edit skills dialog --- .../components/optimization/Optimization.vue | 10 +++ .../components/edit-dialog/EditDialog.vue | 12 ++++ .../components/edit-dialog/edit-dialog.js | 13 ++++ .../components/edit-skills/EditSkills.vue | 51 ++++++++++++++ .../components/edit-skills/edit-skills.css | 16 +++++ .../components/edit-skills/edit-skills.js | 67 +++++++++++++++++++ .../edit-skills/i18n/skill.i18n.de-de.js | 6 ++ .../edit-skills/i18n/skill.i18n.en-us.js | 6 ++ .../components/optimization/optimization.js | 11 ++- 9 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js diff --git a/src/fragments/forms/map-form/components/optimization/Optimization.vue b/src/fragments/forms/map-form/components/optimization/Optimization.vue index af34c5089..ea6bb5c6a 100644 --- a/src/fragments/forms/map-form/components/optimization/Optimization.vue +++ b/src/fragments/forms/map-form/components/optimization/Optimization.vue @@ -52,6 +52,15 @@ + +
diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index be883f206..a99f7eb89 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -81,6 +81,17 @@ + + + +
@@ -102,6 +113,7 @@ + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index 3f46f9bc6..973150b75 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -4,6 +4,7 @@ import OrsFilterUtil, {vehicleIcon} from '@/support/map-data-services/ors-filter import constants from '@/resources/constants' import PlaceAutocomplete from '@/fragments/forms/place-input/PlaceAutocomplete.vue' import ProfileSelectorOption from '@/fragments/forms/profile-selector/components/profile-selector-option/ProfileSelectorOption' +import EditSkills from '@/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue' import {EventBus} from '@/common/event-bus' import Job from '@/models/job' import Vehicle from '@/models/vehicle' @@ -24,6 +25,8 @@ export default { focused: false, searching: false, debounceTimeoutId: null, + showSkillManagement: false, + isSkillsOpen: false, onlyStartPoint: false, newEndPoint: false, activeProfileSlug: null, @@ -53,6 +56,7 @@ export default { MapFormBtn, PlaceAutocomplete, ProfileSelectorOption, + EditSkills, EventBus }, computed: { @@ -225,6 +229,15 @@ export default { this.editData.push(newItem) this.editId = id }, + // update job skills selection when the skills were changed + skillsChanged(editedSkills) { + let newSkills = [] + for (const skill of editedSkills) { + newSkills.push(skill.clone()) + } + this.editSkills = newSkills + this.$emit('skillsChanged', this.editSkills) + }, skillNames(item) { let names = '' for (const skill of item.skills) { diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue new file mode 100644 index 000000000..4699c8e9e --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue @@ -0,0 +1,51 @@ + + + + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css new file mode 100644 index 000000000..cdb3863c5 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css @@ -0,0 +1,16 @@ +.edit-skills-btn { + padding: 0 20px; + min-width: 0; + float: right; + margin: 0; + height: 24px; + background: white; +} + +.remove-btn { + float: right; +} + +.edit-btn { + float: right; +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js new file mode 100644 index 000000000..9b963ed2c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js @@ -0,0 +1,67 @@ +import {EventBus} from '@/common/event-bus' +import Skill from '@/models/skill' + +export default { + data: () => ({ + isSkillsOpen: true, + editId: 0, + editSkills: [], + selectedSkills: [] + }), + props: { + skills: { + Type: Array[Skill], + Required: false + }, + }, + components: { + EventBus + }, + computed: { + editSkillsJSON () { + const jsonSkills = [] + for (const skill of this.editSkills) { + jsonSkills.push(skill.toJSON()) + } + return jsonSkills + } + }, + created() { + for (const skill of this.skills) { + this.editSkills.push(skill.clone()) + } + + const context = this + // edit Skills box is open + EventBus.$on('showSkillsModal', (editId) => { + context.isSkillsOpen = true + context.editId = editId + }) + }, + methods: { + // close editSkills dialog + closeSkillsModal() { + this.isSkillsOpen = false + this.$emit('close') + }, + + // save changed skills and emit event to update in component + saveSkills () { + this.$emit('skillsChanged', this.editSkills) + localStorage.setItem('skills', JSON.stringify(this.editSkillsJSON)) + this.closeSkillsModal() + }, + // add a new skill + addSkill () { + const newSkill = new Skill('', this.editSkills.length + 1) + this.editSkills.push(newSkill) + }, + // delete a skill + removeSkill (id) { + this.editSkills.splice(id-1,1) + for (const i in this.editSkills) { + this.editSkills[i].setId(parseInt(i)+1) + } + }, + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js new file mode 100644 index 000000000..a154c751b --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js @@ -0,0 +1,6 @@ +export default { + skill: { + copiedToClipboard: 'Skills copied to clipboard', + copiedToClipboardFailed: 'Copy to clipboard failed' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js new file mode 100644 index 000000000..a154c751b --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js @@ -0,0 +1,6 @@ +export default { + skill: { + copiedToClipboard: 'Skills copied to clipboard', + copiedToClipboardFailed: 'Copy to clipboard failed' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 005c47fde..42f824c52 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -18,6 +18,7 @@ import OptimizationDetails from './components/optimization-details/OptimizationD import JobList from './components/job-list/JobList.vue' import VehicleList from './components/vehicle-list/VehicleList.vue' import EditDialog from './components/edit-dialog/EditDialog.vue' +import EditSkills from './components/edit-skills/EditSkills.vue' export default { mixins: [MapFormMixin], @@ -30,7 +31,8 @@ export default { pickPlaceSupported: true, showEditDialog: false, editProp: '', - editData: [] + editData: [], + showSkillManagement: false }), components: { FieldsContainer, @@ -39,6 +41,7 @@ export default { JobList, VehicleList, EditDialog, + EditSkills }, computed: { jobsJSON () { @@ -266,6 +269,12 @@ export default { this.setPickPlaceSource(this.vehicles) }, + // open editSkills dialog + manageSkills(skillId) { + this.showSkillManagement = true + EventBus.$emit('showSkillsModal', skillId) + }, + // Set the pick place input source setPickPlaceSource (source) { if (this.pickPlaceSupported) { From fc674ac8e352fb0433a3f40b9b25cb2a6ad70c8d Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 25 Apr 2024 17:23:45 +0200 Subject: [PATCH 018/109] feat: download jobs/vehicles/skills copy JSON to clipboard or download JSON, GeoJSON or CSV --- .../map-form/components/download/Download.vue | 12 ++- .../map-form/components/download/download.css | 8 ++ .../map-form/components/download/download.js | 92 +++++++++++++++++-- .../download/i18n/download.i18n.en-us.js | 7 ++ .../components/edit-dialog/EditDialog.vue | 1 + .../components/edit-dialog/edit-dialog.js | 1 + .../components/edit-skills/EditSkills.vue | 1 + .../components/edit-skills/edit-skills.js | 4 +- 8 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 src/fragments/forms/map-form/components/download/download.css diff --git a/src/fragments/forms/map-form/components/download/Download.vue b/src/fragments/forms/map-form/components/download/Download.vue index fcdabe019..28c252181 100644 --- a/src/fragments/forms/map-form/components/download/Download.vue +++ b/src/fragments/forms/map-form/components/download/Download.vue @@ -1,9 +1,16 @@ + diff --git a/src/fragments/forms/map-form/components/download/download.css b/src/fragments/forms/map-form/components/download/download.css new file mode 100644 index 000000000..ebea18155 --- /dev/null +++ b/src/fragments/forms/map-form/components/download/download.css @@ -0,0 +1,8 @@ +.edit-btn { + padding: 0 20px; + min-width: 0; + float: right; + margin: 0; + height: 24px; + background: white; +} diff --git a/src/fragments/forms/map-form/components/download/download.js b/src/fragments/forms/map-form/components/download/download.js index aab6e5bc4..f2988ad1b 100644 --- a/src/fragments/forms/map-form/components/download/download.js +++ b/src/fragments/forms/map-form/components/download/download.js @@ -1,6 +1,8 @@ import OrsParamsParser from '@/support/map-data-services/ors-params-parser' import {Directions} from '@/support/ors-api-runner' import MapViewData from '@/models/map-view-data' +import Job from '@/models/job' +import Vehicle from '@/models/vehicle' import constants from '@/resources/constants' import toKml from '@maphubs/tokml' import toGpx from 'togpx' @@ -15,12 +17,20 @@ export default { props: { mapViewData: { Type: MapViewData, - Required: true + Required: false + }, + data: { + Type: Array, + Required: false + }, + editProp: { + Type: String, + Required: false }, downloadFormatsSupported: { Type: Array, default: function () { - return ['json', 'ors-gpx', 'geojson', 'to-gpx', 'kml'] + return ['json', 'ors-gpx', 'geojson', 'to-gpx', 'kml', 'csv'] } } }, @@ -31,9 +41,32 @@ export default { { text: 'GeoJSON', value: 'geojson', ext: 'json' }, { text: 'ORS API GPX', value: 'ors-gpx', ext: 'gpx' }, { text: `${this.$t('download.standard')} GPX`, value: 'to-gpx', ext: 'gpx' }, - { text: 'KML', value: 'kml', ext: 'kml' } + { text: 'KML', value: 'kml', ext: 'kml' }, + { text: 'CSV', value: 'csv', ext: 'csv'} ] }, + content () { + if (this.editProp === 'jobs') { + return { + copied: this.$t('download.jobsCopiedToClipboard'), + fileName: 'ors-jobs', + } + } else if (this.editProp === 'vehicles') { + return { + copied: this.$t('download.vehiclesCopiedToClipboard'), + fileName: 'ors-vehicles', + } + } else if (this.editProp === 'skills') { + return { + copied: this.$t('download.skillsCopiedToClipboard'), + fileName: 'ors-skills', + } + } else { + return { + fileName: this.defaultDownloadName, + } + } + }, /** * Return the name of the route first's point * @returns string @@ -56,14 +89,31 @@ export default { return context.downloadFormatsSupported.includes(f.value) }) return available - } + }, + dataJson () { + const jsonData = [] + for (const d of this.data) { + jsonData.push(d.toJSON()) + } + return jsonData + }, + dataGeoJson () { + if (this.editProp === 'skills') { + return Error('GeoJSON cannot be created since skills contain no geoinformation') + } + const jsonData = [] + for (const d of this.data) { + jsonData.push(d.toGeoJSON()) + } + return jsonData + }, }, methods: { /** * Set the default filename and format and open the download modal */ openDownload () { - this.downloadFileName = this.defaultDownloadName + this.downloadFileName = this.content.fileName this.downloadFormat = this.downloadFormats[0].value this.isDownloadModalOpen = true }, @@ -128,8 +178,12 @@ export default { try { if (context.downloadFormat === 'json') { // Get the ORS mapViewData model and stringify it - const orsJSONStr = JSON.stringify(context.mapViewData) - resolve(orsJSONStr) + if (this.mapViewData) { + jsonData = JSON.stringify(context.mapViewData) + } else { + jsonData = JSON.stringify(context.dataJson) + } + resolve(jsonData) } else if (context.downloadFormat === 'ors-gpx') { // If the format is ors-gpx, run anew request with the format being 'gpx' context.getORSGpx().then((orsGpx) => { @@ -143,7 +197,11 @@ export default { const toGPX = toGpx(geoJSON) resolve(toGPX) } else if (context.downloadFormat === 'geojson') { - jsonData = context.mapViewData.getGeoJson() + if (this.mapViewData) { + jsonData = context.mapViewData.getGeoJson() + } else { + jsonData = context.dataGeoJson + } const jsonStr = JSON.stringify(jsonData) resolve(jsonStr) } else if (context.downloadFormat === 'kml') { @@ -156,12 +214,30 @@ export default { // Use the third party utility to convert geojson to kml const toKML = toKml(jsonData, kmlOptions) resolve(toKML) + } else if (context.downloadFormat === 'csv') { + let csvData + if (this.editProp === 'jobs') { + csvData = Job.toCsv(this.data) + } else if (this.editProp === 'vehicles') { + csvData = Vehicle.toCsv(this.data) + } + resolve(csvData) } } catch (error) { reject(error) } }) }, + + copyToClipboard () { + const data = this.dataJson + + navigator.clipboard.writeText(JSON.stringify(data)).then(() => { + this.showSuccess(this.content.copied, {timeout: 3000}) + }, () => { + this.showError(this.$t('download.copiedToClipboardFailed'), {timeout: 3000}) + },) + }, /** * Get the response data routes and make sure that the geometry format is geojson * @returns {Array} of route objects diff --git a/src/fragments/forms/map-form/components/download/i18n/download.i18n.en-us.js b/src/fragments/forms/map-form/components/download/i18n/download.i18n.en-us.js index 0760d6eed..774c306c6 100755 --- a/src/fragments/forms/map-form/components/download/i18n/download.i18n.en-us.js +++ b/src/fragments/forms/map-form/components/download/i18n/download.i18n.en-us.js @@ -6,9 +6,16 @@ export default { downloadFileName: 'Download file name', downloadFormat: 'Download format', downloadRoute: 'Download route', + downloadJson: 'Download JSON', + downloadAsCsv: 'Download as CSV', + copyToClipboard: 'Copy JSON to clipboard', + jobsCopiedToClipboard: 'Jobs copied to clipboard', + vehiclesCopiedToClipboard: 'Vehicles copied to clipboard', + skillsCopiedToClipboard: 'Skills copied to clipboard', preparingDownload: 'Preparing download ...', fileReady: 'File ready', errorPreparingFile: 'Error preparing file', + copiedToClipboardFailed: 'Copy to clipboard failed', fileTooBigToBeDownloaded: 'File too big to be downloaded', standard: 'Standard' } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index a99f7eb89..5482df65a 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -3,6 +3,7 @@

+ add diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index 973150b75..b4e1c0e63 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -1,4 +1,5 @@ import RouteImporter from '@/fragments/forms/route-importer/RouteImporter.vue' +import Download from '@/fragments/forms/map-form/components/download/Download.vue' import MapFormBtn from '@/fragments/forms/map-form-btn/MapFormBtn.vue' import OrsFilterUtil, {vehicleIcon} from '@/support/map-data-services/ors-filter-util' import constants from '@/resources/constants' diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue index 4699c8e9e..29460412c 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue @@ -3,6 +3,7 @@

+ cloud_upload diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js index 9b963ed2c..8e12ca5c3 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js @@ -1,5 +1,6 @@ import {EventBus} from '@/common/event-bus' import Skill from '@/models/skill' +import Download from '@/fragments/forms/map-form/components/download/Download' export default { data: () => ({ @@ -15,7 +16,8 @@ export default { }, }, components: { - EventBus + EventBus, + Download }, computed: { editSkillsJSON () { From 52ee340691f55c5e58d20e4f93cbf9d75a472658 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 25 Apr 2024 17:28:34 +0200 Subject: [PATCH 019/109] feat: import jobs/vehicles/skills paste JSON from clipboard or upload JSON, GeoJSON or CSV --- .../components/optimization/Optimization.vue | 2 + .../components/edit-dialog/EditDialog.vue | 5 + .../components/edit-dialog/edit-dialog.js | 16 ++ .../components/edit-skills/EditSkills.vue | 4 +- .../components/edit-skills/edit-skills.js | 14 +- .../OptimizationImport.vue | 25 +++ .../i18n/optimization-import.i18n.de-de.js | 10 + .../i18n/optimization-import.i18n.en-us.js | 10 + .../optimization-import.css | 8 + .../optimization-import.js | 195 ++++++++++++++++++ .../components/optimization/optimization.js | 15 ++ 11 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/OptimizationImport.vue create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.css create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js diff --git a/src/fragments/forms/map-form/components/optimization/Optimization.vue b/src/fragments/forms/map-form/components/optimization/Optimization.vue index ea6bb5c6a..e9a57db52 100644 --- a/src/fragments/forms/map-form/components/optimization/Optimization.vue +++ b/src/fragments/forms/map-form/components/optimization/Optimization.vue @@ -74,6 +74,8 @@ @jobsChanged="jobsChanged" @vehiclesChanged="vehiclesChanged" @skillsChanged="skillsChanged" @close="showEditDialog=false"> + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 5482df65a..6c36047e8 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -4,6 +4,9 @@

+ + cloud_upload + add @@ -115,6 +118,8 @@ + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index b4e1c0e63..b2ddadcde 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -6,6 +6,7 @@ import constants from '@/resources/constants' import PlaceAutocomplete from '@/fragments/forms/place-input/PlaceAutocomplete.vue' import ProfileSelectorOption from '@/fragments/forms/profile-selector/components/profile-selector-option/ProfileSelectorOption' import EditSkills from '@/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue' +import OptimizationImport from '@/fragments/forms/map-form/components/optimization/components/optimization-import/OptimizationImport.vue' import {EventBus} from '@/common/event-bus' import Job from '@/models/job' import Vehicle from '@/models/vehicle' @@ -28,6 +29,7 @@ export default { debounceTimeoutId: null, showSkillManagement: false, isSkillsOpen: false, + isImportOpen: false, onlyStartPoint: false, newEndPoint: false, activeProfileSlug: null, @@ -58,6 +60,7 @@ export default { PlaceAutocomplete, ProfileSelectorOption, EditSkills, + OptimizationImport, EventBus }, computed: { @@ -68,6 +71,7 @@ export default { class: Job, maxLength: 50, maxWarning: this.$t('optimization.jobMaxWarning'), + import: this.$t('optimization.importJobFile'), add: this.$t('optimization.addJob'), duplicate: this.$t('optimization.duplicateJob'), remove: this.$t('optimization.removeJob'), @@ -84,6 +88,7 @@ export default { class: Vehicle, maxLength: 3, maxWarning: this.$t('optimization.vehicleMaxWarning'), + import: this.$t('optimization.importVehicleFile'), add: this.$t('optimization.addVehicle'), duplicate: this.$t('optimization.duplicateVehicle'), remove: this.$t('optimization.removeVehicle'), @@ -148,6 +153,17 @@ export default { this.$emit('contentUploaded', data) }, + // save jobs from JSON + saveImport(data) { + if (this.jobsBox) { + this.editData = data.jobs + } else if (this.vehiclesBox) { + this.editData = data.vehicles + } + this.saveItems() + this.isImportOpen = false + }, + // check if one of the jobs does not have a location hasEmptyLocation () { for (let item of this.editData) { diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue index 29460412c..48b565875 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue @@ -28,7 +28,7 @@ delete - + @@ -45,6 +45,8 @@ + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js index 8e12ca5c3..454252dbe 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js @@ -1,3 +1,4 @@ +import OptimizationImport from '@/fragments/forms/map-form/components/optimization/components/optimization-import/OptimizationImport.vue' import {EventBus} from '@/common/event-bus' import Skill from '@/models/skill' import Download from '@/fragments/forms/map-form/components/download/Download' @@ -7,7 +8,10 @@ export default { isSkillsOpen: true, editId: 0, editSkills: [], - selectedSkills: [] + selectedSkills: [], + pastedSkills: [], + JsonPlaceholder: '[{"name":"example skill","id":1}]', + isImportOpen: false }), props: { skills: { @@ -16,6 +20,7 @@ export default { }, }, components: { + OptimizationImport, EventBus, Download }, @@ -47,6 +52,13 @@ export default { this.$emit('close') }, + // save skills from JSON + saveSkillImport(data) { + this.editSkills = data.skills + this.saveSkills() + this.isImportOpen = false + }, + // save changed skills and emit event to update in component saveSkills () { this.$emit('skillsChanged', this.editSkills) diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/OptimizationImport.vue b/src/fragments/forms/map-form/components/optimization/components/optimization-import/OptimizationImport.vue new file mode 100644 index 000000000..bab5b9c91 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/OptimizationImport.vue @@ -0,0 +1,25 @@ + + + + diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js new file mode 100644 index 000000000..864920097 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js @@ -0,0 +1,10 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveJobImport: 'Save new jobs', + saveVehicleImport: 'Save new vehicles', + notValidJob: 'File does not contain valid jobs', + notValidVehicle: 'File does not contain valid vehicles', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js new file mode 100644 index 000000000..864920097 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js @@ -0,0 +1,10 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveJobImport: 'Save new jobs', + saveVehicleImport: 'Save new vehicles', + notValidJob: 'File does not contain valid jobs', + notValidVehicle: 'File does not contain valid vehicles', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.css b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.css new file mode 100644 index 000000000..ebea18155 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.css @@ -0,0 +1,8 @@ +.edit-btn { + padding: 0 20px; + min-width: 0; + float: right; + margin: 0; + height: 24px; + background: white; +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js new file mode 100644 index 000000000..7e304ecbe --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js @@ -0,0 +1,195 @@ +import vueDropzone from 'vue2-dropzone' +import constants from '@/resources/constants' +import MapFormBtn from '@/fragments/forms/map-form-btn/MapFormBtn.vue' +import 'vue2-dropzone/dist/vue2Dropzone.min.css' +import Job from '@/models/job' +import Vehicle from '@/models/vehicle' +import Skill from '@/models/skill' + +export default { + data: () => ({ + isImportOpen: true, + acceptedFiles: '.json,.csv,.geojson', + pastedData: [], + }), + props: { + expectedData: { + Type: String, + Required: true + }, + }, + components: { + vueDropzone, + MapFormBtn + }, + computed: { + content () { + if (this.expectedData === 'jobs') { + return { + header: this.$t('optimization.importJobFile'), + saveImport: this.$t('optimizationImport.saveJobImport'), + jsonPlaceholder: '[{"id":1,"location":[8.68525,49.420822],"service":300,"delivery":[1],"skills":[1]}]', + } + } else if (this.expectedData === 'vehicles') { + return { + header: this.$t('optimization.importVehicleFile'), + saveImport: this.$t('optimizationImport.saveVehicleImport'), + jsonPlaceholder: '[{"id":1,"description":"","profile":"driving-car","start":[8.675863,49.418477],"end":[8.675863,49.418477],"capacity":[4],"skills":[1]}]', + } + } else if (this.expectedData === 'skills') { + return { + header: this.$t('optimization.importSkillFile'), + saveImport: this.$t('optimizationImport.saveSkillImport'), + jsonPlaceholder: '["{"name":"length over 1.5m","id":1}"]', + } + } + }, + dropzoneOptions () { + return { + maxFilesize: 0.5, + url: 'https://not-used-url.tld', // declaration required by the component, but never used + uploadMultiple: false, + maxFiles: 1, + clickable: true, + acceptedFiles: this.acceptedFiles, + dictDefaultMessage: this.$t('routeImporter.dictDefaultMessage'), + dictFallbackMessage: this.$t('routeImporter.dictFallbackMessage'), + dictFileTooBig: this.$t('routeImporter.dictFileTooBig'), + dictInvalidFileType: this.$t('routeImporter.dictInvalidFileType'), + dictCancelUpload: this.$t('routeImporter.dictCancelUpload'), + dictUploadCanceled: this.$t('routeImporter.dictUploadCanceled'), + dictRemoveFile: this.$t('routeImporter.dictRemoveFile') + } + } + }, + created() { + this.acceptedFiles = this.$root.appHooks.run('importerAcceptedFilesDefined', this.acceptedFiles) + }, + methods: { + /** + * Handle the file added event + * @param {*} file + */ + fileAdded (file) { + const context = this + const reader = new FileReader() + reader.addEventListener('loadend', function (event) { + const content = event.target.result + if (!content || content === 'null') { + context.showError(context.$t('routeImporter.failedToLoadFile'), {timeout: 0}) + } else { + let parts = file.name.split('.') + let extension = parts.at(-1) + let type = file.type || extension + context.catchAndParseFile(content, type, new Date().getTime()) + } + }) + this.$refs.importRouteDropzone.removeAllFiles() + reader.readAsText(file) + }, + /** + * Catch file contents and type, parse and call load + * @param {*} fileContent + * @param {*} type + */ + catchAndParseFile (fileContent, type) { + let fileType = null + let newJobs = [] + let newVehicles = [] + let newSkills = [] + if (type.indexOf('csv') > -1) { + fileType = 'csv' + if (this.expectedData === 'jobs') { + newJobs = Job.fromCsv(fileContent) + } else if (this.expectedData === 'vehicles') { + newVehicles = Vehicle.fromCsv(fileContent) + } + } else if (type.indexOf('json') > -1 || type.indexOf('geojson') > -1) { + const parsedJson = JSON.parse(fileContent) + if (parsedJson && parsedJson.features) { + fileType = 'geojson' + } else { + fileType = 'json' + } + if (this.expectedData === 'jobs') { + for (const j of parsedJson) { + try { + newJobs.push(Job.fromObject(j)) + } catch { + this.showError(this.$t('optimizationImport.notValidJob')) + } + } + } else if (this.expectedData === 'vehicles') { + for (const v of parsedJson) { + try { + newVehicles.push(Vehicle.fromObject(v)) + } catch { + this.showError(this.$t('optimizationImport.notValidVehicle')) + } + } + } else if (this.expectedData === 'skills') { + for (const s of parsedJson) { + try { + newSkills.push(Skill.fromObject(s)) + } catch { + this.showError(this.$t('optimizationImport.notValidSkill')) + } + } + } + } + if (fileType) { + this.$emit('saveOptimizationImport', {jobs: newJobs, vehicles: newVehicles, skills: newSkills}) + this.closeImporter() + } else { + this.showError(this.$t('routeImporter.failedToLoadFile'), {timeout: 0}) + this.$emit('failedToImportFile') + } + }, + + // save jobs from pasted JSON and return error if not a valid JSON + savePastedJson() { + try { + const newJobs = [] + const newVehicles = [] + const newSkills = [] + if (this.expectedData === 'jobs') { + for (const j of JSON.parse(this.pastedData)) { + newJobs.push(Job.fromObject(j)) + } + } else if (this.expectedData === 'vehicles') { + for (const v of JSON.parse(this.pastedData)) { + newVehicles.push(Vehicle.fromObject(v)) + } + } else if (this.expectedData === 'skills') { + for (const s of JSON.parse(this.pastedData)) { + newSkills.push(Skill.fromObject(s)) + } + } + this.$emit('saveOptimizationImport', {jobs: newJobs, vehicles: newVehicles, skills: newSkills}) + this.closeImporter() + } catch (err) { + this.showError(this.$t('optimizationImport.notAJson'), {timeout: 3000}) + } + }, + + /** + * Send new map data via EventBus to ors-map + * @emits mapViewDataChanged via EventBus passing fileType and fileContent + * @param {*} fileType + * @param {*} fileContent + * @param {*} timestamp + */ + sendDataToMap (fileType, fileContent, timestamp) { + const data = { + content: fileContent, + options: { origin: constants.dataOrigins.fileImporter, contentType: fileType, timestamp: timestamp } + } + this.$emit('contentUploaded', data) + }, + + // close the import dialog + closeImporter () { + this.$emit('close') + } + }, +} diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 42f824c52..767f04f11 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -15,6 +15,7 @@ import Skill from '@/models/skill' // Local components import OptimizationDetails from './components/optimization-details/OptimizationDetails' +import OptimizationImport from './components/optimization-import/OptimizationImport.vue' import JobList from './components/job-list/JobList.vue' import VehicleList from './components/vehicle-list/VehicleList.vue' import EditDialog from './components/edit-dialog/EditDialog.vue' @@ -32,12 +33,15 @@ export default { showEditDialog: false, editProp: '', editData: [], + isImportOpen: false, + expectedImport: '', showSkillManagement: false }), components: { FieldsContainer, FormActions, OptimizationDetails, + OptimizationImport, JobList, VehicleList, EditDialog, @@ -269,6 +273,17 @@ export default { this.setPickPlaceSource(this.vehicles) }, + // save vehicles from pasted JSON and return error if not a valid JSON + saveImport(data) { + if (data.jobs.length) { + this.jobsChanged(data.jobs) + } else if (data.vehicles.length){ + this.vehiclesChanged(data.vehicles) + } + this.isImportOpen = false + this.expectedImport = '' + }, + // open editSkills dialog manageSkills(skillId) { this.showSkillManagement = true From 03a119a44410fbf0966e8f407d636a6284850da1 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 25 Apr 2024 17:36:04 +0200 Subject: [PATCH 020/109] docs: add todo --- src/support/app-hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/support/app-hooks.js b/src/support/app-hooks.js index 0cbbcf0f6..38e8512e6 100644 --- a/src/support/app-hooks.js +++ b/src/support/app-hooks.js @@ -57,7 +57,7 @@ class AppHooks { // Order the hooks so that we run then in the correct order let orderedHooks = lodash.sortBy(targetHooks, [function(h) { return h.priority }]) - // Loop and run each hook + // Loop and run each hook TODO: for-loop that doesn't loop for(let key in orderedHooks) { let hook = orderedHooks[key] return hook.functionToRun(arg) From defe6250fcbe0651ef01110c9e0901eea1e5071d Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 25 Apr 2024 18:16:11 +0200 Subject: [PATCH 021/109] fix: don't show empty chips --- .../optimization/components/edit-dialog/EditDialog.vue | 2 +- .../optimization/components/job-list/JobList.vue | 4 ++-- .../optimization-details/OptimizationDetails.vue | 4 ++-- .../optimization/components/vehicle-list/VehicleList.vue | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 6c36047e8..a584c570b 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -36,7 +36,7 @@
- {{ $t(`optimization.${k}`) }}: {{v[0]}} + {{ $t(`optimization.${k}`) }}: {{v[0]}} {{ $t(`optimization.${k}`) }}: {{skillNames(d)}}
diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue b/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue index 6797aa40a..08d3a1379 100644 --- a/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue @@ -6,8 +6,8 @@ diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/OptimizationDetails.vue b/src/fragments/forms/map-form/components/optimization/components/optimization-details/OptimizationDetails.vue index 2edd23438..c8af1530a 100644 --- a/src/fragments/forms/map-form/components/optimization/components/optimization-details/OptimizationDetails.vue +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/OptimizationDetails.vue @@ -21,8 +21,8 @@
diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue index 235e21b7b..4e9b82373 100644 --- a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue @@ -3,10 +3,10 @@ {{vehicleIcon(v.profile)}}Vehicle {{v.id}} ({{v.profile}}) -
map - {{ $t('optimization.addJobFromMap') }} + {{ $t('optimization.addFromMap') + $t('optimization.job') }}
@@ -37,16 +37,16 @@ - {{ $t('optimization.manageVehicles') }} + {{ $t('optimization.manage') + $t('optimization.vehicles') }}
map - {{ $t('optimization.addVehicleFromMap') }} + {{ $t('optimization.addFromMap') + $t('optimization.vehicle') }}
@@ -56,9 +56,9 @@
- settings + settings -

{{$t('optimization.manageSkills')}}

+

{{$t('optimization.manage') + $t('optimization.skills')}}

diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 90e1a9458..42d1df4a2 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -23,7 +23,7 @@ check - + edit diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index 246c45430..6a7726bec 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -70,13 +70,14 @@ export default { item: 'Job', class: Job, maxLength: 50, - maxWarning: this.$t('optimization.jobMaxWarning'), - import: this.$t('optimization.importJobFile'), - add: this.$t('optimization.addJob'), - duplicate: this.$t('optimization.duplicateJob'), - remove: this.$t('optimization.removeJob'), - header: this.$t('optimization.manageJobs'), - fromMap: this.$t('optimization.addJobFromMap'), + maxWarning: this.$t('optimization.maxWarning') + '50' + this.$t('optimization.jobs'), + import: this.$t('optimization.import') + this.$t('optimization.jobs'), + edit: this.$t('optimization.edit') + this.$t('optimization.job'), + add: this.$t('optimization.add') + this.$t('optimization.job'), + duplicate: this.$t('optimization.duplicate') + this.$t('optimization.job'), + remove: this.$t('optimization.remove') + this.$t('optimization.job'), + header: this.$t('optimization.manage') + this.$t('optimization.jobs'), + fromMap: this.$t('optimization.addFromMap') + this.$t('optimization.job'), expected: 'jobs', changedEvent: 'jobsChanged', skills: 'Skills needed for this Job', @@ -87,13 +88,14 @@ export default { item: 'Vehicle', class: Vehicle, maxLength: 3, - maxWarning: this.$t('optimization.vehicleMaxWarning'), - import: this.$t('optimization.importVehicleFile'), - add: this.$t('optimization.addVehicle'), - duplicate: this.$t('optimization.duplicateVehicle'), - remove: this.$t('optimization.removeVehicle'), - header: this.$t('optimization.manageVehicles'), - fromMap: this.$t('optimization.addVehicleFromMap'), + maxWarning: this.$t('optimization.maxWarning') + '3' + this.$t('optimization.vehicles'), + import: this.$t('optimization.import') + this.$t('optimization.vehicles'), + edit: this.$t('optimization.edit') + this.$t('optimization.vehicle'), + add: this.$t('optimization.add') + this.$t('optimization.vehicle'), + duplicate: this.$t('optimization.duplicate') + this.$t('optimization.vehicle'), + remove: this.$t('optimization.remove') + this.$t('optimization.vehicle'), + header: this.$t('optimization.manage') + this.$t('optimization.vehicles'), + fromMap: this.$t('optimization.addFromMap') + this.$t('optimization.vehicle'), expected: 'vehicles', changedEvent: 'vehiclesChanged', skills: 'Skills this Vehicle has', diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js index d7841cbd2..5b472718f 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js @@ -1,6 +1,5 @@ export default { editDialog: { - makeEdits: 'Edit item', keepEdits: 'Keep edits', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js index d7841cbd2..5b472718f 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js @@ -1,6 +1,5 @@ export default { editDialog: { - makeEdits: 'Edit item', keepEdits: 'Keep edits', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue index 48b565875..55ff480e4 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue @@ -7,24 +7,24 @@ cloud_upload - + add - {{ $t('optimization.manageSkills') }} + {{ $t('optimization.manage') + $t('optimization.skills') }}

map - {{$t('optimization.addSkill')}} + {{ $t('optimization.add') + $t('optimization.skill') }}
Skill: {{ skill.name }}
- + edit - + delete
diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js index 6a926dbc5..61a2f643f 100755 --- a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js @@ -1,48 +1,29 @@ + export default { optimization: { optimization: 'optimization', optimizationResultReady: 'Optimization result ready', notPossible: 'Optimization was not possible', - optimizeJobs: 'optimizeJobs', - couldNotResolveTheJobLocation: 'Could not resolve the job location', - couldNotResolveTheVehicleLocation: 'Could not resolve the vehicle location', - vehicleMaxWarning: 'A maximum of 3 vehicles can be considered with the live API', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', genericErrorMsg: 'It was not possible to optimize Jobs', service: 'Service time', capacity: 'Capacity', delivery: 'Deliveries', pickup: 'Pickups', - keepEdits: 'Keep edits', - skills: 'Skills', - manageSkills: 'Manage Skills', - importSkillFile: 'Import skills', - exportSkillFile: 'Export skills', - saveSkills: 'Save skills', - addSkill: 'Add skill', - removeSkill: 'Remove skill', - editSkill: 'Edit skill', time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', jobs: 'Jobs', - manageJobs: 'Manage jobs', - noJobsToManage: 'No jobs available. Import jobs or use button to add from map', - importJobFile: 'Import jobs', - exportJobFile: 'Export jobs', - saveJobs: 'Save jobs', - addJob: 'Add job', - addJobFromMap: 'Add job from map', - removeJob: 'Remove job', - editJob: 'Edit job', - duplicateJob: 'Duplicate job', + job: 'Job', vehicles: 'Vehicles', - manageVehicles: 'Manage vehicles', - noVehiclesToManage: 'No vehicles available. Import vehicles or use button to add from map', - importVehicleFile: 'Import vehicles', - exportVehicleFile: 'Export vehicles', - saveVehicles: 'Save vehicles', - addVehicle: 'Add vehicle', - addVehicleFromMap: 'Add vehicle from map', - removeVehicle: 'Remove vehicle', - editVehicle: 'Edit vehicle', - duplicateVehicle: 'Duplicate vehicle', + vehicle: 'Vehicle', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js index 5f01722eb..6b33822a0 100755 --- a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.en-us.js @@ -4,46 +4,28 @@ export default { optimization: 'optimization', optimizationResultReady: 'Optimization result ready', notPossible: 'Optimization was not possible', - optimizeJobs: 'optimizeJobs', - couldNotResolveTheJobLocation: 'Could not resolve the job location', - couldNotResolveTheVehicleLocation: 'Could not resolve the vehicle location', - jobMaxWarning: 'A maximum of 50 jobs can be considered with the live API', - vehicleMaxWarning: 'A maximum of 3 vehicles can be considered with the live API', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', genericErrorMsg: 'It was not possible to optimize Jobs', service: 'Service time', capacity: 'Capacity', delivery: 'Deliveries', pickup: 'Pickups', - skills: 'Skills', - manageSkills: 'Manage Skills', - importSkillFile: 'Import skills', - exportSkillFile: 'Export skills', - saveSkills: 'Save skills', - addSkill: 'Add skill', - removeSkill: 'Remove skill', - editSkill: 'Edit skill', time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', jobs: 'Jobs', - manageJobs: 'Manage jobs', - noJobsToManage: 'No jobs available. Import jobs or use button to add from map', - importJobFile: 'Import jobs', - exportJobFile: 'Export jobs', - saveJobs: 'Save jobs', - addJob: 'Add job', - addJobFromMap: 'Add job from map', - removeJob: 'Remove job', - editJob: 'Edit job', - duplicateJob: 'Duplicate job', + job: 'Job', vehicles: 'Vehicles', - manageVehicles: 'Manage vehicles', - noVehiclesToManage: 'No vehicles available. Import vehicles or use button to add from map', - importVehicleFile: 'Import vehicles', - exportVehicleFile: 'Export vehicles', - saveVehicles: 'Save vehicles', - addVehicle: 'Add vehicle', - addVehicleFromMap: 'Add vehicle from map', - removeVehicle: 'Remove vehicle', - editVehicle: 'Edit vehicle', - duplicateVehicle: 'Duplicate vehicle', + vehicle: 'Vehicle', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.de-de.js index 96153eb6a..e64d2a9b2 100644 --- a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.de-de.js +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.de-de.js @@ -3,6 +3,5 @@ export default { amount: 'Amount', service: 'Expected service time', skills: 'Skills needed', - copied: 'Job copied to clipboard', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.en-us.js index 96153eb6a..e64d2a9b2 100644 --- a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.en-us.js +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.en-us.js @@ -3,6 +3,5 @@ export default { amount: 'Amount', service: 'Expected service time', skills: 'Skills needed', - copied: 'Job copied to clipboard', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js index 864920097..40d8d573c 100644 --- a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.de-de.js @@ -2,9 +2,7 @@ export default { optimizationImport: { acceptedImportTypes: 'Accepted import types', notAJson: 'Input is not a valid JSON', - saveJobImport: 'Save new jobs', - saveVehicleImport: 'Save new vehicles', - notValidJob: 'File does not contain valid jobs', - notValidVehicle: 'File does not contain valid vehicles', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js index 864920097..40d8d573c 100644 --- a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.en-us.js @@ -2,9 +2,7 @@ export default { optimizationImport: { acceptedImportTypes: 'Accepted import types', notAJson: 'Input is not a valid JSON', - saveJobImport: 'Save new jobs', - saveVehicleImport: 'Save new vehicles', - notValidJob: 'File does not contain valid jobs', - notValidVehicle: 'File does not contain valid vehicles', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', } } diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js index 7e304ecbe..0a2093597 100644 --- a/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js @@ -26,19 +26,19 @@ export default { content () { if (this.expectedData === 'jobs') { return { - header: this.$t('optimization.importJobFile'), - saveImport: this.$t('optimizationImport.saveJobImport'), + header: this.$t('optimization.import') + this.$t('optimization.jobs'), + saveImport: this.$t('optimizationImport.saveImport') + this.$t('optimization.jobs'), jsonPlaceholder: '[{"id":1,"location":[8.68525,49.420822],"service":300,"delivery":[1],"skills":[1]}]', } } else if (this.expectedData === 'vehicles') { return { - header: this.$t('optimization.importVehicleFile'), - saveImport: this.$t('optimizationImport.saveVehicleImport'), + header: this.$t('optimization.import') + this.$t('optimization.vehicles'), + saveImport: this.$t('optimizationImport.saveImport') + this.$t('optimization.vehicles'), jsonPlaceholder: '[{"id":1,"description":"","profile":"driving-car","start":[8.675863,49.418477],"end":[8.675863,49.418477],"capacity":[4],"skills":[1]}]', } } else if (this.expectedData === 'skills') { return { - header: this.$t('optimization.importSkillFile'), + header: this.$t('optimization.import') + this.$t('optimization.skills'), saveImport: this.$t('optimizationImport.saveSkillImport'), jsonPlaceholder: '["{"name":"length over 1.5m","id":1}"]', } @@ -116,7 +116,7 @@ export default { try { newJobs.push(Job.fromObject(j)) } catch { - this.showError(this.$t('optimizationImport.notValidJob')) + this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.jobs'),) } } } else if (this.expectedData === 'vehicles') { @@ -124,7 +124,7 @@ export default { try { newVehicles.push(Vehicle.fromObject(v)) } catch { - this.showError(this.$t('optimizationImport.notValidVehicle')) + this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.vehicles'),) } } } else if (this.expectedData === 'skills') { diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.de-de.js index 77491c143..6fcaa0ee0 100644 --- a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.de-de.js +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.de-de.js @@ -3,8 +3,5 @@ export default { amount: 'Amount', service: 'Expected service time', skills: 'Skills needed', - copied: 'Vehicle copied to clipboard', - copiedToClipboard: 'Vehicles copied to clipboard', - copiedToClipboardFailed: 'Copy to clipboard failed' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.en-us.js index 17d8fc5bb..6fcaa0ee0 100644 --- a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.en-us.js +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.en-us.js @@ -3,8 +3,5 @@ export default { amount: 'Amount', service: 'Expected service time', skills: 'Skills needed', - copied: 'Vehicle copied to clipboard', - copiedToClipboard: 'Vehicles copied to clipboard', - copiedToClipboardFailed: 'Copy to clipboard failed', } } diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index f213798bb..bf79502d8 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -229,7 +229,7 @@ export default { context.updateAppRoute() }).catch((err) => { console.log(err) - context.showError(this.$t('optimization.couldNotResolveTheJobLocation'), { timeout: 0 }) + context.showError(this.$t('optimization.couldNotResolveTheLocation') + this.$t('optimization.jobs'), { timeout: 0 }) }) }, // open editJobs dialog @@ -252,14 +252,14 @@ export default { const context = this vehicle.resolve().then(() => { if (this.vehicles.length > 3) { - this.showError(this.$t('optimization.vehicleMaxWarning'), {timeout: 3000}) + this.showError(this.$t('optimization.maxWarning') + '3' + this.$t('optimization.vehicles'), {timeout: 3000}) } context.vehicles.push(vehicle) context.manageVehicles(vehicle.id) context.updateAppRoute() }).catch((err) => { console.log(err) - context.showError(this.$t('optimization.couldNotResolveTheVehicleLocation'), { timeout: 0 }) + context.showError(this.$t('optimization.couldNotResolveTheLocation') + this.$t('optimization.vehicles'), { timeout: 0 }) }) }, // open editVehicles dialog @@ -377,7 +377,7 @@ export default { return new Promise((resolve) => { if (context.jobs.length) { if (context.vehicles.length) { - context.showInfo(context.$t('optimization.optimizeJobs'), { timeout: 0 }) + context.showInfo(context.$t('optimization.optimize') + this.$t('optimization.jobs'), { timeout: 0 }) EventBus.$emit('showLoading', true) // Calculate the optimized routes From f4fb158586088cdf0e398d28152429e0b608a99d Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 25 Apr 2024 19:55:51 +0200 Subject: [PATCH 026/109] feat: add (untranslated) translation files --- .../components/optimization/Optimization.vue | 2 +- .../components/edit-dialog/EditDialog.vue | 2 +- .../i18n/edit-dialog.i18n.cs-cz.js | 5 +++ .../i18n/edit-dialog.i18n.es-es.js | 5 +++ .../i18n/edit-dialog.i18n.fr-fr.js | 5 +++ .../i18n/edit-dialog.i18n.hu-hu.js | 5 +++ .../i18n/edit-dialog.i18n.it-it.js | 5 +++ .../i18n/edit-dialog.i18n.pt-br.js | 5 +++ .../i18n/edit-dialog.i18n.ro-ro.js | 5 +++ .../components/edit-skills/EditSkills.vue | 4 +-- .../i18n/edit-skills.i18n.cs-cz.js | 6 ++++ .../i18n/edit-skills.i18n.de-de.js | 6 ++++ .../i18n/edit-skills.i18n.es-es.js | 6 ++++ .../i18n/edit-skills.i18n.fr-fr.js | 6 ++++ .../i18n/edit-skills.i18n.hu-hu.js | 6 ++++ .../i18n/edit-skills.i18n.it-it.js | 6 ++++ .../i18n/edit-skills.i18n.pt-br.js | 6 ++++ .../i18n/edit-skills.i18n.ro-ro.js | 6 ++++ .../edit-skills/i18n/skill.i18n.de-de.js | 6 ---- .../edit-skills/i18n/skill.i18n.en-us.js | 6 ---- .../i18n/optimization.i18n.cs-cz.js | 31 +++++++++++++++++++ .../i18n/optimization.i18n.de-de.js | 2 ++ .../i18n/optimization.i18n.es-es.js | 31 +++++++++++++++++++ .../i18n/optimization.i18n.fr-fr.js | 31 +++++++++++++++++++ .../i18n/optimization.i18n.hu-hu.js | 31 +++++++++++++++++++ .../i18n/optimization.i18n.it-it.js | 31 +++++++++++++++++++ .../i18n/optimization.i18n.pt-br.js | 31 +++++++++++++++++++ .../i18n/optimization.i18n.ro-ro.js | 31 +++++++++++++++++++ .../job-list/i18n/job.i18n.cs-cz,js | 7 +++++ .../job-list/i18n/job.i18n.es-es.js | 7 +++++ .../job-list/i18n/job.i18n.fr-fr.js | 7 +++++ .../job-list/i18n/job.i18n.hu-hu.js | 7 +++++ .../job-list/i18n/job.i18n.it-it.js | 7 +++++ .../job-list/i18n/job.i18n.pt-br.js | 7 +++++ .../job-list/i18n/job.i18n.ro-ro.js | 7 +++++ .../i18n/optimization-details.i18n.cs-cz.js | 15 +++++++++ .../i18n/optimization-details.i18n.es-es.js | 15 +++++++++ .../i18n/optimization-details.i18n.fr-fr.js | 15 +++++++++ .../i18n/optimization-details.i18n.hu-hu.js | 15 +++++++++ .../i18n/optimization-details.i18n.it-it.js | 15 +++++++++ .../i18n/optimization-details.i18n.pt-br.js | 15 +++++++++ .../i18n/optimization-details.i18n.ro-ro.js | 15 +++++++++ .../i18n/optimization-import.i18n.cs-cz.js | 8 +++++ .../i18n/optimization-import.i18n.es-es.js | 8 +++++ .../i18n/optimization-import.i18n.fr-fr.js | 8 +++++ .../i18n/optimization-import.i18n.hu-hu.js | 8 +++++ .../i18n/optimization-import.i18n.it-it.js | 8 +++++ .../i18n/optimization-import.i18n.pt-br.js | 8 +++++ .../i18n/optimization-import.i18n.ro-ro.js | 8 +++++ .../vehicle-list/i18n/vehicle.i18n.cs-cz.js | 7 +++++ .../vehicle-list/i18n/vehicle.i18n.es-es.js | 7 +++++ .../vehicle-list/i18n/vehicle.i18n.fr-fr.js | 7 +++++ .../vehicle-list/i18n/vehicle.i18n.hu-hu.js | 7 +++++ .../vehicle-list/i18n/vehicle.i18n.it-it.js | 7 +++++ .../vehicle-list/i18n/vehicle.i18n.pt-br.js | 7 +++++ .../vehicle-list/i18n/vehicle.i18n.ro-ro.js | 7 +++++ 56 files changed, 565 insertions(+), 16 deletions(-) create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.cs-cz.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.de-de.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.es-es.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.fr-fr.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.hu-hu.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.it-it.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.pt-br.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.ro-ro.js delete mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js delete mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.cs-cz.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.es-es.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.fr-fr.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.hu-hu.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.it-it.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.pt-br.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.ro-ro.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.cs-cz,js create mode 100644 src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.es-es.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.fr-fr.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.hu-hu.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.it-it.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.pt-br.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.ro-ro.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.cs-cz.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.es-es.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.fr-fr.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.hu-hu.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.it-it.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.pt-br.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.ro-ro.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.cs-cz.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.es-es.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.fr-fr.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.hu-hu.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.it-it.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.pt-br.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.ro-ro.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.cs-cz.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.es-es.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.fr-fr.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.hu-hu.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.it-it.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.pt-br.js create mode 100644 src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.ro-ro.js diff --git a/src/fragments/forms/map-form/components/optimization/Optimization.vue b/src/fragments/forms/map-form/components/optimization/Optimization.vue index 1d0e96712..5be6914fc 100644 --- a/src/fragments/forms/map-form/components/optimization/Optimization.vue +++ b/src/fragments/forms/map-form/components/optimization/Optimization.vue @@ -73,7 +73,7 @@ - + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 42d1df4a2..7e3e5561b 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -91,7 +91,7 @@ diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js new file mode 100644 index 000000000..5b472718f --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js @@ -0,0 +1,5 @@ +export default { + editDialog: { + keepEdits: 'Keep edits', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js new file mode 100644 index 000000000..5b472718f --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js @@ -0,0 +1,5 @@ +export default { + editDialog: { + keepEdits: 'Keep edits', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js new file mode 100644 index 000000000..5b472718f --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js @@ -0,0 +1,5 @@ +export default { + editDialog: { + keepEdits: 'Keep edits', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js new file mode 100644 index 000000000..5b472718f --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js @@ -0,0 +1,5 @@ +export default { + editDialog: { + keepEdits: 'Keep edits', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js new file mode 100644 index 000000000..5b472718f --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js @@ -0,0 +1,5 @@ +export default { + editDialog: { + keepEdits: 'Keep edits', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js new file mode 100644 index 000000000..5b472718f --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js @@ -0,0 +1,5 @@ +export default { + editDialog: { + keepEdits: 'Keep edits', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js new file mode 100644 index 000000000..5b472718f --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js @@ -0,0 +1,5 @@ +export default { + editDialog: { + keepEdits: 'Keep edits', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue index 55ff480e4..a07753d05 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue @@ -4,7 +4,7 @@

- + cloud_upload @@ -13,7 +13,7 @@ {{ $t('optimization.manage') + $t('optimization.skills') }}

- + map {{ $t('optimization.add') + $t('optimization.skill') }} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.cs-cz.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.cs-cz.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.cs-cz.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.de-de.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.de-de.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.es-es.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.es-es.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.fr-fr.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.fr-fr.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.hu-hu.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.hu-hu.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.it-it.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.it-it.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.pt-br.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.pt-br.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.ro-ro.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.ro-ro.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js deleted file mode 100644 index a154c751b..000000000 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.de-de.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - skill: { - copiedToClipboard: 'Skills copied to clipboard', - copiedToClipboardFailed: 'Copy to clipboard failed' - } -} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js deleted file mode 100644 index a154c751b..000000000 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/skill.i18n.en-us.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - skill: { - copiedToClipboard: 'Skills copied to clipboard', - copiedToClipboardFailed: 'Copy to clipboard failed' - } -} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.cs-cz.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.cs-cz.js new file mode 100644 index 000000000..6b33822a0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.cs-cz.js @@ -0,0 +1,31 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', + jobs: 'Jobs', + job: 'Job', + vehicles: 'Vehicles', + vehicle: 'Vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js index 61a2f643f..6b33822a0 100755 --- a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.de-de.js @@ -15,12 +15,14 @@ export default { optimize: 'Optimize ', import: 'Import ', manage: 'Manage ', + edit: 'Edit ', add: 'Add ', addFromMap: 'From map, add ', remove: 'Remove ', duplicate: 'Duplicate ', nothingToManage: ' not available. Import or use button to add from map', skills: 'Skills', + skill: 'Skill', jobs: 'Jobs', job: 'Job', vehicles: 'Vehicles', diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.es-es.js new file mode 100644 index 000000000..6b33822a0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.es-es.js @@ -0,0 +1,31 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', + jobs: 'Jobs', + job: 'Job', + vehicles: 'Vehicles', + vehicle: 'Vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.fr-fr.js new file mode 100644 index 000000000..6b33822a0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.fr-fr.js @@ -0,0 +1,31 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', + jobs: 'Jobs', + job: 'Job', + vehicles: 'Vehicles', + vehicle: 'Vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.hu-hu.js new file mode 100644 index 000000000..6b33822a0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.hu-hu.js @@ -0,0 +1,31 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', + jobs: 'Jobs', + job: 'Job', + vehicles: 'Vehicles', + vehicle: 'Vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.it-it.js new file mode 100644 index 000000000..6b33822a0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.it-it.js @@ -0,0 +1,31 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', + jobs: 'Jobs', + job: 'Job', + vehicles: 'Vehicles', + vehicle: 'Vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.pt-br.js new file mode 100644 index 000000000..6b33822a0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.pt-br.js @@ -0,0 +1,31 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', + jobs: 'Jobs', + job: 'Job', + vehicles: 'Vehicles', + vehicle: 'Vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.ro-ro.js new file mode 100644 index 000000000..6b33822a0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/i18n/optimization.i18n.ro-ro.js @@ -0,0 +1,31 @@ + +export default { + optimization: { + optimization: 'optimization', + optimizationResultReady: 'Optimization result ready', + notPossible: 'Optimization was not possible', + couldNotResolveTheLocation: 'Could not resolve the location of the ', + maxWarning: 'The live API can consider a maximum of ', + genericErrorMsg: 'It was not possible to optimize Jobs', + service: 'Service time', + capacity: 'Capacity', + delivery: 'Deliveries', + pickup: 'Pickups', + time_window: 'Time window', + optimize: 'Optimize ', + import: 'Import ', + manage: 'Manage ', + edit: 'Edit ', + add: 'Add ', + addFromMap: 'From map, add ', + remove: 'Remove ', + duplicate: 'Duplicate ', + nothingToManage: ' not available. Import or use button to add from map', + skills: 'Skills', + skill: 'Skill', + jobs: 'Jobs', + job: 'Job', + vehicles: 'Vehicles', + vehicle: 'Vehicle', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.cs-cz,js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.cs-cz,js new file mode 100644 index 000000000..e64d2a9b2 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.cs-cz,js @@ -0,0 +1,7 @@ +export default { + job: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.es-es.js new file mode 100644 index 000000000..e64d2a9b2 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.es-es.js @@ -0,0 +1,7 @@ +export default { + job: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.fr-fr.js new file mode 100644 index 000000000..e64d2a9b2 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.fr-fr.js @@ -0,0 +1,7 @@ +export default { + job: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.hu-hu.js new file mode 100644 index 000000000..e64d2a9b2 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.hu-hu.js @@ -0,0 +1,7 @@ +export default { + job: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.it-it.js new file mode 100644 index 000000000..e64d2a9b2 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.it-it.js @@ -0,0 +1,7 @@ +export default { + job: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.pt-br.js new file mode 100644 index 000000000..e64d2a9b2 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.pt-br.js @@ -0,0 +1,7 @@ +export default { + job: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.ro-ro.js new file mode 100644 index 000000000..e64d2a9b2 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/i18n/job.i18n.ro-ro.js @@ -0,0 +1,7 @@ +export default { + job: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.cs-cz.js b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.cs-cz.js new file mode 100644 index 000000000..25a1c87ef --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.cs-cz.js @@ -0,0 +1,15 @@ +export default { + optimizationDetails: { + optimizationDetails: 'Optimized routes', + schedule: 'Schedule', + warningUnassigned: 'Warning: There are unassigned jobs for this optimization!', + 'distance': 'Distance', + 'duration': 'Duration', + 'service': 'Service time', + 'delivery': 'Deliveries', + 'pickup': 'Pickups', + 'waiting_time': 'Waiting time', + 'isUnassigned': 'is unassigned.', + getInstructions: 'Get instructions for this Vehicle' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.es-es.js new file mode 100644 index 000000000..25a1c87ef --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.es-es.js @@ -0,0 +1,15 @@ +export default { + optimizationDetails: { + optimizationDetails: 'Optimized routes', + schedule: 'Schedule', + warningUnassigned: 'Warning: There are unassigned jobs for this optimization!', + 'distance': 'Distance', + 'duration': 'Duration', + 'service': 'Service time', + 'delivery': 'Deliveries', + 'pickup': 'Pickups', + 'waiting_time': 'Waiting time', + 'isUnassigned': 'is unassigned.', + getInstructions: 'Get instructions for this Vehicle' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.fr-fr.js new file mode 100644 index 000000000..25a1c87ef --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.fr-fr.js @@ -0,0 +1,15 @@ +export default { + optimizationDetails: { + optimizationDetails: 'Optimized routes', + schedule: 'Schedule', + warningUnassigned: 'Warning: There are unassigned jobs for this optimization!', + 'distance': 'Distance', + 'duration': 'Duration', + 'service': 'Service time', + 'delivery': 'Deliveries', + 'pickup': 'Pickups', + 'waiting_time': 'Waiting time', + 'isUnassigned': 'is unassigned.', + getInstructions: 'Get instructions for this Vehicle' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.hu-hu.js new file mode 100644 index 000000000..25a1c87ef --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.hu-hu.js @@ -0,0 +1,15 @@ +export default { + optimizationDetails: { + optimizationDetails: 'Optimized routes', + schedule: 'Schedule', + warningUnassigned: 'Warning: There are unassigned jobs for this optimization!', + 'distance': 'Distance', + 'duration': 'Duration', + 'service': 'Service time', + 'delivery': 'Deliveries', + 'pickup': 'Pickups', + 'waiting_time': 'Waiting time', + 'isUnassigned': 'is unassigned.', + getInstructions: 'Get instructions for this Vehicle' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.it-it.js new file mode 100644 index 000000000..25a1c87ef --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.it-it.js @@ -0,0 +1,15 @@ +export default { + optimizationDetails: { + optimizationDetails: 'Optimized routes', + schedule: 'Schedule', + warningUnassigned: 'Warning: There are unassigned jobs for this optimization!', + 'distance': 'Distance', + 'duration': 'Duration', + 'service': 'Service time', + 'delivery': 'Deliveries', + 'pickup': 'Pickups', + 'waiting_time': 'Waiting time', + 'isUnassigned': 'is unassigned.', + getInstructions: 'Get instructions for this Vehicle' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.pt-br.js new file mode 100644 index 000000000..25a1c87ef --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.pt-br.js @@ -0,0 +1,15 @@ +export default { + optimizationDetails: { + optimizationDetails: 'Optimized routes', + schedule: 'Schedule', + warningUnassigned: 'Warning: There are unassigned jobs for this optimization!', + 'distance': 'Distance', + 'duration': 'Duration', + 'service': 'Service time', + 'delivery': 'Deliveries', + 'pickup': 'Pickups', + 'waiting_time': 'Waiting time', + 'isUnassigned': 'is unassigned.', + getInstructions: 'Get instructions for this Vehicle' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.ro-ro.js new file mode 100644 index 000000000..25a1c87ef --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-details/i18n/optimization-details.i18n.ro-ro.js @@ -0,0 +1,15 @@ +export default { + optimizationDetails: { + optimizationDetails: 'Optimized routes', + schedule: 'Schedule', + warningUnassigned: 'Warning: There are unassigned jobs for this optimization!', + 'distance': 'Distance', + 'duration': 'Duration', + 'service': 'Service time', + 'delivery': 'Deliveries', + 'pickup': 'Pickups', + 'waiting_time': 'Waiting time', + 'isUnassigned': 'is unassigned.', + getInstructions: 'Get instructions for this Vehicle' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.cs-cz.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.cs-cz.js new file mode 100644 index 000000000..40d8d573c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.cs-cz.js @@ -0,0 +1,8 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.es-es.js new file mode 100644 index 000000000..40d8d573c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.es-es.js @@ -0,0 +1,8 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.fr-fr.js new file mode 100644 index 000000000..40d8d573c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.fr-fr.js @@ -0,0 +1,8 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.hu-hu.js new file mode 100644 index 000000000..40d8d573c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.hu-hu.js @@ -0,0 +1,8 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.it-it.js new file mode 100644 index 000000000..40d8d573c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.it-it.js @@ -0,0 +1,8 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.pt-br.js new file mode 100644 index 000000000..40d8d573c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.pt-br.js @@ -0,0 +1,8 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.ro-ro.js new file mode 100644 index 000000000..40d8d573c --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/i18n/optimization-import.i18n.ro-ro.js @@ -0,0 +1,8 @@ +export default { + optimizationImport: { + acceptedImportTypes: 'Accepted import types', + notAJson: 'Input is not a valid JSON', + saveImport: 'Save new ', + notValid: 'File does not contain valid ', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.cs-cz.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.cs-cz.js new file mode 100644 index 000000000..6fcaa0ee0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.cs-cz.js @@ -0,0 +1,7 @@ +export default { + vehicle: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.es-es.js new file mode 100644 index 000000000..6fcaa0ee0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.es-es.js @@ -0,0 +1,7 @@ +export default { + vehicle: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.fr-fr.js new file mode 100644 index 000000000..6fcaa0ee0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.fr-fr.js @@ -0,0 +1,7 @@ +export default { + vehicle: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.hu-hu.js new file mode 100644 index 000000000..6fcaa0ee0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.hu-hu.js @@ -0,0 +1,7 @@ +export default { + vehicle: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.it-it.js new file mode 100644 index 000000000..6fcaa0ee0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.it-it.js @@ -0,0 +1,7 @@ +export default { + vehicle: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.pt-br.js new file mode 100644 index 000000000..6fcaa0ee0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.pt-br.js @@ -0,0 +1,7 @@ +export default { + vehicle: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.ro-ro.js new file mode 100644 index 000000000..6fcaa0ee0 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/i18n/vehicle.i18n.ro-ro.js @@ -0,0 +1,7 @@ +export default { + vehicle: { + amount: 'Amount', + service: 'Expected service time', + skills: 'Skills needed', + } +} From 919d7f82a5be91b5cd26fc900af225c306f0d7bd Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 2 May 2024 12:29:06 +0200 Subject: [PATCH 027/109] fix: GeoJSON download and import --- .../map-form/components/download/download.js | 2 +- .../optimization-import.js | 59 ++++++++++++------- src/models/job.js | 20 ++++++- src/models/vehicle.js | 32 ++++++++-- 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/fragments/forms/map-form/components/download/download.js b/src/fragments/forms/map-form/components/download/download.js index cf62a5b94..5f9ea35e5 100644 --- a/src/fragments/forms/map-form/components/download/download.js +++ b/src/fragments/forms/map-form/components/download/download.js @@ -106,7 +106,7 @@ export default { for (const d of this.data) { jsonData.push(d.toGeoJSON()) } - return jsonData + return { type: 'FeatureCollection', features: jsonData } }, }, methods: { diff --git a/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js index 0a2093597..b26e6d0d6 100644 --- a/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js +++ b/src/fragments/forms/map-form/components/optimization/components/optimization-import/optimization-import.js @@ -108,31 +108,48 @@ export default { const parsedJson = JSON.parse(fileContent) if (parsedJson && parsedJson.features) { fileType = 'geojson' + if (this.expectedData === 'jobs') { + for (const j of parsedJson.features) { + try { + newJobs.push(Job.fromGeoJsonObject(j)) + } catch { + this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.jobs'),) + } + } + } else if (this.expectedData === 'vehicles') { + for (const v of parsedJson.features) { + try { + newVehicles.push(Vehicle.fromGeoJsonObject(v)) + } catch { + this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.vehicles'),) + } + } + } } else { fileType = 'json' - } - if (this.expectedData === 'jobs') { - for (const j of parsedJson) { - try { - newJobs.push(Job.fromObject(j)) - } catch { - this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.jobs'),) + if (this.expectedData === 'jobs') { + for (const j of parsedJson) { + try { + newJobs.push(Job.fromObject(j)) + } catch { + this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.jobs'),) + } } - } - } else if (this.expectedData === 'vehicles') { - for (const v of parsedJson) { - try { - newVehicles.push(Vehicle.fromObject(v)) - } catch { - this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.vehicles'),) + } else if (this.expectedData === 'vehicles') { + for (const v of parsedJson) { + try { + newVehicles.push(Vehicle.fromObject(v)) + } catch { + this.showError(this.$t('optimizationImport.notValid') + this.$t('optimization.vehicles'),) + } } - } - } else if (this.expectedData === 'skills') { - for (const s of parsedJson) { - try { - newSkills.push(Skill.fromObject(s)) - } catch { - this.showError(this.$t('optimizationImport.notValidSkill')) + } else if (this.expectedData === 'skills') { + for (const s of parsedJson) { + try { + newSkills.push(Skill.fromObject(s)) + } catch { + this.showError(this.$t('optimizationImport.notValidSkill')) + } } } } diff --git a/src/models/job.js b/src/models/job.js index cca4f6640..14b0115dc 100644 --- a/src/models/job.js +++ b/src/models/job.js @@ -49,6 +49,25 @@ class Job extends Place { }) } + static fromGeoJsonObject(jobObject) { + let skillObjects = [] + if (jobObject.properties.skills) { + for (let id of jobObject.properties.skills) { + skillObjects.push(Skill.getName(id)) + } + } + return new Job(jobObject.geometry.coordinates[0], jobObject.geometry.coordinates[1], '', { + id: jobObject.properties.id, + service: jobObject.properties.service, + skills: skillObjects, + priority: jobObject.properties.priority, + delivery: jobObject.properties.delivery, + pickup: jobObject.properties.pickup, + time_windows: jobObject.properties.time_windows, + resolve: true + }) + } + static fromCsv(csv) { const lines = csv.split('\n') const header = lines[0].split(',') @@ -113,7 +132,6 @@ class Job extends Place { } toGeoJSON(stringify = false) { - // TODO: fix GeoJSON into proper format let props = { id: this.id, service: this.service, diff --git a/src/models/vehicle.js b/src/models/vehicle.js index 2c8c04414..a487d24b0 100644 --- a/src/models/vehicle.js +++ b/src/models/vehicle.js @@ -55,6 +55,31 @@ class Vehicle extends Place { ) } + static fromGeoJsonObject(vObject) { + let skillObjects = [] + if (vObject.properties.skills) { + for (let id of vObject.properties.skills) { + skillObjects.push(Skill.getName(id)) + } + } + return new Vehicle( + vObject.geometry.coordinates[0][0], + vObject.geometry.coordinates[0][1], + '', + { + id: vObject.properties.id, + description: vObject.properties.description, + profile: vObject.properties.profile, + start: vObject.properties.start, + end: vObject.geometry.coordinates[1], + capacity: vObject.properties.capacity, + skills: skillObjects, + time_window: vObject.properties.time_window, + resolve: true, + } + ) + } + static fromCsv(csv) { const lines = csv.split('\n') const header = lines[0].split(',') @@ -125,7 +150,6 @@ class Vehicle extends Place { } toGeoJSON(stringify = false) { - // TODO: fix GeoJSON into proper format let props = { id: this.id, description: this.description, @@ -145,9 +169,9 @@ class Vehicle extends Place { props.time_windows = this.time_windows } - const geoJsonData = { type: 'FeatureCollection', features: [ - { type: 'Feature', geometry: {type: 'Point', coordinates: this.start }, }, - { type: 'Feature', geometry: { type: 'Point', coordinates: this.end }, }], properties: props } + const geoJsonData = { type: 'Feature', + geometry: { type: 'MultiPoint', coordinates: [this.start, this.end] }, + properties: props } return stringify ? JSON.stringify(geoJsonData) : geoJsonData } From e28bbfeaace070c569d61224948347a01d1b73f8 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 2 May 2024 13:05:25 +0200 Subject: [PATCH 028/109] fix: edit job/vehicle from marker click --- .../components/optimization/Optimization.vue | 2 +- .../components/edit-dialog/edit-dialog.js | 15 +++++++++------ .../components/optimization/optimization.js | 7 +++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/Optimization.vue b/src/fragments/forms/map-form/components/optimization/Optimization.vue index 5be6914fc..baaa9cf0c 100644 --- a/src/fragments/forms/map-form/components/optimization/Optimization.vue +++ b/src/fragments/forms/map-form/components/optimization/Optimization.vue @@ -70,7 +70,7 @@ - diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index 6a7726bec..15bc5b1d5 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -8,6 +8,7 @@ import ProfileSelectorOption from '@/fragments/forms/profile-selector/components import EditSkills from '@/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue' import OptimizationImport from '@/fragments/forms/map-form/components/optimization/components/optimization-import/OptimizationImport.vue' import {EventBus} from '@/common/event-bus' +import {integer} from 'vee-validate/dist/rules.esm' import Job from '@/models/job' import Vehicle from '@/models/vehicle' import Skill from '@/models/skill' @@ -48,6 +49,10 @@ export default { Type: String, Required: true }, + index: { + Type: integer, + Required: false + }, disabledActions: { default: () => [], type: Array, @@ -134,12 +139,10 @@ export default { this.editSkills.push(skill.clone()) } - const context = this - // edit box is open - EventBus.$on('showEditModal', (editId) => { - context.isEditOpen = true - context.editId = editId - }) + if (this.index > 0) { + this.editId = this.index + } + // close editJobs box to pick a place from the map EventBus.$on('pickAPlace', () => { this.closeEditModal() diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index bf79502d8..3d6876e68 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -32,6 +32,7 @@ export default { pickPlaceSupported: true, showEditDialog: false, editProp: '', + editId: 0, editData: [], isImportOpen: false, expectedImport: '', @@ -135,7 +136,6 @@ export default { // On popup edit click -> edit job EventBus.$on('editJob', (index) => { - // TODO: fix index -> jobId received as undefined in editDialog context.manageJobs(index) }) @@ -146,7 +146,6 @@ export default { // On popup edit click -> edit vehicle EventBus.$on('editVehicle', (index) => { - // TODO: fix index -> jobId received as undefined in editDialog context.manageVehicles(index) }) @@ -237,7 +236,7 @@ export default { this.editProp = 'jobs' this.editData = this.jobs this.showEditDialog = true - EventBus.$emit('showEditModal', jobId) + this.editId = jobId }, // when there are no jobs and button in sidebar is clicked addJobFromMap() { @@ -267,7 +266,7 @@ export default { this.editProp = 'vehicles' this.editData = this.vehicles this.showEditDialog = true - EventBus.$emit('showEditModal', vehicleId) + this.editId = vehicleId }, // when there are no vehicles and button in sidebar is clicked addVehicleFromMap() { From 091f4747558d330560b50e83b84da1f1c00a9dd2 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 2 May 2024 16:57:23 +0200 Subject: [PATCH 029/109] feat: skills sync with jobs/vehicles - skills are automatically added when jobs/vehicles are imported - skills that are being used in a job/vehicle cannot be deleted from the edit skills menu --- .../components/edit-dialog/EditDialog.vue | 2 +- .../components/edit-dialog/edit-dialog.js | 33 ++++++++++++++++--- .../components/edit-skills/EditSkills.vue | 6 +++- .../components/edit-skills/edit-skills.css | 1 + .../components/edit-skills/edit-skills.js | 21 ++++++++---- .../i18n/edit-skills.i18n.en-us.js | 6 ++++ .../components/optimization/optimization.js | 20 +++++++++++ 7 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.en-us.js diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 7e3e5561b..1f823c9b2 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -117,7 +117,7 @@
- + diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index 15bc5b1d5..53dfc3083 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -19,8 +19,6 @@ export default { isEditOpen: true, editId: 0, editData: [], - editJobs: [], - editVehicles: [], editSkills: [], jobsBox: false, vehiclesBox: false, @@ -124,6 +122,13 @@ export default { const filter = OrsFilterUtil.getFilterRefByName(constants.profileFilterName) return filter.mapping }, + editSkillsJson () { + const jsonSkills = [] + for (const skill of this.editSkills) { + jsonSkills.push(skill.toJSON()) + } + return jsonSkills + }, }, created () { if (this.editProp === 'jobs') { @@ -175,8 +180,28 @@ export default { } else if (this.vehiclesBox) { this.editData = data.vehicles } - // TODO: check if editData contains skills that are not in editSkills yet - // write in with empty name if not there + + let importedSkill = [] + for (const d of this.editData) { + importedSkill.push(...d.skills) + } + let newSkillIds = [] + for (const s of importedSkill) { + if (!newSkillIds.includes(s.id)) { + newSkillIds.push(s.id) + } + } + let editSkillIds = [] + for (const s of this.editSkills) { + editSkillIds.push(s.id) + } + for (const id of newSkillIds.sort()) { + if (!editSkillIds.includes(id)) { + this.editSkills.push(new Skill(' Skill from imported ' + this.content.item + ' ' + id, id)) + } + } + this.$emit('skillsChanged', this.editSkills) + localStorage.setItem('skills', JSON.stringify(this.editSkillsJson)) this.saveItems() this.isImportOpen = false }, diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue index a07753d05..55e5c5c46 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/EditSkills.vue @@ -21,12 +21,16 @@
Skill: {{ skill.name }}
- + + check + + edit delete + {{ $t('editSkills.inUseShort') }}
diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css index cdb3863c5..85ef19651 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.css @@ -13,4 +13,5 @@ .edit-btn { float: right; + margin-left: 30px; } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js index 2345a3194..b5ab21dd0 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/edit-skills.js @@ -16,8 +16,12 @@ export default { props: { skills: { Type: Array[Skill], - Required: false + Required: true }, + skillsInUse: { + Type: Array, + Required: true + } }, components: { OptimizationImport, @@ -25,7 +29,7 @@ export default { Download }, computed: { - editSkillsJSON () { + editSkillsJson () { const jsonSkills = [] for (const skill of this.editSkills) { jsonSkills.push(skill.toJSON()) @@ -62,7 +66,7 @@ export default { // save changed skills and emit event to update in component saveSkills () { this.$emit('skillsChanged', this.editSkills) - localStorage.setItem('skills', JSON.stringify(this.editSkillsJSON)) + localStorage.setItem('skills', JSON.stringify(this.editSkillsJson)) this.closeSkillsModal() }, // add a new skill @@ -72,10 +76,13 @@ export default { }, // delete a skill removeSkill (id) { - // TODO: check if skills is used in any job/vehicle, only allow removal if not - this.editSkills.splice(id-1,1) - for (const i in this.editSkills) { - this.editSkills[i].setId(parseInt(i)+1) + if (this.skillsInUse.includes(this.editSkills[id-1].id)) { + this.showWarning(this.$t('editSkills.inUse')) + } else { + this.editSkills.splice(id-1,1) + for (const i in this.editSkills) { + this.editSkills[i].setId(parseInt(i)+1) + } } }, } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.en-us.js new file mode 100644 index 000000000..cb3932526 --- /dev/null +++ b/src/fragments/forms/map-form/components/optimization/components/edit-skills/i18n/edit-skills.i18n.en-us.js @@ -0,0 +1,6 @@ +export default { + editSkills: { + inUse: 'Skill is used in a Job or Vehicle', + inUseShort: 'in use' + } +} diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 3d6876e68..358e19671 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -70,6 +70,26 @@ export default { } return jsonSkills }, + skillsInUse () { + let skills = [] + for (const j of this.jobs) { + if (j.skills) { + skills.push(...j.skills) + } + } + for (const v of this.vehicles) { + if (v.skills) { + skills.push(...v.skills) + } + } + let skillIds = [] + for (const s of skills) { + if (s && !skillIds.includes(s.id)) { + skillIds.push(s.id) + } + } + return skillIds + }, disabledActions () { return appConfig.disabledActionsForOptimization } From 29e40016cb29b8c1a7c072450f5f7da1e50e1ff7 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Thu, 2 May 2024 17:33:32 +0200 Subject: [PATCH 030/109] feat: clear all data in edit dialog --- .../components/edit-dialog/EditDialog.vue | 4 ++++ .../components/edit-dialog/edit-dialog.js | 8 ++++++-- .../components/edit-skills/EditSkills.vue | 4 ++++ .../components/edit-skills/edit-skills.js | 19 +++++++++++++++++++ .../i18n/edit-skills.i18n.cs-cz.js | 2 ++ .../i18n/edit-skills.i18n.de-de.js | 2 ++ .../i18n/edit-skills.i18n.en-us.js | 2 ++ .../i18n/edit-skills.i18n.es-es.js | 2 ++ .../i18n/edit-skills.i18n.fr-fr.js | 2 ++ .../i18n/edit-skills.i18n.hu-hu.js | 2 ++ .../i18n/edit-skills.i18n.it-it.js | 2 ++ .../i18n/edit-skills.i18n.pt-br.js | 2 ++ .../i18n/edit-skills.i18n.ro-ro.js | 2 ++ .../i18n/optimization.i18n.cs-cz.js | 1 + .../i18n/optimization.i18n.de-de.js | 1 + .../i18n/optimization.i18n.en-us.js | 1 + .../i18n/optimization.i18n.es-es.js | 1 + .../i18n/optimization.i18n.fr-fr.js | 1 + .../i18n/optimization.i18n.hu-hu.js | 1 + .../i18n/optimization.i18n.it-it.js | 1 + .../i18n/optimization.i18n.pt-br.js | 1 + .../i18n/optimization.i18n.ro-ro.js | 1 + 22 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 1f823c9b2..928a997b1 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -103,6 +103,10 @@ {{ content.maxWarning }} + + {{ $t('optimization.clear') + content.expected }} +
+ + {{ $t('optimization.clear') + $t('optimization.skills') }} + Date: Mon, 6 May 2024 15:52:48 +0200 Subject: [PATCH 031/109] fix: vehicle time window --- .../components/edit-dialog/EditDialog.vue | 11 ++++--- .../components/edit-dialog/edit-dialog.js | 31 +++++++++++++------ .../i18n/edit-dialog.i18n.cs-cz.js | 4 +++ .../i18n/edit-dialog.i18n.de-de.js | 4 +++ .../i18n/edit-dialog.i18n.en-us.js | 4 +++ .../i18n/edit-dialog.i18n.es-es.js | 4 +++ .../i18n/edit-dialog.i18n.fr-fr.js | 4 +++ .../i18n/edit-dialog.i18n.hu-hu.js | 4 +++ .../i18n/edit-dialog.i18n.it-it.js | 4 +++ .../i18n/edit-dialog.i18n.pt-br.js | 4 +++ .../i18n/edit-dialog.i18n.ro-ro.js | 4 +++ .../components/vehicle-list/VehicleList.vue | 2 +- 12 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 928a997b1..e86079797 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -72,7 +72,7 @@ - + @@ -83,8 +83,11 @@ - - + + + + + @@ -99,7 +102,7 @@ - + {{ content.maxWarning }} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index e7ebbe694..f872e4b71 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -73,7 +73,7 @@ export default { item: 'Job', class: Job, maxLength: 50, - maxWarning: this.$t('optimization.maxWarning') + '50' + this.$t('optimization.jobs'), + maxWarning: this.$t('optimization.maxWarning') + '50 ' + this.$t('optimization.jobs'), import: this.$t('optimization.import') + this.$t('optimization.jobs'), edit: this.$t('optimization.edit') + this.$t('optimization.job'), add: this.$t('optimization.add') + this.$t('optimization.job'), @@ -91,7 +91,7 @@ export default { item: 'Vehicle', class: Vehicle, maxLength: 3, - maxWarning: this.$t('optimization.maxWarning') + '3' + this.$t('optimization.vehicles'), + maxWarning: this.$t('optimization.maxWarning') + '3 ' + this.$t('optimization.vehicles'), import: this.$t('optimization.import') + this.$t('optimization.vehicles'), edit: this.$t('optimization.edit') + this.$t('optimization.vehicle'), add: this.$t('optimization.add') + this.$t('optimization.vehicle'), @@ -118,6 +118,15 @@ export default { const id = this.editId - 1 return this.editData[id].start[0] === this.editData[id].end[0] && this.editData[id].start[1] === this.editData[id].end[1] }, + // check if one of the items does not have a location + hasEmptyLocation () { + for (let item of this.editData) { + if (item.location === null || item.start === null) { + return true + } + } + return false + }, profilesMapping () { const filter = OrsFilterUtil.getFilterRefByName(constants.profileFilterName) return filter.mapping @@ -210,18 +219,22 @@ export default { this.isImportOpen = false }, - // check if one of the jobs does not have a location - hasEmptyLocation () { - for (let item of this.editData) { - if (item.location === null || item.start === null) { - return true + // check if vehicle has only a time window start and no time window end + validateTimeWindow () { + for (let index in this.editData) { + if (this.editData[index].time_window[0] === '') { + this.editData[index].time_window = [] + } else if (this.editData[index].time_window.length === 1 || this.editData[index].time_window[1] === '') { + this.editData[index].time_window[1] = this.editData[index].time_window[0] + 3600 } } - return false }, // save items and close the edit box, show error if one or more of them has an empty location saveItems () { - if (this.hasEmptyLocation()) { + if (this.content.item === 'Vehicle') { + this.validateTimeWindow() + } + if (this.hasEmptyLocation) { this.showError(this.content.emptyLoc, {timeout: 3000}) } else { this.$emit(this.content.changedEvent, this.editData) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.cs-cz.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.de-de.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.en-us.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.es-es.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.fr-fr.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.hu-hu.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.it-it.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.pt-br.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js index 5b472718f..4ff8b13ba 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/i18n/edit-dialog.i18n.ro-ro.js @@ -1,5 +1,9 @@ export default { editDialog: { keepEdits: 'Keep edits', + service: 'Service time (in seconds)', + timeWindow: ' of time window (in seconds passed since 00:00 or timestamp)', + start: 'Start', + end: 'End' } } diff --git a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue index 4e9b82373..0baafbe8a 100644 --- a/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue +++ b/src/fragments/forms/map-form/components/optimization/components/vehicle-list/VehicleList.vue @@ -4,7 +4,7 @@ {{vehicleIcon(v.profile)}}Vehicle {{v.id}} ({{v.profile}}) diff --git a/src/fragments/map-view/components/map-view-markers/map-view-markers.js b/src/fragments/map-view/components/map-view-markers/map-view-markers.js index 9bfdda923..e71994b91 100644 --- a/src/fragments/map-view/components/map-view-markers/map-view-markers.js +++ b/src/fragments/map-view/components/map-view-markers/map-view-markers.js @@ -143,9 +143,13 @@ export default { }, skillIds(skills) { - const ids = [] + let ids = '' for (const skill of skills) { - ids.push(skill.id) + if(ids === ''){ + ids = skill.id + } else { + ids = ids + ', ' + skill.id + } } return ids } From 3a3df01e008b89b9f558992980fc2866e9fb3673 Mon Sep 17 00:00:00 2001 From: Selina Breitenbach Date: Thu, 23 May 2024 15:12:21 +0200 Subject: [PATCH 033/109] refactor: remove unused functions, parameter and imports remove disable Actions functionality for optimization --- .../components/optimization/Optimization.vue | 2 +- .../components/optimization/optimization.js | 31 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/Optimization.vue b/src/fragments/forms/map-form/components/optimization/Optimization.vue index baaa9cf0c..b357b8a49 100644 --- a/src/fragments/forms/map-form/components/optimization/Optimization.vue +++ b/src/fragments/forms/map-form/components/optimization/Optimization.vue @@ -62,7 +62,7 @@ - Date: Thu, 23 May 2024 16:12:14 +0200 Subject: [PATCH 034/109] feat: save job properties to AppRouteData options fixes #418 --- .../components/optimization/optimization.js | 77 +++++++++++++++---- src/models/place.js | 6 ++ 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 556562b4f..3c41f52f5 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -7,6 +7,7 @@ import { Optimization } from '@/support/ors-api-runner' import AppMode from '@/support/app-modes/app-mode' import MapViewData from '@/models/map-view-data' import constants from '@/resources/constants' +import Place from '@/models/place' import Job from '@/models/job' import Vehicle from '@/models/vehicle' import Skill from '@/models/skill' @@ -341,19 +342,65 @@ export default { * url synchronized with the current map status */ updateAppRoute () { - let localCoords = this.jobs.map(j => j.coordinates) - const appRouteData = this.$store.getters.appRouteData - let urlCoords = appRouteData.places.map(urlJ => urlJ.coordinates) - if (JSON.stringify(localCoords) !== JSON.stringify(urlCoords) || JSON.stringify(this.vehiclesJSON) !== JSON.stringify(appRouteData.options.vehicles)) { - const jobs = this.jobs - const vehicles = this.vehiclesJSON - this.$store.commit('mode', constants.modes.optimization) - const appMode = new AppMode(this.$store.getters.mode) - const route = appMode.getRoute(jobs, {vehicles: vehicles}) - if (Object.keys(route.params).length > 1) {// params contains data and placeName? props - this.$router.push(route) + this.$store.commit('mode', constants.modes.optimization) + const appMode = new AppMode(this.$store.getters.mode) + let jobLocations = [] + let jobProps = [] + for (const job of this.jobs) { + jobLocations.push(Place.fromJob(job)) + jobProps.push(this.getPropsFromJob(job)) + } + const route = appMode.getRoute(jobLocations, {vehicles: this.vehiclesJSON, jobProps: jobProps}) + if (Object.keys(route.params).length > 1) {// params contains data and placeName? props + this.$router.push(route) + } + }, + getPropsFromJob (job) { + let jobProps = {id: job.id} + if (job.skills.length) { + let skillIds = [] + for (const skill of job.skills) { + skillIds.push(skill.id) + } + skillIds.sort() + jobProps.skills = skillIds + } + for (const prop of ['service', 'priority', 'delivery', 'pickup', 'time_windows']) { + if (job[prop].length && job[prop][0] !== 0) { + jobProps[prop] = job[prop] + } + } + return jobProps + }, + parseProps(jobProps) { + if (!jobProps) { + return undefined + } + let parsedProps = [] + for (const j of jobProps) { + let parsedJobProps = {id: j.id} + for (const prop of ['service', 'priority', 'delivery', 'pickup', 'time_windows']) { + if (j[prop]) { + parsedJobProps[prop] = j[prop] + } + } + let propSkills = [] + for (const s of j.skills) { + let skillIds = [] + for (const skill of this.skills) { + skillIds.push(skill.id) + } + if (skillIds.includes(s)) { + propSkills.push(this.skills[s-1]) + } else { + propSkills.push(new Skill('Skill from added ' + this.$t('optimization.job') + ' ' + j.id, s)) + } } + parsedJobProps.skills = propSkills + + parsedProps.push(parsedJobProps) } + return parsedProps }, /** * Request and draw a route based on the value of multiples places input @@ -435,9 +482,9 @@ export default { // object reference because it is a prop const defaultJobs = this.jobs const defaultVehicles = this.vehicles - this.jobs = this.$store.getters.appRouteData.jobs + const jobProps = this.parseProps(this.$store.getters.appRouteData.options.jobProps) const urlVehicles = this.$store.getters.appRouteData.options.vehicles - let places = this.$store.getters.appRouteData.places + const places = this.$store.getters.appRouteData.places let storedJobs = localStorage.getItem('jobs') let storedVehicles = localStorage.getItem('vehicles') // prioritise data from url, then data from local storage @@ -459,9 +506,7 @@ export default { const jobs = [] if (places.length > 0) { for (const [i, place] of places.entries()) { - const job = Job.fromPlace(place) - job.setId(i + 1) - jobs.push(job) + jobs.push(new Job(place.lng, place.lat, place.placeName, jobProps[i])) } } else if (this.jobs === undefined && storedJobs) { for (const job of JSON.parse(storedJobs)) { diff --git a/src/models/place.js b/src/models/place.js index b72596b2b..17f972160 100644 --- a/src/models/place.js +++ b/src/models/place.js @@ -219,6 +219,12 @@ class Place { }) } + static fromJob(job) { + return new Place(job.location[0], job.location[1], '', { + placeId: job.id + }) + } + /** * Get place models that are filled * @returns {Array} of filled places From 588a87efad17b2235c951c0abda8b4e368f5307d Mon Sep 17 00:00:00 2001 From: Selina Breitenbach Date: Thu, 23 May 2024 16:48:43 +0200 Subject: [PATCH 035/109] refactor: decrease complexity --- .../components/optimization/optimization.js | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 3c41f52f5..cadbb9980 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -187,26 +187,10 @@ export default { let id = data.placeInputId // pickEditSource indicates which property the place should fill if (data.pickEditSource === 'jobs') { - if (id > context.jobs.length) { - let job = Job.fromPlace(data.place) - job.setId(id) - context.jobs.push(job) - } else { - context.jobs[data.pickPlaceIndex].location = data.place.coordinates - } + context.setJobLocation(id, data) context.manageJobs(id) } else if (data.pickEditSource === 'vehicleStart') { - if (id > context.vehicles.length) { - let v = Vehicle.fromPlace(data.place) - v.setId(id) - context.vehicles.push(v) - } else { - const v = context.vehicles[data.pickPlaceIndex] - if (v.end[0] === v.start[0] && v.end[1] === v.start[1]) { - context.vehicles[data.pickPlaceIndex].end = data.place.coordinates - } - context.vehicles[data.pickPlaceIndex].start = data.place.coordinates - } + context.setVehicleStartLocation(id, data) context.manageVehicles(id) } else if (data.pickEditSource === 'vehicleEnd') { this.vehicles[data.pickPlaceIndex].end = data.place.coordinates @@ -336,6 +320,38 @@ export default { } this.updateAppRoute() }, + /** + * Set location of job with id from given data or create new Job + * @param id + * @param data + */ + setJobLocation(id, data){ + if (id > this.jobs.length) { + let job = Job.fromPlace(data.place) + job.setId(id) + this.jobs.push(job) + } else { + this.jobs[data.pickPlaceIndex].location = data.place.coordinates + } + }, + /** + * Set start location of vehicle with id from given data or create new Job + * @param id + * @param data + */ + setVehicleStartLocation(id, data){ + if (id > this.vehicles.length) { + let v = Vehicle.fromPlace(data.place) + v.setId(id) + this.vehicles.push(v) + } else { + const v = this.vehicles[data.pickPlaceIndex] + if (v.end[0] === v.start[0] && v.end[1] === v.start[1]) { + this.vehicles[data.pickPlaceIndex].end = data.place.coordinates + } + this.vehicles[data.pickPlaceIndex].start = data.place.coordinates + } + }, /** * After each change on the map search we redirect the user to the built target app route * The data will be loaded from the path and the map will be updated, keeping the From b29e418f914307528d95428a24658554ca291957 Mon Sep 17 00:00:00 2001 From: Selina Breitenbach Date: Mon, 27 May 2024 13:17:49 +0200 Subject: [PATCH 036/109] refactor: decrease complexity --- .../components/optimization/optimization.js | 92 +++++++++++-------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index cadbb9980..191914dfa 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -493,47 +493,63 @@ export default { */ loadData () { if (this.$store.getters.mode === constants.modes.optimization) { - // Empty the array and populate it with the - // places from the appRoute without changing the - // object reference because it is a prop - const defaultJobs = this.jobs - const defaultVehicles = this.vehicles - const jobProps = this.parseProps(this.$store.getters.appRouteData.options.jobProps) - const urlVehicles = this.$store.getters.appRouteData.options.vehicles - const places = this.$store.getters.appRouteData.places - let storedJobs = localStorage.getItem('jobs') - let storedVehicles = localStorage.getItem('vehicles') - // prioritise data from url, then data from local storage - if (urlVehicles) { - const vehicles = [] - for (let v of urlVehicles) { - vehicles.push(Vehicle.fromObject(v)) - } - this.vehicles = vehicles - } else if (this.vehicles === undefined && storedVehicles) { - const vehicles = [] - for (const v of JSON.parse(storedVehicles)) { - vehicles.push(Vehicle.fromObject(v)) - } - this.vehicles = vehicles - } else if (this.vehicles === undefined || !this.vehicles.length) { - this.vehicles = defaultVehicles + this.loadVehicles() + this.loadJobs() + this.optimizeJobs() + } + }, + + /** + * Load data of vehicles + * prioritizing url data over storage data + */ + loadVehicles() { + const defaultVehicles = this.vehicles + const urlVehicles = this.$store.getters.appRouteData.options.vehicles + let storedVehicles = localStorage.getItem('vehicles') + // prioritise data from url, then data from local storage + if (urlVehicles) { + const vehicles = [] + for (let v of urlVehicles) { + vehicles.push(Vehicle.fromObject(v)) } - const jobs = [] - if (places.length > 0) { - for (const [i, place] of places.entries()) { - jobs.push(new Job(place.lng, place.lat, place.placeName, jobProps[i])) - } - } else if (this.jobs === undefined && storedJobs) { - for (const job of JSON.parse(storedJobs)) { - jobs.push(Job.fromObject(job)) - } + this.vehicles = vehicles + } else if (this.vehicles === undefined && storedVehicles) { + const vehicles = [] + for (const v of JSON.parse(storedVehicles)) { + vehicles.push(Vehicle.fromObject(v)) } - this.jobs = jobs - if (!this.jobs.length) { - this.jobs = defaultJobs + this.vehicles = vehicles + } else if (this.vehicles === undefined || !this.vehicles.length) { + this.vehicles = defaultVehicles + } + }, + + /** + * Load data of jobs + * prioritizing url data over storage data + */ + loadJobs() { + // Empty the array and populate it with the + // places from the appRoute without changing the + // object reference because it is a prop + const defaultJobs = this.jobs + const jobProps = this.parseProps(this.$store.getters.appRouteData.options.jobProps) + const places = this.$store.getters.appRouteData.places + let storedJobs = localStorage.getItem('jobs') + const jobs = [] + if (places.length > 0) { + for (const [i, place] of places.entries()) { + jobs.push(new Job(place.lng, place.lat, place.placeName, jobProps[i])) } - this.optimizeJobs() + } else if (this.jobs === undefined && storedJobs) { + for (const job of JSON.parse(storedJobs)) { + jobs.push(Job.fromObject(job)) + } + } + this.jobs = jobs + if (!this.jobs.length) { + this.jobs = defaultJobs } }, // when jobs are changed update jobs and generate new route From 62debb4612306da10bcee1929860a34384aef29d Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Mon, 27 May 2024 14:25:11 +0200 Subject: [PATCH 037/109] fix: jobProps can't be parsed when undefined --- .../components/optimization/optimization.js | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 191914dfa..c622e0bc2 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -389,9 +389,6 @@ export default { return jobProps }, parseProps(jobProps) { - if (!jobProps) { - return undefined - } let parsedProps = [] for (const j of jobProps) { let parsedJobProps = {id: j.id} @@ -400,19 +397,21 @@ export default { parsedJobProps[prop] = j[prop] } } - let propSkills = [] - for (const s of j.skills) { - let skillIds = [] - for (const skill of this.skills) { - skillIds.push(skill.id) - } - if (skillIds.includes(s)) { - propSkills.push(this.skills[s-1]) - } else { - propSkills.push(new Skill('Skill from added ' + this.$t('optimization.job') + ' ' + j.id, s)) + if (j.skills) { + let propSkills = [] + for (const s of j.skills) { + let skillIds = [] + for (const skill of this.skills) { + skillIds.push(skill.id) + } + if (skillIds.includes(s)) { + propSkills.push(this.skills[s-1]) + } else { + propSkills.push(new Skill('Skill from added ' + this.$t('optimization.job') + ' ' + j.id, s)) + } } + parsedJobProps.skills = propSkills } - parsedJobProps.skills = propSkills parsedProps.push(parsedJobProps) } @@ -534,14 +533,19 @@ export default { // places from the appRoute without changing the // object reference because it is a prop const defaultJobs = this.jobs - const jobProps = this.parseProps(this.$store.getters.appRouteData.options.jobProps) const places = this.$store.getters.appRouteData.places + const propData = this.$store.getters.appRouteData.options.jobProps let storedJobs = localStorage.getItem('jobs') const jobs = [] - if (places.length > 0) { + if (propData && places.length === propData.length) { + const jobProps = this.parseProps(propData) for (const [i, place] of places.entries()) { jobs.push(new Job(place.lng, place.lat, place.placeName, jobProps[i])) } + } else if (places.length > 0) { + for (const [i, place] of places.entries()) { + jobs.push(new Job(place.lng, place.lat, place.placeName, {id: i+1})) + } } else if (this.jobs === undefined && storedJobs) { for (const job of JSON.parse(storedJobs)) { jobs.push(Job.fromObject(job)) From 197635f376f218ac63bd6fb12f2d351437b7145b Mon Sep 17 00:00:00 2001 From: Selina Breitenbach Date: Mon, 27 May 2024 16:32:49 +0200 Subject: [PATCH 038/109] refactor: reduce redundancy --- .../optimization/components/edit-dialog/edit-dialog.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index f872e4b71..e3fe74003 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -107,11 +107,11 @@ export default { } }, headerText () { - if (this.editId === 0) { - return this.content.header - } else { - return this.content.header + ' - editing ' + this.editId + let editing = '' + if (this.editId !== 0) { + editing = ' - editing ' + this.editId } + return this.content.header + editing }, // returns true if start and end point are the same sameStartEndPoint () { From 7edf018a39339ad0cbf6e1ace324f0fc193059fb Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Mon, 3 Jun 2024 11:10:07 +0200 Subject: [PATCH 039/109] fix: add disabledActions roundtrip and addPlaceInput buttons are no longer shown, since they do nothing in the optimization tab anyway. routeImporter is disabled as it's currently not implemented --- src/config-examples/app-config-example.js | 1 + .../forms/map-form/components/optimization/Optimization.vue | 3 +-- .../forms/map-form/components/optimization/optimization.js | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/config-examples/app-config-example.js b/src/config-examples/app-config-example.js index 771e3b615..083ac6198 100755 --- a/src/config-examples/app-config-example.js +++ b/src/config-examples/app-config-example.js @@ -36,6 +36,7 @@ const appConfig = { autoSelectFirstExactAddressMatchOnSearchEnter: true, // If the first exact address match must be auto selected when the user type a text and in the place search and hit enter/return + disabledActionsForOptimization: ['addPlaceInput', 'roundtrip', 'routeImporter'], // Possible values: `addPlaceInput`, `clearPlaces`, `reverseRoute`, `roundtrip`, `routeImporter` disabledActionsForIsochrones: ['roundtrip'], // Possible values: `addPlaceInput`, `clearPlaces`, `reverseRoute`, `roundtrip`, `routeImporter` disabledActionsForPlacesAndDirections: [], // // Possible values: `addPlaceInput`, `clearPlaces`, `reverseRoute`, `roundtrip`, `routeImporter` supportsPlacesAndDirections: true, // If the whole places and directions feature is supported/enabled in the application diff --git a/src/fragments/forms/map-form/components/optimization/Optimization.vue b/src/fragments/forms/map-form/components/optimization/Optimization.vue index b357b8a49..5bd4c0ceb 100644 --- a/src/fragments/forms/map-form/components/optimization/Optimization.vue +++ b/src/fragments/forms/map-form/components/optimization/Optimization.vue @@ -62,8 +62,7 @@ - diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index c622e0bc2..62e2869f5 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -7,6 +7,7 @@ import { Optimization } from '@/support/ors-api-runner' import AppMode from '@/support/app-modes/app-mode' import MapViewData from '@/models/map-view-data' import constants from '@/resources/constants' +import appConfig from '@/config/app-config' import Place from '@/models/place' import Job from '@/models/job' import Vehicle from '@/models/vehicle' @@ -88,6 +89,9 @@ export default { } } return skillIds + }, + disabledActions () { + return appConfig.disabledActionsForOptimization } }, created () { From d34c82dea49b1c09459593f4228b4cdf709daa66 Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Mon, 3 Jun 2024 14:26:56 +0200 Subject: [PATCH 040/109] fix: parsing and display of job props --- .../optimization/components/edit-dialog/EditDialog.vue | 5 +++-- .../forms/map-form/components/optimization/optimization.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index 651b8a10c..ba9e67b1e 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -39,8 +39,9 @@
- {{ $t(`optimization.${k}`) }}: {{v[0]}} - {{ $t(`optimization.${k}`) }}: {{skillNames(d)}} + {{ $t(`optimization.${k}`) }}: {{v}} + {{ $t(`optimization.${k}`) }}: {{v[0]}} + {{ $t(`optimization.${k}`) }}: {{skillNames(d)}}
diff --git a/src/fragments/forms/map-form/components/optimization/optimization.js b/src/fragments/forms/map-form/components/optimization/optimization.js index 62e2869f5..5c3d80eb5 100644 --- a/src/fragments/forms/map-form/components/optimization/optimization.js +++ b/src/fragments/forms/map-form/components/optimization/optimization.js @@ -385,8 +385,8 @@ export default { skillIds.sort() jobProps.skills = skillIds } - for (const prop of ['service', 'priority', 'delivery', 'pickup', 'time_windows']) { - if (job[prop].length && job[prop][0] !== 0) { + for (const prop of ['service', 'priority', 'delivery', 'pickup']) { + if (job[prop] && job[prop] !== 0 && job[prop][0] !== 0) { jobProps[prop] = job[prop] } } From 1b3d490610381cb001714ba77d50d913142e084e Mon Sep 17 00:00:00 2001 From: Emma Hegarty Date: Mon, 3 Jun 2024 14:45:43 +0200 Subject: [PATCH 041/109] feat: humanise time in job/vehicle chips --- .../components/edit-dialog/EditDialog.vue | 2 +- .../components/edit-dialog/edit-dialog.js | 6 ++++++ .../components/job-list/JobList.vue | 2 +- .../components/job-list/job-list.js | 8 ++++++- .../components/vehicle-list/VehicleList.vue | 4 +++- .../components/vehicle-list/vehicle-list.js | 13 ++++++++++++ src/support/geo-utils.js | 21 +++++++++++++++++++ 7 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue index ba9e67b1e..75d6773ce 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/EditDialog.vue @@ -39,7 +39,7 @@
- {{ $t(`optimization.${k}`) }}: {{v}} + {{ $t(`optimization.${k}`) }}: {{humanisedTime(v)}} {{ $t(`optimization.${k}`) }}: {{v[0]}} {{ $t(`optimization.${k}`) }}: {{skillNames(d)}} diff --git a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js index e3fe74003..be7f197b9 100644 --- a/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js +++ b/src/fragments/forms/map-form/components/optimization/components/edit-dialog/edit-dialog.js @@ -13,6 +13,7 @@ import Job from '@/models/job' import Vehicle from '@/models/vehicle' import Skill from '@/models/skill' import {vehicleColors} from '@/support/optimization-utils' +import geoUtils from '@/support/geo-utils' export default { data: () => ({ @@ -179,6 +180,11 @@ export default { this.$emit('contentUploaded', data) }, + humanisedTime (time) { + const data = geoUtils.getHumanizedTimeAndDistance({duration: time}, this.$t('global.units')) + return data.duration + }, + clearData () { this.editId = 0 this.editData = [] diff --git a/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue b/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue index 08d3a1379..55d71603e 100644 --- a/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue +++ b/src/fragments/forms/map-form/components/optimization/components/job-list/JobList.vue @@ -5,7 +5,7 @@
workJob {{j.id}} - {{ j.location[0].toPrecision(8) }}, {{ j.location[1].toPrecision(8)}}