Add reputation actions feature to Falukant module

- Introduced new endpoints for retrieving and executing reputation actions in FalukantController and falukantRouter.
- Implemented service methods in FalukantService to handle reputation actions, including daily limits and action execution logic.
- Updated the frontend ReputationView component to display available actions and their details, including cost and potential reputation gain.
- Added translations for reputation actions in both German and English locales.
- Enhanced initialization logic to set up reputation action types in the database.
This commit is contained in:
Torsten Schulz (local)
2025-12-21 21:09:31 +01:00
parent 38f23cc6ae
commit 38dd51f757
13 changed files with 594 additions and 2 deletions

View File

@@ -142,6 +142,44 @@
</table>
</div>
</div>
<div v-else-if="activeTab === 'actions'">
<p>
{{ $t('falukant.reputation.actions.description') }}
</p>
<p v-if="reputationActionsDailyCap != null" class="reputation-actions-daily">
{{ $t('falukant.reputation.actions.dailyLimit', { remaining: reputationActionsDailyRemaining, cap: reputationActionsDailyCap }) }}
</p>
<table v-if="reputationActions.length">
<thead>
<tr>
<th>{{ $t('falukant.reputation.actions.action') }}</th>
<th>{{ $t('falukant.reputation.actions.cost') }}</th>
<th>{{ $t('falukant.reputation.actions.gain') }}</th>
<th>{{ $t('falukant.reputation.actions.timesUsed') }}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="a in reputationActions" :key="a.id">
<td>{{ $t('falukant.reputation.actions.type.' + a.tr) }}</td>
<td>{{ Number(a.cost || 0).toLocaleString($i18n.locale) }}</td>
<td>+{{ Number(a.currentGain || 0) }}</td>
<td>{{ Number(a.timesUsed || 0) }}</td>
<td>
<button type="button" :disabled="runningActionId === a.id"
@click.prevent="executeReputationAction(a)">
{{ runningActionId === a.id ? $t('falukant.reputation.actions.running') : $t('falukant.reputation.actions.execute') }}
</button>
</td>
</tr>
</tbody>
</table>
<p v-else>
{{ $t('falukant.reputation.actions.none') }}
</p>
</div>
</div>
</div>
</template>
@@ -159,7 +197,8 @@ export default {
activeTab: 'overview',
tabs: [
{ value: 'overview', label: 'falukant.reputation.overview.title' },
{ value: 'party', label: 'falukant.reputation.party.title' }
{ value: 'party', label: 'falukant.reputation.party.title' },
{ value: 'actions', label: 'falukant.reputation.actions.title' }
],
newPartyView: false,
newPartyTypeId: null,
@@ -174,6 +213,11 @@ export default {
inProgressParties: [],
completedParties: [],
reputation: null,
reputationActions: [],
reputationActionsDailyCap: null,
reputationActionsDailyUsed: null,
reputationActionsDailyRemaining: null,
runningActionId: null,
}
},
methods: {
@@ -211,6 +255,41 @@ export default {
this.reputation = null;
}
},
async loadReputationActions() {
try {
const { data } = await apiClient.get('/api/falukant/reputation/actions');
this.reputationActionsDailyCap = data?.dailyCap ?? null;
this.reputationActionsDailyUsed = data?.dailyUsed ?? null;
this.reputationActionsDailyRemaining = data?.dailyRemaining ?? null;
this.reputationActions = Array.isArray(data?.actions) ? data.actions : [];
} catch (e) {
console.error('Failed to load reputation actions', e);
this.reputationActions = [];
this.reputationActionsDailyCap = null;
this.reputationActionsDailyUsed = null;
this.reputationActionsDailyRemaining = null;
}
},
async executeReputationAction(action) {
if (!action?.id) return;
if (this.runningActionId) return;
this.runningActionId = action.id;
try {
const { data } = await apiClient.post('/api/falukant/reputation/actions', { actionTypeId: action.id });
const gain = data?.gain ?? null;
const cost = data?.cost ?? null;
const msg = gain != null
? this.$t('falukant.reputation.actions.success', { gain, cost })
: this.$t('falukant.reputation.actions.successSimple');
this.$root.$refs.messageDialog?.open(this.$t('falukant.reputation.actions.title'), msg);
await Promise.all([this.loadReputation(), this.loadReputationActions()]);
} catch (e) {
const errText = e?.response?.data?.error || e?.message || String(e);
this.$root.$refs.messageDialog?.open(this.$t('falukant.reputation.actions.title'), errText);
} finally {
this.runningActionId = null;
}
},
async loadNobilityTitles() {
this.nobilityTitles = await apiClient.get('/api/falukant/nobility/titels').then(r => r.data)
},
@@ -256,13 +335,14 @@ export default {
},
async mounted() {
const tabFromQuery = this.$route?.query?.tab;
if (['overview','party'].includes(tabFromQuery)) {
if (['overview','party','actions'].includes(tabFromQuery)) {
this.activeTab = tabFromQuery;
}
await this.loadPartyTypes();
await this.loadNobilityTitles();
await this.loadParties();
await this.loadReputation();
await this.loadReputationActions();
}
}
</script>
@@ -323,4 +403,9 @@ table th {
border-top: 1px solid #ccc;
margin-top: 1em;
}
.reputation-actions-daily {
margin: 0.5rem 0 1rem;
font-weight: bold;
}
</style>