Add NPC creation and titles retrieval functionality in Admin module

- Implemented createNPCs method in AdminController to handle NPC creation with specified parameters including region, age, title, and count.
- Added getTitlesOfNobility method in AdminController to retrieve available titles for users.
- Updated adminRouter to include new routes for creating NPCs and fetching titles.
- Enhanced navigationController and frontend localization files to support new NPC creation feature.
- Introduced corresponding UI components and routes for NPC management in the admin interface.
This commit is contained in:
Torsten Schulz (local)
2026-01-07 16:45:39 +01:00
parent 511df52c3c
commit bb91c2bbe5
11 changed files with 549 additions and 19 deletions

View File

@@ -113,6 +113,27 @@
"errorSaveConnection": "Die Verbindung konnte nicht gespeichert werden.",
"errorDeleteConnection": "Die Verbindung konnte nicht gelöscht werden.",
"confirmDeleteConnection": "Verbindung wirklich löschen?"
},
"createNPC": {
"title": "NPCs erstellen",
"region": "Stadt",
"allRegions": "Alle Städte",
"ageRange": "Altersbereich",
"to": "bis",
"years": "Jahre",
"titleRange": "Titel-Bereich",
"count": "Anzahl",
"create": "NPCs erstellen",
"creating": "Erstelle...",
"result": "Ergebnis",
"createdCount": "{count} NPCs wurden erstellt.",
"age": "Alter",
"errorLoadingRegions": "Fehler beim Laden der Städte.",
"errorLoadingTitles": "Fehler beim Laden der Titel.",
"errorCreating": "Fehler beim Erstellen der NPCs.",
"invalidAgeRange": "Ungültiger Altersbereich.",
"invalidTitleRange": "Ungültiger Titel-Bereich.",
"invalidCount": "Ungültige Anzahl (1-100)."
}
},
"chatrooms": {

View File

@@ -62,7 +62,8 @@
"logentries": "Log-Einträge",
"edituser": "Benutzer bearbeiten",
"database": "Datenbank",
"mapEditor": "Karteneditor"
"mapEditor": "Karteneditor",
"createNPC": "NPCs erstellen"
},
"minigames": "Minispiele",
"m-minigames": {

View File

@@ -140,6 +140,27 @@
"errorAddingStock": "Error adding warehouse.",
"stockAdded": "Warehouse successfully added.",
"invalidStockData": "Please enter valid warehouse type and quantity."
},
"createNPC": {
"title": "Create NPCs",
"region": "City",
"allRegions": "All Cities",
"ageRange": "Age Range",
"to": "to",
"years": "years",
"titleRange": "Title Range",
"count": "Count",
"create": "Create NPCs",
"creating": "Creating...",
"result": "Result",
"createdCount": "{count} NPCs have been created.",
"age": "Age",
"errorLoadingRegions": "Error loading cities.",
"errorLoadingTitles": "Error loading titles.",
"errorCreating": "Error creating NPCs.",
"invalidAgeRange": "Invalid age range.",
"invalidTitleRange": "Invalid title range.",
"invalidCount": "Invalid count (1-100)."
}
},
"chatrooms": {

View File

@@ -62,7 +62,8 @@
"logentries": "Log entries",
"edituser": "Edit user",
"database": "Database",
"mapEditor": "Map editor"
"mapEditor": "Map editor",
"createNPC": "Create NPCs"
},
"minigames": "Mini games",
"m-minigames": {

View File

@@ -5,6 +5,7 @@ import UserRightsView from '../views/admin/UserRightsView.vue';
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
import AdminFalukantEditUserView from '../views/admin/falukant/EditUserView.vue';
import AdminFalukantMapRegionsView from '../views/admin/falukant/MapRegionsView.vue';
import AdminFalukantCreateNPCView from '../views/admin/falukant/CreateNPCView.vue';
import AdminMinigamesView from '../views/admin/MinigamesView.vue';
import AdminTaxiToolsView from '../views/admin/TaxiToolsView.vue';
import AdminUsersView from '../views/admin/UsersView.vue';
@@ -66,6 +67,12 @@ const adminRoutes = [
component: AdminFalukantMapRegionsView,
meta: { requiresAuth: true }
},
{
path: '/admin/falukant/create-npc',
name: 'AdminFalukantCreateNPCView',
component: AdminFalukantCreateNPCView,
meta: { requiresAuth: true }
},
{
path: '/admin/minigames/match3',
name: 'AdminMinigames',

View File

@@ -0,0 +1,289 @@
<template>
<div class="create-npc-view">
<h1>{{ $t('admin.falukant.createNPC.title') }}</h1>
<div class="form-section">
<div class="form-group">
<label>{{ $t('admin.falukant.createNPC.region') }}:</label>
<div class="region-selection">
<label>
<input type="checkbox" v-model="allRegions" @change="onAllRegionsChange" />
{{ $t('admin.falukant.createNPC.allRegions') }}
</label>
<select v-model="selectedRegionIds" multiple :disabled="allRegions" class="form-select" size="10">
<option v-for="region in regions" :key="region.id" :value="region.id">
{{ region.name }}
</option>
</select>
</div>
</div>
<div class="form-group">
<label>{{ $t('admin.falukant.createNPC.ageRange') }}:</label>
<div class="age-range">
<input type="number" v-model.number="minAge" min="0" max="100" class="form-input" />
<span>{{ $t('admin.falukant.createNPC.to') }}</span>
<input type="number" v-model.number="maxAge" min="0" max="100" class="form-input" />
<span>{{ $t('admin.falukant.createNPC.years') }}</span>
</div>
</div>
<div class="form-group">
<label>{{ $t('admin.falukant.createNPC.titleRange') }}:</label>
<div class="title-range">
<select v-model.number="minTitleId" class="form-select">
<option v-for="title in titles" :key="title.id" :value="title.id">
{{ $t(`falukant.titles.male.${title.labelTr}`) }} (ID: {{ title.id }})
</option>
</select>
<span>{{ $t('admin.falukant.createNPC.to') }}</span>
<select v-model.number="maxTitleId" class="form-select">
<option v-for="title in titles" :key="title.id" :value="title.id">
{{ $t(`falukant.titles.male.${title.labelTr}`) }} (ID: {{ title.id }})
</option>
</select>
</div>
</div>
<div class="form-group">
<label>{{ $t('admin.falukant.createNPC.count') }}:</label>
<input type="number" v-model.number="count" min="1" max="100" class="form-input" />
</div>
<div class="action-buttons">
<button @click="createNPCs" :disabled="creating" class="btn btn-primary">
{{ creating ? $t('admin.falukant.createNPC.creating') : $t('admin.falukant.createNPC.create') }}
</button>
</div>
</div>
<!-- Ergebnis-Anzeige -->
<div v-if="result" class="result-section">
<h2>{{ $t('admin.falukant.createNPC.result') }}</h2>
<p>{{ $t('admin.falukant.createNPC.createdCount', { count: result.count }) }}</p>
<div v-if="result.npcs && result.npcs.length > 0" class="npcs-list">
<div v-for="npc in result.npcs" :key="npc.id" class="npc-item">
{{ $t(`falukant.titles.${npc.gender}.${npc.title}`) }} {{ npc.firstName }} {{ npc.lastName }}
({{ $t('admin.falukant.createNPC.age') }}: {{ npc.age }}, {{ $t('admin.falukant.createNPC.region') }}: {{ npc.region }})
</div>
</div>
</div>
<div v-if="error" class="error-message">
{{ error }}
</div>
</div>
</template>
<script>
import apiClient from '@/utils/axios.js';
export default {
name: 'AdminFalukantCreateNPCView',
data() {
return {
regions: [],
titles: [],
selectedRegionIds: [],
allRegions: true,
minAge: 0,
maxAge: 100,
minTitleId: 1,
maxTitleId: 19,
count: 1,
creating: false,
result: null,
error: null
};
},
async mounted() {
await this.loadRegions();
await this.loadTitles();
},
methods: {
async loadRegions() {
try {
const response = await apiClient.get('/api/admin/falukant/regions');
this.regions = response.data || [];
} catch (error) {
console.error('Error loading regions:', error);
this.error = this.$t('admin.falukant.createNPC.errorLoadingRegions');
}
},
async loadTitles() {
try {
const response = await apiClient.get('/api/admin/falukant/titles');
this.titles = response.data || [];
if (this.titles.length > 0) {
this.minTitleId = this.titles[0].id;
this.maxTitleId = this.titles[this.titles.length - 1].id;
}
} catch (error) {
console.error('Error loading titles:', error);
this.error = this.$t('admin.falukant.createNPC.errorLoadingTitles');
}
},
onAllRegionsChange() {
if (this.allRegions) {
this.selectedRegionIds = [];
}
},
async createNPCs() {
if (this.creating) return;
// Validierung
if (this.minAge < 0 || this.maxAge < 0 || this.minAge > this.maxAge) {
this.error = this.$t('admin.falukant.createNPC.invalidAgeRange');
return;
}
if (this.minTitleId > this.maxTitleId) {
this.error = this.$t('admin.falukant.createNPC.invalidTitleRange');
return;
}
if (this.count < 1 || this.count > 100) {
this.error = this.$t('admin.falukant.createNPC.invalidCount');
return;
}
this.creating = true;
this.error = null;
this.result = null;
try {
const response = await apiClient.post('/api/admin/falukant/npcs/create', {
regionIds: this.allRegions ? null : this.selectedRegionIds,
minAge: this.minAge,
maxAge: this.maxAge,
minTitleId: this.minTitleId,
maxTitleId: this.maxTitleId,
count: this.count
});
this.result = response.data;
} catch (error) {
console.error('Error creating NPCs:', error);
this.error = error.response?.data?.error || this.$t('admin.falukant.createNPC.errorCreating');
} finally {
this.creating = false;
}
}
}
};
</script>
<style scoped>
.create-npc-view {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.form-section {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
.region-selection {
display: flex;
flex-direction: column;
gap: 10px;
}
.region-selection label {
display: flex;
align-items: center;
gap: 8px;
font-weight: normal;
}
.form-select {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.form-select[multiple] {
min-height: 200px;
}
.age-range,
.title-range {
display: flex;
align-items: center;
gap: 10px;
}
.form-input {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
width: 100px;
}
.action-buttons {
margin-top: 20px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn-primary {
background-color: #28a745;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #218838;
}
.btn-primary:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.result-section {
background: #d4edda;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
}
.npcs-list {
margin-top: 15px;
}
.npc-item {
padding: 8px;
background: white;
margin-bottom: 5px;
border-radius: 4px;
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
}
</style>