Some fixes and additions

This commit is contained in:
Torsten Schulz
2025-07-09 14:28:35 +02:00
parent 5029be81e9
commit fceea5b7fb
32 changed files with 4373 additions and 1294 deletions

View File

@@ -0,0 +1,342 @@
<template>
<DialogWidget
ref="dialog"
name="create-branch"
:title="$t('falukant.branch.actions.create')"
icon="branch.png"
showClose
:buttons="dialogButtons"
@close="onClose"
>
<div class="create-branch-form">
<div class="map-wrapper">
<!-- linke Spalte: Karte + Regionen + Dev-Draw -->
<div class="map-container">
<img
ref="mapImage"
src="/images/falukant/map.png"
class="map"
@mousedown="onMouseDown"
@mousemove="onMouseMove"
@mouseup="onMouseUp"
@mouseleave="onMouseUp"
@dragstart.prevent
/>
<div
v-for="city in cities"
:key="city.name"
class="city-region"
:class="city.branches.length > 0 ? 'has-branch' : 'clickable'"
:style="{
top: city.map.y + 'px',
left: city.map.x + 'px',
width: city.map.w + 'px',
height: city.map.h + 'px'
}"
@click="city.branches.length === 0 && onCityClick(city)"
:title="city.name"
></div>
<div
v-if="devMode && rect"
class="dev-rect"
:style="{
top: rect.y + 'px',
left: rect.x + 'px',
width: rect.width + 'px',
height: rect.height + 'px'
}"
></div>
</div>
<!-- rechte Spalte: Dev-Info + Auswahl -->
<div class="sidebar">
<div v-if="devMode" class="dev-info">
<span class="dev-badge">DEV MODE</span>
<span v-if="rect" class="dev-label-outside">
{{ rect.x }},{{ rect.y }} {{ rect.width }}×{{ rect.height }}
</span>
</div>
<div v-if="selectedRegion" class="selected-region-wrapper">
<div class="selected-region">
{{ $t('falukant.branch.selection.selected') }}:
<strong>{{ selectedRegion.name }}</strong>
</div>
<label class="form-label">
{{ $t('falukant.branch.columns.type') }}
<select v-model="selectedType" class="form-control">
<option
v-for="type in branchTypes"
:key="type.id"
:value="type.id"
>
{{ $t(`falukant.branch.types.${type.labelTr}`) }}
({{ formatCost(computeBranchCost(type)) }})
</option>
</select>
</label>
</div>
</div>
</div>
</div>
</DialogWidget>
</template>
<script>
import DialogWidget from '@/components/DialogWidget.vue';
import apiClient from '@/utils/axios.js';
export default {
name: 'CreateBranchDialog',
components: { DialogWidget },
data() {
return {
cities: [],
branchTypes: [],
selectedRegion: null,
selectedType: null,
devMode: false,
rect: null,
startX: null,
startY: null,
currentX: 0,
currentY: 0,
};
},
computed: {
dialogButtons() {
return [
{ text: this.$t('Cancel'), action: this.close },
{ text: this.$t('falukant.branch.actions.create'), action: this.onConfirm },
];
},
},
async mounted() {
window.addEventListener('keydown', this.onKeyDown);
await Promise.all([
this.loadCities(),
this.loadBranchTypes(),
]);
this.selectedType = this.branchTypes.length ? this.branchTypes[0].id : null;
},
beforeDestroy() {
window.removeEventListener('keydown', this.onKeyDown);
},
methods: {
open() {
this.$refs.dialog.open();
},
close() {
this.$refs.dialog.close();
},
async onConfirm() {
if (!this.selectedRegion || !this.selectedType) return;
try {
await apiClient.post('/api/falukant/branches', {
cityId: this.selectedRegion.id,
branchTypeId: this.selectedType,
});
this.$emit('create-branch');
this.close();
} catch (e) {
}
},
onClose() {
this.close();
this.$emit('close');
},
onKeyDown(e) {
if (e.ctrlKey && e.altKey && e.code === 'KeyD') {
this.devMode = !this.devMode;
if (!this.devMode) this.rect = null;
}
},
onMouseDown(e) {
if (!this.devMode) return;
const bounds = this.$refs.mapImage.getBoundingClientRect();
this.startX = e.clientX - bounds.left;
this.startY = e.clientY - bounds.top;
this.currentX = this.startX;
this.currentY = this.startY;
this.updateRect();
e.preventDefault();
},
onMouseMove(e) {
if (!this.devMode || this.startX === null) return;
const bounds = this.$refs.mapImage.getBoundingClientRect();
this.currentX = e.clientX - bounds.left;
this.currentY = e.clientY - bounds.top;
this.updateRect();
},
onMouseUp() {
if (!this.devMode) return;
this.startX = null;
this.startY = null;
},
updateRect() {
if (this.startX === null || this.startY === null) return;
const x = Math.min(this.startX, this.currentX);
const y = Math.min(this.startY, this.currentY);
const width = Math.abs(this.currentX - this.startX);
const height = Math.abs(this.currentY - this.startY);
this.rect = {
x: Math.round(x),
y: Math.round(y),
width: Math.round(width),
height: Math.round(height),
};
},
async loadCities() {
const { data } = await apiClient.get('/api/falukant/cities');
this.cities = data;
},
onCityClick(city) {
this.selectedRegion = city;
},
async loadBranchTypes() {
const { data } = await apiClient.get('/api/falukant/branches/types');
this.branchTypes = data;
},
computeBranchCost(type) {
const total = this.cities.reduce((sum, city) => sum + city.branches.length, 0);
const factor = Math.pow(Math.max(total, 1), 1.2);
const raw = type.baseCost * factor;
return Math.round(raw * 100) / 100;
},
formatCost(value) {
return new Intl.NumberFormat(navigator.language, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(value);
},
},
};
</script>
<style scoped>
.create-branch-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.map-wrapper {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.map-container {
position: relative;
width: fit-content;
}
.map {
max-width: 500px;
max-height: 400px;
user-select: none;
cursor: crosshair;
}
.city-region {
position: absolute;
}
.city-region.clickable {
cursor: pointer;
background: rgba(0, 0, 255, 0.2);
}
.city-region.has-branch {
cursor: default;
background: transparent;
border: 2px solid #00ff00;
}
.dev-rect {
position: absolute;
border: 2px dashed red;
pointer-events: none;
}
.sidebar {
display: flex;
flex-direction: column;
gap: 1rem;
min-width: 200px;
}
.dev-info {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dev-badge {
background: rgba(255, 0, 0, 0.7);
color: white;
padding: 2px 6px;
font-size: 0.75rem;
border-radius: 3px;
pointer-events: none;
}
.dev-label-outside {
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 0.75rem;
padding: 2px 4px;
border-radius: 2px;
pointer-events: none;
}
.selected-region-wrapper {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.selected-region {
font-weight: 600;
}
.form-label {
font-weight: 600;
margin-bottom: 0.25rem;
}
.form-control {
padding: 0.5rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}
</style>