feat: implemented visual messaging

This commit is contained in:
steev 2025-01-16 00:16:45 +01:00
parent 20ac0faa75
commit 5769fabce4
9 changed files with 761 additions and 556 deletions

318
app.py
View File

@ -40,182 +40,200 @@ gpxHandler = GPXHandler(session)
driverHandler = DriverHandler(session) driverHandler = DriverHandler(session)
vehicleHandler = VehicleHandler(session) vehicleHandler = VehicleHandler(session)
@app.route('/') @app.route('/')
@cross_origin() @cross_origin()
def serve_vue_app(): def serve_vue_app():
return send_from_directory(app.static_folder, 'index.html') return send_from_directory(app.static_folder, 'index.html')
@app.route("/track", methods=['GET'])
@app.route("/track", methods=['GET', 'DELETE'])
@cross_origin() @cross_origin()
def getTrack(): def getTrack():
app.logger.debug(f"found arguments {request.args}") app.logger.debug(f"found arguments {request.args}")
if ("start" in request.args and "end" in request.args) or ("id" in request.args):
if "start" in request.args and "end" in request.args:
if "asMap" in request.args:
# get tracks by filter
start = request.args["start"]
end = request.args["end"]
# Die GeoJSON-Daten aus der Datenbank abrufen
geojson_data = gpxHandler.getTracksInTimeWithGeoData(start, end)
app.logger.debug(f"returned track {geojson_data}")
# Die GeoJSON-Daten als JSON zurückgeben match request.method:
return jsonify(geojson_data) case "GET":
if ("start" in request.args and "end" in request.args) or ("id" in request.args):
# get tracks by filter if "start" in request.args and "end" in request.args:
start = request.args["start"] if "asMap" in request.args:
end = request.args["end"] # get tracks by filter
try: start = request.args["start"]
return gpxHandler.getTracksInTime(start, request.args["end"]), 200 end = request.args["end"]
except Exception as e:
app.logger.debug(f"failed to search tracks error {e} values: start={start}, end={end}")
return f"error {e}", 500
elif "id" in request.args: # Die GeoJSON-Daten aus der Datenbank abrufen
# get track by id geojson_data = gpxHandler.getTracksInTimeWithGeoData(
trackID = int(request.args["id"]) start, end)
try: app.logger.debug(f"returned track {geojson_data}")
app.logger.debug(f"Request args: {request.args}")
app.logger.debug(f"track id {trackID}") # Die GeoJSON-Daten als JSON zurückgeben
track = gpxHandler.getTrack(trackID) return jsonify(geojson_data)
app.logger.debug(f"returned track {track}") # get tracks by filter
start = request.args["start"]
return jsonify(track), 200 end = request.args["end"]
except Exception as e: try:
app.logger.debug(f"fetching track failed with error {e}") return gpxHandler.getTracksInTime(start, request.args["end"]), 200
return f"error {e}", 500 except Exception as e:
else: app.logger.debug(f"failed to search tracks error {
try: e} values: start={start}, end={end}")
tracks = gpxHandler.getTracks() return f"error {e}", 500
if len(tracks) > 0:
# gets all tracks as list elif "id" in request.args:
return tracks, 200 # get track by id
trackID = int(request.args["id"])
try:
app.logger.debug(f"Request args: {request.args}")
app.logger.debug(f"track id {trackID}")
track = gpxHandler.getTrack(trackID)
app.logger.debug(f"returned track {track}")
return jsonify(track), 200
except Exception as e:
app.logger.debug(f"fetching track failed with error {e}")
return f"error {e}", 500
else: else:
return [], 200 try:
except Exception as e: tracks = gpxHandler.getTracks()
app.logger.debug(f"fetching all tracks failed with error {e}") if len(tracks) > 0:
return f"error {e}", 500 # gets all tracks as list
return tracks, 200
else:
return [], 200
except Exception as e:
app.logger.debug(f"fetching all tracks failed with error {e}")
return f"error {e}", 500
case "DELETE":
pass
@app.route("/track/meta", methods=['GET']) @app.route("/track/meta", methods=['GET'])
@cross_origin() @cross_origin()
def getTrackMeta(): def getTrackMeta():
app.logger.debug(f"found arguments {request.args}") app.logger.debug(f"found arguments {request.args}")
if "id" in request.args:
# get track by id
trackID = int(request.args["id"])
try:
app.logger.debug(f"Request args: {request.args}")
app.logger.debug(f"track id {trackID}")
track = gpxHandler.getTrackMeta(trackID)
app.logger.debug(f"returned track {track}")
return jsonify(track), 200
except Exception as e:
app.logger.debug(f"fetching track failed with error {e}")
return f"error {e}", 500
@app.route("/driver", methods=['GET', 'POST']) if "id" in request.args:
# get track by id
trackID = int(request.args["id"])
try:
app.logger.debug(f"Request args: {request.args}")
app.logger.debug(f"track id {trackID}")
track = gpxHandler.getTrackMeta(trackID)
app.logger.debug(f"returned track {track}")
return jsonify(track), 200
except Exception as e:
app.logger.debug(f"fetching track failed with error {e}")
return f"error {e}", 500
@app.route("/driver", methods=['GET', 'POST', 'PUT', 'DELETE'])
@cross_origin() @cross_origin()
def handleDriverRoute(): def handleDriverRoute():
if request.method == "GET": match request.method:
if 'driver' in request.args: case "GET":
if 'driver' in request.args:
try:
driver = driverHandler.getDriver(int(request.args["driver"]))
return driver, 200
except Exception as e:
app.logger.debug(f"getting driver failed with error {e}")
return "error" + " " + str(e), 500
else:
try:
drivers = driverHandler.getDrivers()
if len(drivers) > 0:
return drivers, 200
except Exception as e:
app.logger.debug(f"getting drivers failed with error {e}")
return "error" + " " + str(e), 500
case "POST":
# grabbing json from request
data = request.get_json()
app.logger.debug(f"json request payload: {data}")
if "name" not in data:
app.logger.debug(f"no driver name was found in request")
return "missing name", 400
app.logger.debug(f"driver name has passed check {data["name"]}")
# handle creating vehicle
try: try:
driver = driverHandler.getDriver(int(request.args["driver"])) app.logger.debug(f"json request name: {data["name"]}")
return driver, 200 driver = driverHandler.createDriver(data["name"])
return jsonify({"name": driver.name, "id": driver.id}), 200
except Exception as e: except Exception as e:
app.logger.debug(f"getting driver failed with error {e}") app.logger.debug(f"creating drivers failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
case "PUT":
else: pass
case "DELETE":
try: pass
drivers = driverHandler.getDrivers()
if len(drivers) > 0:
return drivers, 200
except Exception as e:
app.logger.debug(f"getting drivers failed with error {e}")
return "error" + " " + str(e), 500
elif request.method == "POST":
# grabbing json from request
data = request.get_json()
app.logger.debug(f"json request payload: {data}")
if "name" not in data:
app.logger.debug(f"no driver name was found in request")
return "missing name", 400
app.logger.debug(f"driver name has passed check {data["name"]}")
# handle creating vehicle
try:
app.logger.debug(f"json request name: {data["name"]}")
driver = driverHandler.createDriver(data["name"])
return jsonify({"name": driver.name, "id": driver.id}), 200
except Exception as e:
app.logger.debug(f"creating drivers failed with error {e}")
return "error" + " " + str(e), 500
@app.route("/vehicle", methods=['GET', 'POST']) @app.route("/vehicle", methods=['GET', 'POST', 'PUT', 'DELETE'])
@cross_origin() @cross_origin()
def handleVehicleRoute(): def handleVehicleRoute():
if request.method == "GET": match request.method:
if 'vehicle' in request.args: case "GET":
app.logger.debug(f"no vehicle id was found in request") if 'vehicle' in request.args:
vehicle = int(request.args["vehicle"]) app.logger.debug(f"no vehicle id was found in request")
vehicle = int(request.args["vehicle"])
try:
foundVehicle = vehicleHandler.getVehicle(vehicle)
return foundVehicle, 200
except Exception as e:
app.logger.debug(f"getting vehicle {vehicle} failed with error {e}")
return "error" + " " + str(e), 500
else:
try:
foundVehicles = vehicleHandler.getVehicles()
return foundVehicles, 200
except Exception as e:
app.logger.debug(
f"getting all vehicles failed with error {e}")
return "error" + " " + str(e), 500
case "POST":
data = request.get_json()
app.logger.debug(f"json request payload: {data}")
if "name" not in data:
return "missing name", 400
licenseplate = ""
if "licensePlate" not in data:
licenseplate = "N/A"
else:
licenseplate = data["licensePlate"]
name = data["name"]
# handle creating vehicle
try: try:
foundVehicle = vehicleHandler.getVehicle(vehicle) vehicle = vehicleHandler.createVehicle(name, licenseplate)
return foundVehicle, 200 return jsonify({"id": vehicle.id, "name": vehicle.name}), 200
except Exception as e: except Exception as e:
app.logger.debug(f"getting vehicle {vehicle} failed with error {e}") app.logger.debug(f"creating vehicle with name {name} failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
case "PUT":
else: pass
case "DELETE":
try: pass
foundVehicles = vehicleHandler.getVehicles()
return foundVehicles, 200
except Exception as e:
app.logger.debug(f"getting all vehicles failed with error {e}")
return "error" + " " + str(e), 500
elif request.method == "POST":
data = request.get_json()
app.logger.debug(f"json request payload: {data}")
if "name" not in data:
return "missing name", 400
licenseplate = ""
if "licensePlate" not in data:
licenseplate = "N/A"
else:
licenseplate = data["licensePlate"]
name = data["name"]
# handle creating vehicle
try:
vehicle = vehicleHandler.createVehicle(name, licenseplate)
return jsonify({"id": vehicle.id, "name": vehicle.name}), 200
except Exception as e:
app.logger.debug(f"creating vehicle with name {name} failed with error {e}")
return "error" + " " + str(e), 500
@app.route('/upload', methods=['POST']) @app.route('/upload', methods=['POST'])
@ -231,29 +249,29 @@ def uploadFile():
return "no file selected", 400 return "no file selected", 400
try: try:
app.logger.debug(f"Received file: {file.filename}, size: {len(file.read())} bytes") app.logger.debug(f"Received file: {file.filename}, size: {len(file.read())} bytes")
file.seek(0) # Setzt den Datei-Zeiger zurück, nachdem die Größe abgerufen wurde.
file.seek(0)
file_path = f'./uploads/{file.filename}' file_path = f'./uploads/{file.filename}'
with open(file_path, 'wb') as f: with open(file_path, 'wb') as f:
f.write(file.read()) f.write(file.read())
driverID = int(request.form.get('driverID')) driverID = int(request.form.get('driverID'))
vehicleID = int(request.form.get('vehicleID')) vehicleID = int(request.form.get('vehicleID'))
driver = driverHandler.getDriver(driverID) driver = driverHandler.getDriver(driverID)
vehicle = vehicleHandler.getVehicle(vehicleID) vehicle = vehicleHandler.getVehicle(vehicleID)
app.logger.debug(f"driver {driver.id}") app.logger.debug(f"driver {driver.id}")
app.logger.debug(f"vehicle {vehicle.id}") app.logger.debug(f"vehicle {vehicle.id}")
if not driver or not vehicle: if not driver or not vehicle:
raise ValueError("Driver or vehicle not found") raise ValueError("Driver or vehicle not found")
app.logger.debug(f"attempting to parse file: {file.filename}") app.logger.debug(f"attempting to parse file: {file.filename}")
gpxHandler.parse(file.filename, driver, vehicle) gpxHandler.parse(file.filename, driver, vehicle)
return "file stored succesfull", 200 return "file stored succesfull", 200
except Exception as e: except Exception as e:
app.logger.debug(f"storing gpx file failed with error {e}") app.logger.debug(f"storing gpx file failed with error {e}")

View File

@ -1,214 +1,244 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, SetupContext, ref, Ref } from 'vue'; import { defineComponent, SetupContext, ref, Ref } from 'vue';
import GetLocalizedText from "../classes/language"; import GetLocalizedText from "../classes/language";
import Message from "./message.vue";
type driver = { type driver = {
id: number id: number
name: string name: string
} }
type vehicle = { type vehicle = {
id: number id: number
name: string name: string
} }
export default defineComponent({ export default defineComponent({
emits: ['close', 'response'], emits: ['close', 'response'],
name: 'settings', components: { Message },
props: ["drivers"], name: 'settings',
setup(props, { emit }: SetupContext) { props: ["drivers"],
setup(props, { emit }: SetupContext) {
const file = ref<File | null>(); const file = ref<File | null>();
const form = ref<HTMLFormElement>(); const form = ref<HTMLFormElement>();
const drivers: Ref<driver[]> = ref([]) const drivers: Ref<driver[]> = ref([])
const vehicles: Ref<vehicle[]> = ref([]) const vehicles: Ref<vehicle[]> = ref([])
const selectedDriverID: Ref<number> = ref(0); const selectedDriverID: Ref<number> = ref(0);
const selectedVehicleID: Ref<number> = ref(0); const selectedVehicleID: Ref<number> = ref(0);
const selectedDriverName: Ref<String> = ref("N/A"); const selectedDriverName: Ref<String> = ref("N/A");
const selectedVehicleName: Ref<String> = ref("N/A"); const selectedVehicleName: Ref<String> = ref("N/A");
props.drivers.forEach((d: driver) => { // button text
drivers.value.push({ id: d.id, name: d.name }) const buttonText: Ref<string> = ref('Upload');
});
// localized text // values for UI Information distribution
// if there is time left this gets moved out to its own class const messageType: Ref<string> = ref("None ");
var localizedUploadHeader: Ref<string> = ref("") const message:Ref<string> = ref("");
async function getLocalization() {
localizedUploadHeader.value = await GetLocalizedText("localizedUploadHeader")
}
var fileInputChange = async ($event: Event) => { props.drivers.forEach((d: driver) => {
const target = $event.target as HTMLInputElement; drivers.value.push({ id: d.id, name: d.name })
if (target && target.files) { });
file.value = target.files[0];
console.log(`selected file: ${file.value}`);
}
}
// handles sending webrequests to the backend // localized text
const getDrivers = async () => { // if there is time left this gets moved out to its own class
var localizedUploadHeader: Ref<string> = ref("")
async function getLocalization() {
localizedUploadHeader.value = await GetLocalizedText("localizedUploadHeader")
}
const headers: Headers = new Headers() var fileInputChange = async ($event: Event) => {
const target = $event.target as HTMLInputElement;
if (target && target.files) {
file.value = target.files[0];
console.log(`selected file: ${file.value}`);
}
}
headers.set('Content-Type', 'application/json') // handles sending webrequests to the backend
headers.set('Accept', 'application/json') const getDrivers = async () => {
const request: RequestInfo = new Request("http://localhost:5000/driver", { const headers: Headers = new Headers()
method: "GET",
headers: headers
})
var response = await fetch(request) headers.set('Content-Type', 'application/json')
headers.set('Accept', 'application/json')
// make sure the request was successfull const request: RequestInfo = new Request("http://localhost:5000/driver", {
if (response.ok) { method: "GET",
headers: headers
})
var jsonBody = await response.json() var response = await fetch(request)
// convert vehicles from json response to processable data // make sure the request was successfull
for (let i = 0; i < jsonBody.length; i++) { if (response.ok) {
drivers.value.push({ id: jsonBody[i]["id"], name: jsonBody[i]["name"] })
}
} else {
console.log(await response.text())
}
}
// handles getting all existing drivers var jsonBody = await response.json()
const getVehicles = async () => {
const headers: Headers = new Headers()
headers.set('Content-Type', 'application/json')
headers.set('Accept', 'application/json')
const request: RequestInfo = new Request("http://localhost:5000/vehicle", { // convert vehicles from json response to processable data
method: "GET", for (let i = 0; i < jsonBody.length; i++) {
headers: headers drivers.value.push({ id: jsonBody[i]["id"], name: jsonBody[i]["name"] })
}) }
} else {
console.log(await response.text())
messageType.value = "error";
message.value = `upload failed: ${await response.statusText}`;
}
}
var response = await fetch(request) // handles getting all existing drivers
// make sure the request was successfull const getVehicles = async () => {
if (response.ok) { const headers: Headers = new Headers()
var jsonBody = await response.json() headers.set('Content-Type', 'application/json')
headers.set('Accept', 'application/json')
// convert vehicles from json response to processable data const request: RequestInfo = new Request("http://localhost:5000/vehicle", {
for (let i = 0; i < jsonBody.length; i++) { method: "GET",
vehicles.value.push({ id: jsonBody[i]["id"], name: jsonBody[i]["name"] }) headers: headers
} })
} else {
console.log(await response.text())
}
}
var save = async () => { var response = await fetch(request)
// make sure the request was successfull
if (response.ok) {
var jsonBody = await response.json()
if (!file.value) { // convert vehicles from json response to processable data
alert("Bitte wählen Sie eine Datei aus, bevor Sie sie hochladen."); for (let i = 0; i < jsonBody.length; i++) {
return; vehicles.value.push({ id: jsonBody[i]["id"], name: jsonBody[i]["name"] })
} }
} else {
console.log(await response.text())
messageType.value = "error";
message.value = `upload failed: ${await response.text()}`;
buttonText.value = "Upload";
}
}
const formData = new FormData(); var save = async () => {
formData.append("file", file.value);
formData.append("driverID", selectedDriverID.value.toString());
formData.append("vehicleID", selectedVehicleID.value.toString());
try { if (!file.value) {
messageType.value = "warning";
message.value = "please fill all values";
return;
}
console.log('Request Body:', formData); const formData = new FormData();
formData.append("file", file.value);
formData.append("driverID", selectedDriverID.value.toString());
formData.append("vehicleID", selectedVehicleID.value.toString());
const response = await fetch('http://localhost:5000/upload', { buttonText.value = 'Loading <span class="loading loading-spinner loading-xs"></span>';
method: 'POST',
body: formData,
});
if (response.ok) { try {
const result = await response.json();
emit("response", result);
alert("Datei erfolgreich hochgeladen!");
} else {
console.error('Fehler beim Hochladen:', response.statusText);
alert("Fehler beim Hochladen der Datei.");
}
} catch (error) { console.log('Request Body:', formData);
console.error("upload failed:", error);
alert("failed to upload file.");
}
} const response = await fetch('http://localhost:5000/upload', {
method: 'POST',
body: formData,
});
getVehicles() if (response.ok) {
getDrivers() const result = await response.json();
getLocalization() emit("response", result);
const close = () => { messageType.value = "success";
emit("close"); message.value = `upload successfull`;
}; buttonText.value = "Upload";
} else {
console.error('upload failed: ', await response.statusText);
messageType.value = "error";
message.value = `upload failed: ${await response.statusText}`;
buttonText.value = "Upload";
}
} catch (error) {
messageType.value = "error";
message.value = `upload failed: ${error}`;
buttonText.value = "Upload";
}
return { }
close,
fileInputChange, getVehicles()
selectedDriverID, getDrivers()
selectedVehicleID, getLocalization()
selectedDriverName, const close = () => {
selectedVehicleName, emit("close");
save, };
drivers,
vehicles, return {
localizedUploadHeader close,
}; fileInputChange,
}, selectedDriverID,
selectedVehicleID,
selectedDriverName,
selectedVehicleName,
save,
drivers,
vehicles,
localizedUploadHeader,
buttonText,
messageType,
message
};
},
}); });
</script> </script>
<template> <template>
<div class="card bg-base-100 w-full shadow-xl" style=""> <div class="card bg-base-100 w-full shadow-xl" style="">
<div class="card-body"> <div class="card-body">
<h2 class="card-title">File Upload</h2> <h2 class="card-title">File Upload</h2>
<Message v-if="type=='none'" :type="messageType" :message="message" @close="message=''; messageType='None'"></Message>
<button class="btn btn-error close round" @click="close()"> <button class="btn btn-error close round" @click="close()">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"> <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"
<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z" /> fill="#e8eaed">
</svg> <path
</button> d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z" />
<label class="form-control w-full max-w-xs"> </svg>
<div class="label"> </button>
<span class="label-text">Upload a GPX File</span> <label class="form-control w-full max-w-xs">
</div> <div class="label">
<input type="file" ref="file" v-on:change="fileInputChange($event)" <span class="label-text">Upload a GPX File</span>
class="file-input file-input-bordered w-full max-w-xs" /> </div>
</label> <input type="file" ref="file" v-on:change="fileInputChange($event)"
<div class="dropdown dropdown-bottom"> class="file-input file-input-bordered w-full max-w-xs" />
select Driver: <div tabindex="0" role="button" class="btn m-1"> {{ selectedDriverName }}</div> </label>
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"> <div class="dropdown dropdown-bottom">
<li v-for="driver in drivers"><a v-on:click="selectedDriverID = driver.id; selectedDriverName=driver.name">{{ driver.name }}</a></li> select Driver: <div tabindex="0" role="button" class="btn m-1"> {{ selectedDriverName }}</div>
</ul> <ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
</div> <li v-for="driver in drivers"><a
<div class="dropdown dropdown-bottom"> v-on:click="selectedDriverID = driver.id; selectedDriverName = driver.name">{{ driver.name
select Vehicle: <div tabindex="0" role="button" class="btn m-1"> {{ selectedVehicleName }}</div> }}</a></li>
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"> </ul>
<li v-for="vehicle in vehicles"><a v-on:click="selectedVehicleID = vehicle.id; selectedVehicleName=vehicle.name">{{ vehicle.name }}</a></li> </div>
</ul> <div class="dropdown dropdown-bottom">
</div> select Vehicle: <div tabindex="0" role="button" class="btn m-1"> {{ selectedVehicleName }}</div>
<button class="btn btn-success" v-on:click="save">Upload</button> <ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
</div> <li v-for="vehicle in vehicles"><a
</div> v-on:click="selectedVehicleID = vehicle.id; selectedVehicleName = vehicle.name">{{
vehicle.name }}</a></li>
</ul>
</div>
<button class="btn btn-success" v-on:click="save" v-html="buttonText"></button>
</div>
</div>
</template> </template>
<style scoped> <style scoped>
.settingsBody { .settingsBody {
position: absolute; position: absolute;
margin: 10% 10% 0 10%; margin: 10% 10% 0 10%;
width: 80%; width: 80%;
z-index: 10; z-index: 10;
} }
.btn.close { .btn.close {
position: absolute; position: absolute;
right: 30px; right: 30px;
top: 30px; top: 30px;
margin: 0; margin: 0;
} }
</style> </style>

View File

@ -4,174 +4,203 @@ import "leaflet/dist/leaflet.css";
import L from "leaflet"; import L from "leaflet";
type Track = { type Track = {
id: number id: number
name: string name: string
driver: string driver: string
vehicle: { vehicle: {
name: string name: string
licenseplate: string licenseplate: string
} }
distance: number distance: number
} }
export default defineComponent({ export default defineComponent({
name: 'Map', name: 'Map',
props: { props: {
track: { track: {
type: Number, type: Number,
default: 0, default: 0,
require: true, require: true,
}, },
geoJsonData: { geoJsonData: {
type: Object as PropType<any>, type: Object as PropType<any>,
required: true required: true
}, },
multiple: { multiple: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
}, },
setup(props) { setup(props) {
const mapDiv = ref<HTMLElement | null>(null); // ref to map container element const mapDiv = ref<HTMLElement | null>(null); // ref to map container element
const mapInstance = ref<any>(null); // reference for the Leaflet map const mapInstance = ref<any>(null); // reference for the Leaflet map
const track: Ref<Track> = ref({ id: 0, name: "N/A", driver: "N/A", vehicle: { name: "N/A", licenseplate: "N/A" }, distance: 0.0 }) const track: Ref<Track> = ref({ id: 0, name: "N/A", driver: "N/A", vehicle: { name: "N/A", licenseplate: "N/A" }, distance: 0.0 })
const multiple:Ref<boolean> = ref(props.multiple); const multiple: Ref<boolean> = ref(props.multiple);
const initializeMap = () => { // values for UI Information distribution
if (mapDiv.value) { const messageType: Ref<string> = ref("None");
mapInstance.value = L.map(mapDiv.value, { const message: Ref<string> = ref("");
center: [51.4819, 7.2162],
zoom: 13,
});
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {}).addTo(mapInstance.value); const initializeMap = () => {
} if (mapDiv.value) {
}; mapInstance.value = L.map(mapDiv.value, {
center: [51.4819, 7.2162],
zoom: 13,
});
onMounted(() => { L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {}).addTo(mapInstance.value);
console.log(props.geoJsonData) }
initializeMap(); };
});
const clearMap = () => { onMounted(() => {
if (mapInstance.value) { console.log(props.geoJsonData)
mapInstance.value.eachLayer((layer: any) => { initializeMap();
if (!(layer instanceof L.TileLayer)) { });
mapInstance.value.removeLayer(layer);
}
});
}
};
const getTrackMeta = async (id: number) => { const clearMap = () => {
if (mapInstance.value) {
mapInstance.value.eachLayer((layer: any) => {
if (!(layer instanceof L.TileLayer)) {
mapInstance.value.removeLayer(layer);
}
});
}
};
const headers: Headers = new Headers() const getTrackMeta = async (id: number) => {
headers.set('Content-Type', 'application/json')
headers.set('Accept', 'application/json')
const request: RequestInfo = new Request(`http://localhost:5000/track/meta?id=${id}`, { const headers: Headers = new Headers()
method: "GET", headers.set('Content-Type', 'application/json')
headers: headers headers.set('Accept', 'application/json')
})
var response = await fetch(request) const request: RequestInfo = new Request(`http://localhost:5000/track/meta?id=${id}`, {
// make sure the request was successfull method: "GET",
if (response.ok) { headers: headers
var jsonBody = await response.json() })
console.log(jsonBody)
track.value = {
id: jsonBody["id"],
name: jsonBody["name"],
driver: jsonBody["driver"]["name"],
vehicle: {
name: jsonBody["vehicle"]["name"],
licenseplate: jsonBody["vehicle"]["licenseplate"] ?? "N/A"
},
distance: jsonBody["distance"]
}
console.log(track.value)
}
}
watch(() => props.geoJsonData, (newData) => { var response = await fetch(request)
if (newData) { // make sure the request was successfull
console.log("loading GeoJSON:", newData); if (response.ok) {
var jsonBody = await response.json()
console.log(jsonBody)
track.value = {
id: jsonBody["id"],
name: jsonBody["name"],
driver: jsonBody["driver"]["name"],
vehicle: {
name: jsonBody["vehicle"]["name"],
licenseplate: jsonBody["vehicle"]["licenseplate"] ?? "N/A"
},
distance: jsonBody["distance"]
}
console.log(track.value)
} else {
messageType.value = "error";
message.value = `"failed to load track meta with error: ${await response.statusText}`;
}
}
if (mapInstance.value) { watch(() => props.geoJsonData, (newData) => {
if (!props.multiple){ if (newData) {
clearMap(); console.log("loading GeoJSON:", newData);
}
var geoJsonLayer = L.geoJSON(newData).addTo(mapInstance.value); if (mapInstance.value) {
if (!props.multiple) {
clearMap();
}
// pull meta data for for a single track var geoJsonLayer = L.geoJSON(newData).addTo(mapInstance.value);
// function wont be called if multiple tracks are loaded
if (!props.multiple) {
getTrackMeta(props.track);
}
// move camera view according to loaded track // pull meta data for for a single track
const bounds = geoJsonLayer.getBounds(); // function wont be called if multiple tracks are loaded
mapInstance.value.fitBounds(bounds); if (!props.multiple) {
} else { getTrackMeta(props.track);
console.error("Map or GeoJSON data not available."); }
}
}
});
return { // move camera view according to loaded track
mapDiv, const bounds = geoJsonLayer.getBounds();
multiple, mapInstance.value.fitBounds(bounds);
track } else {
}; console.error("Map or GeoJSON data not available.");
}, messageType.value = "error";
message.value = `"Map or GeoJSON data not available.`;
}
}
});
return {
mapDiv,
multiple,
track,
message,
messageType
};
},
}); });
</script> </script>
<template> <template>
<div ref="mapDiv" style="width: 70vw; height: 75vh; overflow: hidden;"></div> <Message v-if="type == 'none'" :type="messageType" :message="message" @close="message = ''; messageType = 'None'"></Message>
<br> <div ref="mapDiv" style="width: 70vw; height: 75vh; overflow: hidden;"></div>
<div class="overflow-x-auto" style="width: 70vw;" v-if="!multiple"> <br>
<table class="table"> <div class="overflow-x-auto" style="width: 70vw;" v-if="!multiple">
<!-- head --> <table class="table">
<thead> <!-- head -->
<tr> <thead>
<th>track name</th> <tr>
<th>driver</th> <th>track name</th>
<th>vehicle</th> <th>driver</th>
<th>License Plate</th> <th>vehicle</th>
<th>distance</th> <th>License Plate</th>
</tr> <th>distance</th>
</thead> </tr>
<tbody> </thead>
<!-- row 1 --> <tbody>
<tr class="bg-base-200"> <!-- row 1 -->
<th>{{ track.name }}</th> <tr v-if="track.id == 0" class="bg-base-200">
<td>{{ track.driver }}</td> <th>{{ track.name }}</th>
<td>{{ track.vehicle.name }}</td> <td>
<td>{{ track.vehicle.licenseplate }}</td> <div class="skeleton h-4 w-full"></div>
<td>{{ Math.round((track.distance / 1000) * 100) / 100 }}KM</td> </td>
</tr> <td>
</tbody> <div class="skeleton h-4 w-full"></div>
</table> </td>
</div> <td>
<div class="skeleton h-4 w-full"></div>
</td>
<td>
<div class="skeleton h-4 w-full"></div>
</td>
</tr>
<tr v-if="track.id != 0" class="bg-base-200">
<th>{{ track.name }}</th>
<td>{{ track.driver }}</td>
<td>{{ track.vehicle.name }}</td>
<td>{{ track.vehicle.licenseplate }}</td>
<td>{{ Math.round((track.distance / 1000) * 100) / 100 }}KM</td>
</tr>
</tbody>
</table>
</div>
</template> </template>
<style scoped> <style scoped>
#mapDiv { #mapDiv {
width: 100vw; width: 100vw;
height: 70vh; height: 70vh;
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
} }
html, html,
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
</style> </style>

View File

@ -0,0 +1,46 @@
<script lang="ts">
import { defineComponent, SetupContext, Ref, ref } from 'vue';
export default defineComponent({
name: 'Navbar',
emits: ['close'],
props:["type", "message"],
setup(props, { emit }: SetupContext) {
const type:Ref<string> = ref(props.type);
const message:Ref<string> = ref(props.message);
const close = () => {
emit("close");
};
return {
close,
type,
message,
};
},
});
</script>
<template>
<div v-if="type==='info'" role="alert" class="alert alert-info alert-soft">
<span>{{ message }}</span>
<button class="btn btn-soft btn-info" v-on:click="close">Close</button>
</div>
<div v-if="type==='success'" role="alert" class="alert alert-success alert-soft">
<span>{{ message }}</span>
<button class="btn btn-soft btn-success" v-on:click="close">Close</button>
</div>
<div v-if="type==='warning'" role="alert" class="alert alert-warning alert-soft">
<span>{{ message }}</span>
<button class="btn btn-soft btn-warning" v-on:click="close">Close</button>
</div>
<div v-if="type==='error'" role="alert" class="alert error alert-soft">
<span>{{ message }}</span>
<button class="btn btn-soft btn-error" v-on:click="close">Close</button>
</div>
</template>
<style scoped></style>

View File

@ -13,7 +13,7 @@ type Route = {
name: string; name: string;
id: number; id: number;
time: Date; time: Date;
driver: {id:number, name:string} driver: { id: number, name: string }
}; };
@ -21,12 +21,12 @@ export default defineComponent({
emits: ['close', 'response'], emits: ['close', 'response'],
name: 'settings', name: 'settings',
props: { props: {
routes: { routes: {
type: Array as () => Route[], type: Array as () => Route[],
default: () => [], // Standard: leeres Array default: () => [], // Standard: leeres Array
required: true, required: true,
},
}, },
},
setup(props, { emit }: SetupContext) { setup(props, { emit }: SetupContext) {
console.log(props) console.log(props)
if (!props.routes || props.routes.length <= 0) alert("no points to show"); if (!props.routes || props.routes.length <= 0) alert("no points to show");
@ -37,8 +37,12 @@ export default defineComponent({
const router = useRouter(); const router = useRouter();
const legend = ref<Record<string, string>>({}); const legend = ref<Record<string, string>>({});
const driverColors = ref<Record<string, string>>({}); const driverColors = ref<Record<string, string>>({});
var drivers:driver[] = []; var drivers: driver[] = [];
const points:Route[] = props.routes; const points: Route[] = props.routes;
// values for UI Information distribution
const messageType: Ref<string> = ref("None");
const message: Ref<string> = ref("");
// handles sending webrequests to the backend // handles sending webrequests to the backend
const getDrivers = async () => { const getDrivers = async () => {
@ -72,18 +76,20 @@ export default defineComponent({
} }
} else { } else {
console.log(await response.text()) console.log(await response.text())
messageType.value = "error";
message.value = `failed to get drivers with error: ${await response.statusText}`;
} }
} }
// assigns a driver to a point based on the points driver id // assigns a driver to a point based on the points driver id
const assignDriverToPoint = (points:Route[]) => { const assignDriverToPoint = (points: Route[]) => {
return points.reduce((acc, point) => { return points.reduce((acc, point) => {
const driverId = point.driver.id; const driverId = point.driver.id;
console.log(`driverid: ${driverId}`) console.log(`driverid: ${driverId}`)
acc[driverId] = acc[driverId] || []; acc[driverId] = acc[driverId] || [];
acc[driverId].push(point); acc[driverId].push(point);
return acc; return acc;
}, {} as Record<number, Route[]>); }, {} as Record<number, Route[]>);
} }
@ -97,13 +103,13 @@ export default defineComponent({
console.log(`routes: ${props.routes}`) console.log(`routes: ${props.routes}`)
const scene = new THREE.Scene(); const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true, }); const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true, });
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight);
const controls = new OrbitControls(camera, renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement);
const raycaster = new THREE.Raycaster(); const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(); const mouse = new THREE.Vector2();
const groupedPoints = assignDriverToPoint(points) const groupedPoints = assignDriverToPoint(points)
@ -112,11 +118,9 @@ export default defineComponent({
const colors: number[] = []; const colors: number[] = [];
const totalPoints = points.length; const totalPoints = points.length;
const offsetXStep = 2; // Wir setzen den Abstand pro Gruppe const offsetXStep = 2;
let offsetX = 0; let offsetX = 0;
// Berechnungen für den Mittelpunkt der Punktwolke
let sumX = 0, sumY = 0, sumZ = 0; let sumX = 0, sumY = 0, sumZ = 0;
const getColorForDriver = (driverId: number) => { const getColorForDriver = (driverId: number) => {
@ -128,7 +132,12 @@ export default defineComponent({
return color; return color;
}; };
// complicated math things /**
* complicated math things (i am scared of this part)
*
* calculates a points position and ensures that all points grouped by the same driver are placed together
* a point is a uploaded track and during this step it gets applied its color material which was generated whiles getting drivers
*/
Object.entries(groupedPoints).forEach(([driverIdStr, driverPoints], index) => { Object.entries(groupedPoints).forEach(([driverIdStr, driverPoints], index) => {
const driverId = parseInt(driverIdStr, 10); const driverId = parseInt(driverIdStr, 10);
console.log("Driver Colors:", driverColors.value[driverIdStr]); console.log("Driver Colors:", driverColors.value[driverIdStr]);
@ -137,7 +146,7 @@ export default defineComponent({
console.error(`Missing color for Driver ID: ${driverId}`); console.error(`Missing color for Driver ID: ${driverId}`);
} }
const color = new THREE.Color(driverColors.value[driverId]); const color = new THREE.Color(driverColors.value[driverId]);
console.log(`Color for Driver ${driverId}:`, driverColors.value[driverId]); console.log(`Color for Driver ${driverId}:`, driverColors.value[driverId]);
// calculate offset base on driverid // calculate offset base on driverid
@ -158,8 +167,8 @@ export default defineComponent({
colors.push(color.r, color.g, color.b); colors.push(color.r, color.g, color.b);
}); });
}); });
// Mittelpunkt der Punktwolke berechnen // calculate clouds centerpoint
const centerX = sumX / totalPoints; const centerX = sumX / totalPoints;
const centerY = sumY / totalPoints; const centerY = sumY / totalPoints;
const centerZ = sumZ / totalPoints; const centerZ = sumZ / totalPoints;
@ -194,7 +203,10 @@ export default defineComponent({
} }
animate(); animate();
// handles mouse actions /**
* handles mouse events and moves 3D space
* @param event the event triggered when the mouse is moved inside the canvas
*/
const onMouseMove = (event: MouseEvent) => { const onMouseMove = (event: MouseEvent) => {
if (!canvasRef.value || !tooltipRef.value) return; if (!canvasRef.value || !tooltipRef.value) return;
@ -211,22 +223,24 @@ export default defineComponent({
tooltipRef.value.style.display = "block"; tooltipRef.value.style.display = "block";
// 3D-Koordinaten des Punktes holen
/**
* getting coordinates and calculating 2D coordinates from them
* this is done for the tooltip to be positions at the cursor
*/
const positionArray = pointCloud.geometry.attributes.position.array; const positionArray = pointCloud.geometry.attributes.position.array;
const x = positionArray[intersectIndex * 3]; const x = positionArray[intersectIndex * 3];
const y = positionArray[intersectIndex * 3 + 1]; const y = positionArray[intersectIndex * 3 + 1];
const z = positionArray[intersectIndex * 3 + 2]; const z = positionArray[intersectIndex * 3 + 2];
// 3D-Koordinaten in 2D Bildschirmkoordinaten umrechnen
const screenPosition = new THREE.Vector3(x, y, z); const screenPosition = new THREE.Vector3(x, y, z);
screenPosition.project(camera); screenPosition.project(camera);
// Umrechnung von NDC (Normalized Device Coordinates) zu Pixeln
const tooltipX = (screenPosition.x * 0.5 + 0.5) * window.innerWidth; const tooltipX = (screenPosition.x * 0.5 + 0.5) * window.innerWidth;
const tooltipY = (screenPosition.y * -0.5 + 0.5) * window.innerHeight; const tooltipY = (screenPosition.y * -0.5 + 0.5) * window.innerHeight;
tooltipRef.value.style.left = `${tooltipX + 10}px`; // 10px Abstand zum Punkt tooltipRef.value.style.left = `${tooltipX + 10}px`;
tooltipRef.value.style.top = `${tooltipY + 10}px`; // 10px Abstand zum Punkt tooltipRef.value.style.top = `${tooltipY + 10}px`;
tooltipText.value = `${point.driver.name}: ${point.name} (${point.time.toLocaleString()})`; tooltipText.value = `${point.driver.name}: ${point.name} (${point.time.toLocaleString()})`;
} else { } else {
@ -250,6 +264,8 @@ export default defineComponent({
tooltipText, tooltipText,
legend, legend,
driverColors, driverColors,
message,
messageType
}; };
}, },
}); });
@ -257,6 +273,7 @@ export default defineComponent({
<template> <template>
<Message v-if="type == 'none'" :type="messageType" :message="message" @close="message = ''; messageType = 'None'"></Message>
<div class="pointcloud-container relative"> <div class="pointcloud-container relative">
<canvas ref="canvasRef"></canvas> <canvas ref="canvasRef"></canvas>
<div ref="tooltipRef" class="tooltip hidden fixed z-50 px-2 py-1 bg-base-200 text-base-content rounded"> <div ref="tooltipRef" class="tooltip hidden fixed z-50 px-2 py-1 bg-base-200 text-base-content rounded">
@ -264,7 +281,7 @@ export default defineComponent({
</div> </div>
<!-- Legende --> <!-- Legende -->
<div class="legend absolute top-10 left-10 bg-base-200 p-4 rounded shadow-lg z-50"> <div class="legend absolute top-10 left-10 bg-base-200 p-4 rounded shadow-lg z-50">
<h3 class="font-bold mb-2">Fahrer Legende</h3> <h3 class="font-bold mb-2">Drivers</h3>
<ul> <ul>
<li v-for="(driverName, driverId) in legend" :key="driverId" class="flex items-center mb-1"> <li v-for="(driverName, driverId) in legend" :key="driverId" class="flex items-center mb-1">
<div class="w-4 h-4" :style="{ backgroundColor: driverColors[driverId] }"></div> <div class="w-4 h-4" :style="{ backgroundColor: driverColors[driverId] }"></div>
@ -276,52 +293,52 @@ export default defineComponent({
</template> </template>
<style scoped> <style scoped>
.pointcloud-container { .pointcloud-container {
width: 100%; width: 100%;
height: calc(90vh); height: calc(90vh);
overflow: hidden; overflow: hidden;
} }
canvas { canvas {
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.tooltip { .tooltip {
display: none; display: none;
position: absolute; position: absolute;
z-index: 50; z-index: 50;
padding: 2px 8px; padding: 2px 8px;
background-color: rgba(0, 0, 0, 0.7); background-color: rgba(0, 0, 0, 0.7);
color: white; color: white;
border-radius: 4px; border-radius: 4px;
pointer-events: none; pointer-events: none;
} }
.legend { .legend {
width: auto; width: auto;
max-width: 250px; max-width: 250px;
background-color: rgba(0, 0, 0, 0.7); background-color: rgba(0, 0, 0, 0.7);
color: white; color: white;
padding: 10px; padding: 10px;
border-radius: 8px; border-radius: 8px;
font-size: 14px; font-size: 14px;
} }
.legend ul { .legend ul {
padding: 0; padding: 0;
list-style-type: none; list-style-type: none;
} }
.legend li { .legend li {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.legend .color-box { .legend .color-box {
width: 15px; width: 15px;
height: 15px; height: 15px;
margin-right: 10px; margin-right: 10px;
} }
</style> </style>

View File

@ -13,6 +13,11 @@ export default defineComponent({
const driverName: Ref<string> = ref("") const driverName: Ref<string> = ref("")
const driverList: Ref<driver[]> = ref([]) const driverList: Ref<driver[]> = ref([])
// values for UI Information distribution
const messageType: Ref<string> = ref("");
const message: Ref<string> = ref("");
// handles sending webrequests to the backend // handles sending webrequests to the backend
const getDrivers = async () => { const getDrivers = async () => {
@ -39,6 +44,8 @@ export default defineComponent({
} }
} else { } else {
console.log(await response.text()) console.log(await response.text())
messageType.value = "error";
message.value = `upload failed: ${response.text()}`;
} }
} }
@ -64,6 +71,8 @@ export default defineComponent({
driverList.value.push({ id: jsonBody["body"], name: driverName.value }) driverList.value.push({ id: jsonBody["body"], name: driverName.value })
} else { } else {
console.log(await response.text()) console.log(await response.text())
messageType.value = "error";
message.value = `upload failed: ${response.text()}`;
} }
} }
@ -71,7 +80,9 @@ export default defineComponent({
return { return {
createDriver, createDriver,
driverName, driverName,
driverList driverList,
message,
messageType
}; };
}, },
}); });
@ -79,6 +90,7 @@ export default defineComponent({
<template> <template>
<Message v-if="type == 'none'" :type="messageType" :message="message" @close="message = ''; messageType = 'None'"></Message>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table">
<!-- head --> <!-- head -->
@ -92,9 +104,18 @@ export default defineComponent({
<tbody> <tbody>
<tr> <tr>
<td></td> <td></td>
<td><input type="text" placeholder="Driver Name" class="input input-bordered w-full max-w-xs" <td><input type="text" placeholder="Driver Name" class="input input-bordered w-full" v-model="driverName" />
v-model="driverName" /></td> </td>
<td><a class="btn btn-success" v-on:click="createDriver">Create Driver</a></td> <td><a class="btn btn-success w-full" v-on:click="createDriver">Create Driver</a></td>
</tr>
<tr v-if="driverList.length == 0">
<th>
<div class="skeleton h-4 w-full"></div>
</th>
<td>
<div class="skeleton h-4 w-full"></div>
</td>
<td><a class="btn btn-error w-full" v-on:click="createVehicle">Delete Vehicle</a></td>
</tr> </tr>
<tr v-for="driver in driverList"> <tr v-for="driver in driverList">
<th>{{ driver.id }}</th> <th>{{ driver.id }}</th>

View File

@ -5,6 +5,7 @@ import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css' import '@vuepic/vue-datepicker/dist/main.css'
import Map from '../components/map.vue'; import Map from '../components/map.vue';
import FileUpload from '../components/fileUpload.vue'; import FileUpload from '../components/fileUpload.vue';
import Message from '../components/message.vue';
type DriverType = { type DriverType = {
id: number id: number
@ -37,7 +38,7 @@ type GeoJSON = {
export default defineComponent({ export default defineComponent({
name: 'map', name: 'map',
components: { PointCloud, VueDatePicker, Map, FileUpload }, components: { PointCloud, VueDatePicker, Map, FileUpload, Message },
setup(_, { emit }: SetupContext) { setup(_, { emit }: SetupContext) {
const showMap: Ref<boolean> = ref(false); const showMap: Ref<boolean> = ref(false);
const showCloud: Ref<boolean> = ref(false); const showCloud: Ref<boolean> = ref(false);
@ -52,6 +53,10 @@ export default defineComponent({
const multipleTracks: Ref<Boolean> = ref(false); const multipleTracks: Ref<Boolean> = ref(false);
const trackid: Ref<number> = ref(0) const trackid: Ref<number> = ref(0)
// values for UI Information distribution
const messageType: Ref<string> = ref("");
const message: Ref<string> = ref("");
const loadTrack = async (id: number) => { const loadTrack = async (id: number) => {
showMap.value = true; showMap.value = true;
showCloud.value = false; showCloud.value = false;
@ -75,12 +80,15 @@ export default defineComponent({
if (response.ok) { if (response.ok) {
// Wenn die Antwort OK ist, die Daten verarbeiten // Wenn die Antwort OK ist, die Daten verarbeiten
mapData.value = await response.json(); mapData.value = await response.json();
console.log("GeoJSON-Daten erfolgreich geladen", mapData.value);
} else { } else {
console.log(await response.text()); console.log(await response.text());
messageType.value = "error";
message.value = `loading tracks failed: ${await response.text()}`;
} }
} catch (error) { } catch (error) {
console.error("Fehler beim Laden der Track-Daten:", error); console.error("loading tracks failed with error:", error);
messageType.value = "error";
message.value = `loading tracks failed with error: ${error}`;
} }
}; };
@ -106,6 +114,8 @@ export default defineComponent({
} }
} else { } else {
console.log(await response.text()) console.log(await response.text())
messageType.value = "error";
message.value = `loading track list failed with error: ${error}`;
} }
} }
@ -121,7 +131,8 @@ export default defineComponent({
multipleTracks.value = false; multipleTracks.value = false;
if (startSearchDate.value == null || endSearchDate.value == null) { if (startSearchDate.value == null || endSearchDate.value == null) {
alert("please give all required infos") messageType.value = "error";
message.value = "please give all required infos";
return; return;
} }
@ -155,6 +166,8 @@ export default defineComponent({
} else { } else {
console.log(await response.text()) console.log(await response.text())
messageType.value = "error";
message.value = `searching tracks failed: ${await response.text()}`;
} }
if (renderSearchOnMap.value) { if (renderSearchOnMap.value) {
@ -188,6 +201,8 @@ export default defineComponent({
showUpload, showUpload,
multipleTracks, multipleTracks,
trackid, trackid,
message,
messageType
}; };
}, },
}); });
@ -195,6 +210,7 @@ export default defineComponent({
<template> <template>
<Message v-if="type == 'none'" :type="messageType" :message="message" @close="message = ''; messageType = 'None'"></Message>
<FileUpload v-if="showUpload" @close="showUpload = false;" :drivers="[]" <FileUpload v-if="showUpload" @close="showUpload = false;" :drivers="[]"
style="position:absolute; top: 30VH; width: 80%;"></FileUpload> style="position:absolute; top: 30VH; width: 80%;"></FileUpload>
<div class="grid grid-flow-col auto-cols-max gap-4"> <div class="grid grid-flow-col auto-cols-max gap-4">
@ -207,6 +223,9 @@ export default defineComponent({
<li> <li>
<div class="divider"></div> <div class="divider"></div>
</li> </li>
<li v-if="tracks.length == 0">
<div class="skeleton h-4 w-full"></div>
</li>
<li v-for="track in tracks"> <a v-on:click="loadTrack(track.id); multipleTracks = false;"> {{ track.name }} </a> <li v-for="track in tracks"> <a v-on:click="loadTrack(track.id); multipleTracks = false;"> {{ track.name }} </a>
</li> </li>
</ul> </ul>
@ -217,11 +236,11 @@ export default defineComponent({
<div v-if="!showMap && !showCloud && search" style="margin-left: 20%; display:flex;"> <div v-if="!showMap && !showCloud && search" style="margin-left: 20%; display:flex;">
<div> <div>
start time start time
<input class="datepicker" type="date" id="birthday" name="birthday" v-model="startSearchDate"> <input class="datepicker" type="date" id="startdate" name="startdate" v-model="startSearchDate">
</div> </div>
<div style="margin-left: 5%;"> <div style="margin-left: 5%;">
end time end time
<input class="datepicker" type="date" id="birthday" name="birthday" v-model="endSearchDate"> <input class="datepicker" type="date" id="enddate" name="enddate" v-model="endSearchDate">
</div> </div>
<div style="margin-left: 5%;"> <div style="margin-left: 5%;">
show on map: show on map:

View File

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import {defineComponent, Ref, ref, SetupContext} from 'vue'; import { defineComponent, Ref, ref, SetupContext } from 'vue';
type vehicle = { type vehicle = {
id:number id: number
name:string name: string
licenseplate: string licenseplate: string
} }
@ -11,9 +11,13 @@ export default defineComponent({
name: 'map', name: 'map',
setup(_, { emit }: SetupContext) { setup(_, { emit }: SetupContext) {
const vehicleName:Ref<string> = ref("") const vehicleName: Ref<string> = ref("")
const licensePlate:Ref<string> = ref("") const licensePlate: Ref<string> = ref("")
const vehicleList:Ref<vehicle[]> = ref([]) const vehicleList: Ref<vehicle[]> = ref([])
// values for UI Information distribution
const messageType: Ref<string> = ref("");
const message: Ref<string> = ref("");
// handles getting all existing drivers // handles getting all existing drivers
const getVehicles = async () => { const getVehicles = async () => {
@ -22,27 +26,29 @@ export default defineComponent({
headers.set('Accept', 'application/json') headers.set('Accept', 'application/json')
const request: RequestInfo = new Request("http://localhost:5000/vehicle", { const request: RequestInfo = new Request("http://localhost:5000/vehicle", {
method:"GET", method: "GET",
headers:headers headers: headers
}) })
var response = await fetch(request) var response = await fetch(request)
// make sure the request was successfull // make sure the request was successfull
if (response.ok){ if (response.ok) {
var jsonBody = await response.json() var jsonBody = await response.json()
// convert vehicles from json response to processable data // convert vehicles from json response to processable data
for(let i = 0; i < jsonBody.length; i++) { for (let i = 0; i < jsonBody.length; i++) {
let plate = "N/A" let plate = "N/A"
let vehicle = jsonBody[i] let vehicle = jsonBody[i]
if (vehicle["licensePlate"] != undefined) { if (vehicle["licensePlate"] != undefined) {
plate = vehicle["licensePlate"]; plate = vehicle["licensePlate"];
} }
vehicleList.value.push({id: vehicle["id"], name: vehicle["name"], licenseplate: plate}) vehicleList.value.push({ id: vehicle["id"], name: vehicle["name"], licenseplate: plate })
} }
} else { } else {
console.log(await response.text()) console.log(await response.text())
messageType.value = "error";
message.value = `loading vehicles failed with error: ${await response.text()}`;
} }
} }
@ -56,27 +62,31 @@ export default defineComponent({
const requestBody = JSON.stringify({ name: vehicleName.value, licensePlate: licensePlate.value }); const requestBody = JSON.stringify({ name: vehicleName.value, licensePlate: licensePlate.value });
const request: RequestInfo = new Request("http://localhost:5000/vehicle", { const request: RequestInfo = new Request("http://localhost:5000/vehicle", {
method:"POST", method: "POST",
headers:headers, headers: headers,
body: requestBody body: requestBody
}) })
var response = await fetch(request) var response = await fetch(request)
// make sure the request was successfull // make sure the request was successfull
if (response.ok){ if (response.ok) {
var jsonBody = await response.json() var jsonBody = await response.json()
vehicleList.value.push({id: jsonBody["body"], name: vehicleName.value, licenseplate: "N/A"}) vehicleList.value.push({ id: jsonBody["body"], name: vehicleName.value, licenseplate: "N/A" })
} else { } else {
console.log(await response.text()) console.log(await response.text())
messageType.value = "error";
message.value = `creating vehicle failed with error: ${error}`;
} }
} }
getVehicles() getVehicles()
return { return {
createVehicle, createVehicle,
vehicleName, vehicleName,
licensePlate, licensePlate,
vehicleList vehicleList,
message,
messageType
}; };
}, },
}); });
@ -84,6 +94,7 @@ export default defineComponent({
<template> <template>
<Message v-if="type == 'none'" :type="messageType" :message="message" @close="message = ''; messageType = 'None'"></Message>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table">
<!-- head --> <!-- head -->
@ -97,20 +108,34 @@ export default defineComponent({
<tbody> <tbody>
<tr> <tr>
<td></td> <td></td>
<td><input type="text" placeholder="Vehicle Name" class="input input-bordered w-full max-w-xs" v-model="vehicleName"/></td> <td><input type="text" placeholder="Vehicle Name" class="input input-bordered w-full "
<td><input type="text" placeholder="License Plate" class="input input-bordered w-full max-w-xs" v-model="licensePlate"/></td> v-model="vehicleName" /></td>
<td><a class="btn btn-success" v-on:click="createVehicle">Create Vehicle</a></td> <td><input type="text" placeholder="License Plate" class="input input-bordered w-full "
v-model="licensePlate" /></td>
<td><a class="btn btn-success w-full" v-on:click="createVehicle">Create Vehicle</a></td>
</tr>
<tr v-if="vehicleList.length == 0">
<th>
<div class="skeleton h-4 w-full"></div>
</th>
<td>
<div class="skeleton h-4 w-full"></div>
</td>
<td>
<div class="skeleton h-4 w-full"></div>
</td>
<td><a class="btn btn-error w-full" v-on:click="createVehicle">Delete Vehicle</a></td>
<td></td>
</tr> </tr>
<tr v-for="vehicle in vehicleList"> <tr v-for="vehicle in vehicleList">
<th>{{ vehicle.id }}</th> <th>{{ vehicle.id }}</th>
<td>{{ vehicle.name }}</td> <td>{{ vehicle.name }}</td>
<td>{{ vehicle.licenseplate }}</td> <td>{{ vehicle.licenseplate }}</td>
<td></td> <td><a class="btn btn-error w-full" v-on:click="createVehicle">Delete Vehicle</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@ -1 +1 @@
{"root":["./theme-manager.config.ts","./vite.config.ts","./src/app.vue","./src/leatflet.d.ts","./src/main.ts","./src/vite-env.d.ts","./src/classes/debugger.ts","./src/classes/language.ts","./src/components/settings.vue","./src/components/cookieprompt.vue","./src/components/fileupload.vue","./src/components/map.vue","./src/components/navbar.vue","./src/components/pointcloud.vue","./src/views/driver.vue","./src/views/home.vue","./src/views/login.vue","./src/views/register.vue","./src/views/route.vue","./src/views/settings.vue","./src/views/vehicle.vue"],"version":"5.6.2"} {"root":["./theme-manager.config.ts","./vite.config.ts","./src/app.vue","./src/leatflet.d.ts","./src/main.ts","./src/vite-env.d.ts","./src/classes/debugger.ts","./src/classes/language.ts","./src/components/settings.vue","./src/components/cookieprompt.vue","./src/components/fileupload.vue","./src/components/map.vue","./src/components/navbar.vue","./src/components/pointcloud.vue","./src/views/driver.vue","./src/views/home.vue","./src/views/login.vue","./src/views/register.vue","./src/views/route.vue","./src/views/settings.vue","./src/views/vehicle.vue"],"errors":true,"version":"5.6.2"}