Files
yourpart3/frontend/src/views/falukant/ReputationView.vue
Torsten Schulz (local) 4ba9e498a7 Änderung: Verbesserung der Benutzeroberfläche und Logik in ReputationView.vue
Änderungen:
- Anpassung der Schaltflächen zur Verhinderung von Standardaktionen mit `@click.prevent`.
- Einführung einer neuen Methode `getPartyDate`, um das Datum der Partys korrekt zu berechnen.
- Aktualisierung der Filterlogik in `loadParties`, um Partys der letzten 24 Stunden anzuzeigen.

Diese Anpassungen verbessern die Benutzerinteraktion und die Datumsverarbeitung in der Anwendung.
2025-09-25 14:34:00 +02:00

308 lines
12 KiB
Vue

<template>
<div class="reputation-view">
<StatusBar />
<h2>{{ $t('falukant.reputation.title') }}</h2>
<div class="simple-tabs">
<button v-for="tab in tabs" :key="tab.value" :class="['simple-tab', { active: activeTab === tab.value }]"
@click="activeTab = tab.value">
{{ $t(tab.label) }}
</button>
</div>
<div class="tab-content">
<div v-if="activeTab === 'overview'">
<p>Deine aktuelle Reputation: </p>
</div>
<div v-else-if="activeTab === 'party'">
<button @click.prevent="toggleNewPartyView()" type="button">
{{ $t('falukant.reputation.party.newpartyview.' + (newPartyView ? 'close' : 'open')) }}
</button>
<div v-if="newPartyView" class="new-party-form">
<label>
{{ $t('falukant.reputation.party.newpartyview.type') }}:
<select v-model.number="newPartyTypeId">
<option v-for="type in partyTypes" :key="type.id" :value="type.id">
{{ $t('falukant.party.type.' + type.tr) }}
</option>
</select>
</label>
<div v-if="newPartyTypeId" class="party-options">
<label>
{{ $t('falukant.reputation.party.music.label') }}:
<select v-model.number="musicId">
<option v-for="m in musicTypes" :key="m.id" :value="m.id">
{{ $t(`falukant.reputation.party.music.${m.tr}`) }}
</option>
</select>
</label>
<label>
{{ $t('falukant.reputation.party.banquette.label') }}:
<select v-model.number="banquetteId">
<option v-for="b in banquetteTypes" :key="b.id" :value="b.id">
{{ $t(`falukant.reputation.party.banquette.${b.tr}`) }}
</option>
</select>
</label>
<label>
{{ $t('falukant.reputation.party.servants.label') }}:
<input type="number" v-model.number="servantRatio" min="1" max="50" />
{{ $t('falukant.reputation.party.servants.perPersons') }}
</label>
<label>
{{ $t('falukant.reputation.party.esteemedInvites.label') }}:
<multiselect v-model="selectedNobilityIds" :options="nobilityTitles" :multiple="true"
track-by="id" label="labelTr" :close-on-select="false" :preserve-search="true"
placeholder="">
<template #option="{ option }">
{{ $t('falukant.titles.male.' + option.labelTr) }}
</template>
<template #tag="{ option, remove }">
<span class="multiselect__tag">
{{ $t('falukant.titles.male.' + option.labelTr) }}
<i @click="remove(option.id)" class="multiselect__tag-icon"></i>
</span>
</template>
</multiselect>
</label>
<p class="total-cost">
{{ $t('falukant.reputation.party.totalCost') }}:
{{ formattedCost }}
</p>
</div>
<div>
<button type="button" @click="orderParty()">
{{ $t('falukant.reputation.party.order') }}
</button>
</div>
</div>
<!-- In-Progress Parties -->
<div class="separator-class">
<h3>{{ $t('falukant.reputation.party.inProgress') }}</h3>
<table>
<thead>
<tr>
<th>{{ $t('falukant.reputation.party.type') }}</th>
<th>{{ $t('falukant.reputation.party.music.label') }}</th>
<th>{{ $t('falukant.reputation.party.banquette.label') }}</th>
<th>{{ $t('falukant.reputation.party.servants.label') }}</th>
<th>{{ $t('falukant.reputation.party.cost') }}</th>
<th>{{ $t('falukant.reputation.party.date') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="party in inProgressParties" :key="party.id">
<td>{{ $t('falukant.party.type.' + party.partyType.tr) }}</td>
<td>{{ $t('falukant.reputation.party.music.' + party.musicType.tr) }}</td>
<td>{{ $t('falukant.reputation.party.banquette.' + party.banquetteType.tr) }}</td>
<td>{{ party.servantRatio }}</td>
<td>{{ party.cost.toLocaleString($i18n.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</td>
<td>{{ getPartyDate(party.createdAt).toLocaleString() }}</td>
</tr>
</tbody>
</table>
</div>
<!-- Completed Parties -->
<div class="separator-class">
<h3>{{ $t('falukant.reputation.party.completed') }}</h3>
<table>
<thead>
<tr>
<th>{{ $t('falukant.reputation.party.type') }}</th>
<th>{{ $t('falukant.reputation.party.music.label') }}</th>
<th>{{ $t('falukant.reputation.party.banquette.label') }}</th>
<th>{{ $t('falukant.reputation.party.servants.label') }}</th>
<th>{{ $t('falukant.reputation.party.cost') }}</th>
<th>{{ $t('falukant.reputation.party.date') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="party in completedParties" :key="party.id">
<td>{{ $t('falukant.party.type.' + party.partyType.tr) }}</td>
<td>{{ $t('falukant.reputation.party.music.' + party.musicType.tr) }}</td>
<td>{{ $t('falukant.reputation.party.banquette.' + party.banquetteType.tr) }}</td>
<td>{{ party.servantRatio }}</td>
<td>{{ party.cost.toLocaleString($i18n.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</td>
<td>{{ getPartyDate(party.createdAt).toLocaleString() }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<script>
import StatusBar from '@/components/falukant/StatusBar.vue'
import apiClient from '@/utils/axios.js'
import Multiselect from 'vue-multiselect'
export default {
name: 'ReputationView',
components: { StatusBar, Multiselect },
data() {
return {
activeTab: 'overview',
tabs: [
{ value: 'overview', label: 'falukant.reputation.overview.title' },
{ value: 'party', label: 'falukant.reputation.party.title' }
],
newPartyView: false,
newPartyTypeId: null,
partyTypes: [],
musicId: null,
musicTypes: [],
banquetteId: null,
banquetteTypes: [],
nobilityTitles: [],
selectedNobilityIds: [],
servantRatio: 50,
inProgressParties: [],
completedParties: []
}
},
methods: {
toggleNewPartyView() {
this.newPartyView = !this.newPartyView
},
async loadPartyTypes() {
const { data } = await apiClient.get('/api/falukant/party/types');
this.partyTypes = data.partyTypes;
this.musicTypes = data.musicTypes;
this.banquetteTypes = data.banquetteTypes;
this.musicId = this.musicTypes[0]?.id;
this.banquetteId = this.banquetteTypes[0]?.id;
},
async loadParties() {
const { data } = await apiClient.get('/api/falukant/party');
const now = new Date();
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
this.inProgressParties = data.filter(party => {
const partyDate = this.getPartyDate(party.createdAt);
return partyDate > twentyFourHoursAgo;
});
this.completedParties = data.filter(party => {
const partyDate = this.getPartyDate(party.createdAt);
return partyDate <= twentyFourHoursAgo;
});
},
async loadNobilityTitles() {
this.nobilityTitles = await apiClient.get('/api/falukant/nobility/titels').then(r => r.data)
},
async orderParty() {
await apiClient.post('/api/falukant/party', {
partyTypeId: this.newPartyTypeId,
musicId: this.musicId,
banquetteId: this.banquetteId,
nobilityIds: this.selectedNobilityIds.map(n => n.id ?? n),
servantRatio: this.servantRatio
});
this.toggleNewPartyView();
},
getPartyDate(createdAt) {
// Feste finden 1 Tag nach der Bestellung statt
const partyDate = new Date(createdAt);
partyDate.setDate(partyDate.getDate() + 1);
return partyDate;
}
},
computed: {
formattedCost() {
const type = this.partyTypes.find(t => t.id === this.newPartyTypeId) || {};
const music = this.musicTypes.find(m => m.id === this.musicId) || {};
const banquette = this.banquetteTypes.find(b => b.id === this.banquetteId) || {};
let cost = (type.cost || 0) + (music.cost || 0) + (banquette.cost || 0);
cost += (50 / this.servantRatio - 1) * 1000;
let nobilityCost = this.selectedNobilityIds.reduce((sum, id) => {
const nob = this.nobilityTitles.find(n => n.id === id)
return sum + ((nob?.id ^ 5) * 1000)
}, 0);
cost += nobilityCost;
const locale = this.$i18n?.locale || 'de-DE';
return cost.toLocaleString(locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
},
async mounted() {
const tabFromQuery = this.$route?.query?.tab;
if (['overview','party'].includes(tabFromQuery)) {
this.activeTab = tabFromQuery;
}
await this.loadPartyTypes();
await this.loadNobilityTitles();
await this.loadParties();
}
}
</script>
<style scoped>
h2 {
padding-top: 20px;
}
.simple-tabs {
display: flex;
margin-top: 1rem;
}
.simple-tab {
padding: 0.5rem 1rem;
background: #fff;
border: none;
cursor: pointer;
transition: background 0.2s;
}
.simple-tab.active {
background: #F9A22C;
color: #000;
}
.tab-content {
margin-top: 1rem;
}
.new-party-form {
margin-top: 0.5rem;
}
.party-options {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
}
.total-cost {
font-weight: bold;
margin-top: 1rem;
}
.multiselect {
display: inline-block !important;
vertical-align: middle;
}
table th {
text-align: left;
}
.separator-class {
border-top: 1px solid #ccc;
margin-top: 1em;
}
</style>