Add NPC creation status tracking and progress reporting in Admin module

- Implemented getNPCsCreationStatus method in AdminController to retrieve the status of NPC creation jobs.
- Enhanced AdminService to manage NPC creation jobs, including job ID generation, progress updates, and error handling.
- Updated frontend CreateNPCView to display progress of NPC creation, including estimated time remaining and job status.
- Added localization strings for progress reporting in both German and English.
- Improved overall user experience by providing real-time feedback during NPC creation processes.
This commit is contained in:
Torsten Schulz (local)
2026-01-07 17:09:54 +01:00
parent b34dcac685
commit c322eb1e5a
8 changed files with 347 additions and 25 deletions

View File

@@ -1,6 +1,8 @@
<template>
<main>
<router-view></router-view>
<main class="contenthidden">
<div class="contentscroll">
<router-view></router-view>
</div>
</main>
</template>
@@ -12,9 +14,13 @@
<style scoped>
main {
padding: 20px;
padding: 0;
background-color: #ffffff;
flex: 1;
}
.contentscroll {
padding: 20px;
}
</style>

View File

@@ -135,7 +135,13 @@
"errorCreating": "Fehler beim Erstellen der NPCs.",
"invalidAgeRange": "Ungültiger Altersbereich.",
"invalidTitleRange": "Ungültiger Titel-Bereich.",
"invalidCount": "Ungültige Anzahl (1-500)."
"invalidCount": "Ungültige Anzahl (1-500).",
"progress": "Fortschritt",
"progressDetails": "{current} von {total} NPCs erstellt",
"timeRemainingSeconds": "Verbleibende Zeit: {seconds} Sekunden",
"timeRemainingMinutes": "Verbleibende Zeit: {minutes} Minuten {seconds} Sekunden",
"almostDone": "Fast fertig...",
"jobNotFound": "Job nicht gefunden oder abgelaufen."
}
},
"chatrooms": {

View File

@@ -162,7 +162,13 @@
"errorCreating": "Error creating NPCs.",
"invalidAgeRange": "Invalid age range.",
"invalidTitleRange": "Invalid title range.",
"invalidCount": "Invalid count (1-500)."
"invalidCount": "Invalid count (1-500).",
"progress": "Progress",
"progressDetails": "{current} of {total} NPCs created",
"timeRemainingSeconds": "Time remaining: {seconds} seconds",
"timeRemainingMinutes": "Time remaining: {minutes} minutes {seconds} seconds",
"almostDone": "Almost done...",
"jobNotFound": "Job not found or expired."
}
},
"chatrooms": {

View File

@@ -58,6 +58,26 @@
{{ creating ? $t('admin.falukant.createNPC.creating') : $t('admin.falukant.createNPC.create') }}
</button>
</div>
<!-- Fortschrittsanzeige -->
<div v-if="creating && jobStatus" class="progress-section">
<div class="progress-header">
<h3>{{ $t('admin.falukant.createNPC.progress') }}</h3>
<span class="progress-percentage">{{ jobStatus.progress }}%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" :style="{ width: jobStatus.progress + '%' }"></div>
</div>
<div class="progress-details">
<div>{{ $t('admin.falukant.createNPC.progressDetails', {
current: jobStatus.current || 0,
total: jobStatus.total || 0
}) }}</div>
<div v-if="jobStatus.estimatedTimeRemaining" class="time-remaining">
{{ formatTimeRemaining(jobStatus.estimatedTimeRemaining) }}
</div>
</div>
</div>
</div>
<!-- Ergebnis-Anzeige -->
@@ -105,9 +125,17 @@ export default {
count: 1,
creating: false,
result: null,
error: null
error: null,
jobId: null,
jobStatus: null,
statusPollInterval: null
};
},
beforeUnmount() {
if (this.statusPollInterval) {
clearInterval(this.statusPollInterval);
}
},
async mounted() {
await this.loadRegions();
await this.loadTitles();
@@ -162,6 +190,8 @@ export default {
this.creating = true;
this.error = null;
this.result = null;
this.jobStatus = null;
this.jobId = null;
try {
const response = await apiClient.post('/api/admin/falukant/npcs/create', {
@@ -173,13 +203,62 @@ export default {
count: this.count
});
this.result = response.data;
this.jobId = response.data.jobId;
this.startStatusPolling();
} catch (error) {
console.error('Error creating NPCs:', error);
this.error = error.response?.data?.error || this.$t('admin.falukant.createNPC.errorCreating');
} finally {
this.creating = false;
}
},
startStatusPolling() {
if (this.statusPollInterval) {
clearInterval(this.statusPollInterval);
}
this.statusPollInterval = setInterval(async () => {
if (!this.jobId) return;
try {
const response = await apiClient.get(`/api/admin/falukant/npcs/status/${this.jobId}`);
this.jobStatus = response.data;
if (this.jobStatus.status === 'completed') {
this.result = this.jobStatus.result;
this.creating = false;
clearInterval(this.statusPollInterval);
this.statusPollInterval = null;
} else if (this.jobStatus.status === 'error') {
this.error = this.jobStatus.error || this.$t('admin.falukant.createNPC.errorCreating');
this.creating = false;
clearInterval(this.statusPollInterval);
this.statusPollInterval = null;
}
} catch (error) {
console.error('Error polling status:', error);
if (error.response?.status === 404) {
// Job nicht gefunden - möglicherweise abgelaufen
this.error = this.$t('admin.falukant.createNPC.jobNotFound');
this.creating = false;
clearInterval(this.statusPollInterval);
this.statusPollInterval = null;
}
}
}, 1000); // Poll alle Sekunde
},
formatTimeRemaining(ms) {
if (!ms || ms <= 0) return this.$t('admin.falukant.createNPC.almostDone');
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (minutes > 0) {
return this.$t('admin.falukant.createNPC.timeRemainingMinutes', {
minutes,
seconds: remainingSeconds
});
}
return this.$t('admin.falukant.createNPC.timeRemainingSeconds', { seconds });
}
}
};
@@ -311,4 +390,58 @@ export default {
color: #155724;
margin-top: 5px;
}
.progress-section {
background: #e7f3ff;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid #b3d9ff;
}
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.progress-header h3 {
margin: 0;
font-size: 1.2em;
}
.progress-percentage {
font-size: 1.5em;
font-weight: bold;
color: #0066cc;
}
.progress-bar-container {
width: 100%;
height: 30px;
background-color: #e0e0e0;
border-radius: 15px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #28a745, #20c997);
transition: width 0.3s ease;
border-radius: 15px;
}
.progress-details {
display: flex;
justify-content: space-between;
font-size: 0.9em;
color: #666;
}
.time-remaining {
font-weight: bold;
color: #0066cc;
}
</style>