feat(admin): implement pregnancy and birth management features
Some checks failed
Deploy to production / deploy (push) Failing after 2m6s
Some checks failed
Deploy to production / deploy (push) Failing after 2m6s
- Added new admin functionalities to force pregnancy, clear pregnancy, and trigger birth for characters. - Introduced corresponding routes and controller methods in adminRouter and adminController. - Enhanced the FalukantCharacter model to include pregnancy-related fields. - Created database migration for adding pregnancy columns to the character table. - Updated frontend views and internationalization files to support new pregnancy and birth management features. - Improved user feedback and error handling for these new actions.
This commit is contained in:
@@ -168,7 +168,40 @@
|
||||
"errorLoadingStockTypes": "Fehler beim Laden der Lagertypen.",
|
||||
"errorAddingStock": "Fehler beim Hinzufügen des Lagers.",
|
||||
"stockAdded": "Lager erfolgreich hinzugefügt.",
|
||||
"invalidStockData": "Bitte gültige Lagertyp- und Mengenangaben eingeben."
|
||||
"invalidStockData": "Bitte gültige Lagertyp- und Mengenangaben eingeben.",
|
||||
"pregnancy": {
|
||||
"title": "Schwangerschaft (Admin)",
|
||||
"characterId": "Charakter-ID",
|
||||
"status": "Status",
|
||||
"statusActive": "Schwanger bis",
|
||||
"statusNone": "Nicht schwanger",
|
||||
"fatherId": "Vater-Charakter-ID (optional)",
|
||||
"dueDays": "Tage bis zum Termin",
|
||||
"force": "Schwangerschaft setzen",
|
||||
"clear": "Schwangerschaft entfernen",
|
||||
"successForce": "Schwangerschaft wurde gesetzt.",
|
||||
"successClear": "Schwangerschaft wurde entfernt.",
|
||||
"error": "Aktion fehlgeschlagen."
|
||||
},
|
||||
"birth": {
|
||||
"title": "Geburt erzwingen (Admin)",
|
||||
"motherHint": "Es wird der oben genannte Charakter (Mutter) verwendet.",
|
||||
"fatherId": "Vater-Charakter-ID",
|
||||
"context": "Kontext",
|
||||
"contextMarriage": "Ehe",
|
||||
"contextLover": "Liebschaft",
|
||||
"legitimacy": "Legitimität",
|
||||
"legitimate": "Legitim",
|
||||
"ackBastard": "Anerkannt unehelich",
|
||||
"hiddenBastard": "Verborgen unehelich",
|
||||
"gender": "Kind-Geschlecht",
|
||||
"genderRandom": "Zufällig",
|
||||
"male": "Männlich",
|
||||
"female": "Weiblich",
|
||||
"force": "Geburt auslösen",
|
||||
"success": "Kind wurde angelegt (Taufe ausstehend).",
|
||||
"error": "Geburt konnte nicht ausgelöst werden."
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"title": "Falukant Karten-Editor (Regionen)",
|
||||
|
||||
@@ -540,6 +540,10 @@
|
||||
"familyWarning": "Anhaltender Kreditverzug belastet Ehe, Haushalt und Liebschaften.",
|
||||
"familyImpact": "Der Schuldturm schadet Ehe, Hausfrieden und der Stabilität von Liebschaften."
|
||||
},
|
||||
"pregnancy": {
|
||||
"banner": "Du erwartest ein Kind.",
|
||||
"dueHint": "Voraussichtlicher Geburtstermin"
|
||||
},
|
||||
"spouse": {
|
||||
"title": "Beziehung",
|
||||
"name": "Name",
|
||||
|
||||
@@ -223,7 +223,40 @@
|
||||
"errorLoadingStockTypes": "Error loading warehouse types.",
|
||||
"errorAddingStock": "Error adding warehouse.",
|
||||
"stockAdded": "Warehouse successfully added.",
|
||||
"invalidStockData": "Please enter valid warehouse type and quantity."
|
||||
"invalidStockData": "Please enter valid warehouse type and quantity.",
|
||||
"pregnancy": {
|
||||
"title": "Pregnancy (admin)",
|
||||
"characterId": "Character ID",
|
||||
"status": "Status",
|
||||
"statusActive": "Expecting until",
|
||||
"statusNone": "Not pregnant",
|
||||
"fatherId": "Father character ID (optional)",
|
||||
"dueDays": "Days until due date",
|
||||
"force": "Set pregnancy",
|
||||
"clear": "Clear pregnancy",
|
||||
"successForce": "Pregnancy has been set.",
|
||||
"successClear": "Pregnancy has been cleared.",
|
||||
"error": "Action failed."
|
||||
},
|
||||
"birth": {
|
||||
"title": "Force birth (admin)",
|
||||
"motherHint": "The character listed above is used as the mother.",
|
||||
"fatherId": "Father character ID",
|
||||
"context": "Context",
|
||||
"contextMarriage": "Marriage",
|
||||
"contextLover": "Affair",
|
||||
"legitimacy": "Legitimacy",
|
||||
"legitimate": "Legitimate",
|
||||
"ackBastard": "Acknowledged bastard",
|
||||
"hiddenBastard": "Hidden bastard",
|
||||
"gender": "Child gender",
|
||||
"genderRandom": "Random",
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"force": "Trigger birth",
|
||||
"success": "Child created (baptism pending).",
|
||||
"error": "Could not trigger birth."
|
||||
}
|
||||
},
|
||||
"createNPC": {
|
||||
"title": "Create NPCs",
|
||||
|
||||
@@ -611,6 +611,10 @@
|
||||
"familyWarning": "Ongoing debt delinquency puts strain on marriage, household and affairs.",
|
||||
"familyImpact": "Debtors' prison damages marriage, household peace and the stability of affairs."
|
||||
},
|
||||
"pregnancy": {
|
||||
"banner": "You are expecting a child.",
|
||||
"dueHint": "Expected due date"
|
||||
},
|
||||
"children": {
|
||||
"title": "Children",
|
||||
"name": "Name",
|
||||
|
||||
@@ -168,7 +168,40 @@
|
||||
"errorLoadingStockTypes": "Error al cargar los tipos de almacén.",
|
||||
"errorAddingStock": "Error al añadir el almacén.",
|
||||
"stockAdded": "Almacén añadido correctamente.",
|
||||
"invalidStockData": "Por favor, introduce un tipo de almacén y una cantidad válidos."
|
||||
"invalidStockData": "Por favor, introduce un tipo de almacén y una cantidad válidos.",
|
||||
"pregnancy": {
|
||||
"title": "Embarazo (admin)",
|
||||
"characterId": "ID de personaje",
|
||||
"status": "Estado",
|
||||
"statusActive": "Embarazo hasta",
|
||||
"statusNone": "No embarazada",
|
||||
"fatherId": "ID del padre (opcional)",
|
||||
"dueDays": "Días hasta el parto previsto",
|
||||
"force": "Establecer embarazo",
|
||||
"clear": "Quitar embarazo",
|
||||
"successForce": "Embarazo establecido.",
|
||||
"successClear": "Embarazo eliminado.",
|
||||
"error": "La acción ha fallado."
|
||||
},
|
||||
"birth": {
|
||||
"title": "Forzar nacimiento (admin)",
|
||||
"motherHint": "Se usa el personaje indicado arriba como madre.",
|
||||
"fatherId": "ID del padre",
|
||||
"context": "Contexto",
|
||||
"contextMarriage": "Matrimonio",
|
||||
"contextLover": "Amante",
|
||||
"legitimacy": "Legitimidad",
|
||||
"legitimate": "Legítimo",
|
||||
"ackBastard": "Bastardo reconocido",
|
||||
"hiddenBastard": "Bastardo oculto",
|
||||
"gender": "Sexo del niño",
|
||||
"genderRandom": "Aleatorio",
|
||||
"male": "Masculino",
|
||||
"female": "Femenino",
|
||||
"force": "Provocar nacimiento",
|
||||
"success": "Niño creado (bautizo pendiente).",
|
||||
"error": "No se pudo provocar el nacimiento."
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"title": "Editor de mapas de Falukant (regiones)",
|
||||
|
||||
@@ -521,6 +521,10 @@
|
||||
"familyWarning": "La mora continuada perjudica el matrimonio, el hogar y las relaciones.",
|
||||
"familyImpact": "La prisión por deudas daña el matrimonio, la paz del hogar y la estabilidad de las relaciones."
|
||||
},
|
||||
"pregnancy": {
|
||||
"banner": "Esperas un hijo.",
|
||||
"dueHint": "Fecha prevista de nacimiento"
|
||||
},
|
||||
"spouse": {
|
||||
"title": "Relación",
|
||||
"name": "Nombre",
|
||||
|
||||
@@ -39,6 +39,67 @@
|
||||
<option v-for="house in houses" :value="house.id">{{ $t(`falukant.house.type.${house.labelTr}`) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="admin-family-tools">
|
||||
<h4>{{ $t('admin.falukant.edituser.pregnancy.title') }}</h4>
|
||||
<p class="admin-family-tools__meta">
|
||||
{{ $t('admin.falukant.edituser.pregnancy.characterId') }}:
|
||||
<code>{{ editableUser.falukantData[0].character.id }}</code>
|
||||
</p>
|
||||
<p class="admin-family-tools__meta">
|
||||
{{ $t('admin.falukant.edituser.pregnancy.status') }}:
|
||||
<template v-if="pregnancyDueDisplay">
|
||||
{{ $t('admin.falukant.edituser.pregnancy.statusActive') }} {{ pregnancyDueDisplay }}
|
||||
</template>
|
||||
<template v-else>{{ $t('admin.falukant.edituser.pregnancy.statusNone') }}</template>
|
||||
</p>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.pregnancy.fatherId') }}
|
||||
<input type="number" v-model.number="adminPregnancyFatherId" min="1" placeholder="—" />
|
||||
</label>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.pregnancy.dueDays') }}
|
||||
<input type="number" v-model.number="adminDueInDays" min="1" max="365" />
|
||||
</label>
|
||||
<div class="action-buttons">
|
||||
<button type="button" @click="adminForcePregnancy">{{ $t('admin.falukant.edituser.pregnancy.force') }}</button>
|
||||
<button type="button" class="button-secondary" @click="adminClearPregnancy">{{ $t('admin.falukant.edituser.pregnancy.clear') }}</button>
|
||||
</div>
|
||||
|
||||
<h4>{{ $t('admin.falukant.edituser.birth.title') }}</h4>
|
||||
<p class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.birth.motherHint') }}</p>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.birth.fatherId') }} *
|
||||
<input type="number" v-model.number="adminBirthFatherId" min="1" required />
|
||||
</label>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.birth.context') }}
|
||||
<select v-model="adminBirthContext">
|
||||
<option value="marriage">{{ $t('admin.falukant.edituser.birth.contextMarriage') }}</option>
|
||||
<option value="lover">{{ $t('admin.falukant.edituser.birth.contextLover') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.birth.legitimacy') }}
|
||||
<select v-model="adminBirthLegitimacy">
|
||||
<option value="legitimate">{{ $t('admin.falukant.edituser.birth.legitimate') }}</option>
|
||||
<option value="acknowledged_bastard">{{ $t('admin.falukant.edituser.birth.ackBastard') }}</option>
|
||||
<option value="hidden_bastard">{{ $t('admin.falukant.edituser.birth.hiddenBastard') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.birth.gender') }}
|
||||
<select v-model="adminBirthGender">
|
||||
<option value="">{{ $t('admin.falukant.edituser.birth.genderRandom') }}</option>
|
||||
<option value="male">{{ $t('admin.falukant.edituser.birth.male') }}</option>
|
||||
<option value="female">{{ $t('admin.falukant.edituser.birth.female') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="action-buttons">
|
||||
<button type="button" @click="adminForceBirth">{{ $t('admin.falukant.edituser.birth.force') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button @click="saveUser" :disabled="!hasUserChanges">{{ $t('common.save') }}</button>
|
||||
<button @click="deleteUser" class="button-secondary">{{ $t('common.delete') }}</button>
|
||||
@@ -158,7 +219,13 @@ export default {
|
||||
loading: {
|
||||
branches: false,
|
||||
stockTypes: false
|
||||
}
|
||||
},
|
||||
adminPregnancyFatherId: null,
|
||||
adminDueInDays: 21,
|
||||
adminBirthFatherId: null,
|
||||
adminBirthContext: 'marriage',
|
||||
adminBirthLegitimacy: 'legitimate',
|
||||
adminBirthGender: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -172,6 +239,20 @@ export default {
|
||||
|| this.editableUser.falukantData[0].character.title_of_nobility != this.originalUser.falukantData[0].character.title_of_nobility
|
||||
|| this.originalAge != this.age;
|
||||
},
|
||||
pregnancyDueDisplay() {
|
||||
const c = this.editableUser?.falukantData?.[0]?.character;
|
||||
if (!c) return null;
|
||||
const raw = c.pregnancyDueAt ?? c.pregnancy_due_at;
|
||||
if (!raw) return null;
|
||||
try {
|
||||
return new Date(raw).toLocaleString(this.$i18n?.locale || undefined, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short'
|
||||
});
|
||||
} catch (_) {
|
||||
return String(raw);
|
||||
}
|
||||
},
|
||||
availableStockTypes() {
|
||||
if (!this.newStock.branchId || !this.stockTypes.length) {
|
||||
return this.stockTypes;
|
||||
@@ -343,6 +424,59 @@ export default {
|
||||
!existingStockTypeIds.includes(stockType.id)
|
||||
);
|
||||
return availableStockTypes.length > 0;
|
||||
},
|
||||
async refreshEditableUser() {
|
||||
if (!this.editableUser?.hashedId) return;
|
||||
const userResult = await apiClient.get(`/api/admin/falukant/getuser/${this.editableUser.hashedId}`);
|
||||
this.editableUser = userResult.data;
|
||||
this.originalUser = JSON.parse(JSON.stringify(this.editableUser));
|
||||
this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000));
|
||||
this.originalAge = this.age;
|
||||
},
|
||||
async adminForcePregnancy() {
|
||||
const characterId = this.editableUser.falukantData[0].character.id;
|
||||
const payload = { characterId, dueInDays: Number(this.adminDueInDays) || 21 };
|
||||
if (this.adminPregnancyFatherId) payload.fatherCharacterId = Number(this.adminPregnancyFatherId);
|
||||
try {
|
||||
await apiClient.post('/api/admin/falukant/character/force-pregnancy', payload);
|
||||
showSuccess(this, 'tr:admin.falukant.edituser.pregnancy.successForce');
|
||||
await this.refreshEditableUser();
|
||||
} catch (error) {
|
||||
showApiError(this, error, 'tr:admin.falukant.edituser.pregnancy.error');
|
||||
}
|
||||
},
|
||||
async adminClearPregnancy() {
|
||||
const characterId = this.editableUser.falukantData[0].character.id;
|
||||
try {
|
||||
await apiClient.post('/api/admin/falukant/character/clear-pregnancy', { characterId });
|
||||
showSuccess(this, 'tr:admin.falukant.edituser.pregnancy.successClear');
|
||||
await this.refreshEditableUser();
|
||||
} catch (error) {
|
||||
showApiError(this, error, 'tr:admin.falukant.edituser.pregnancy.error');
|
||||
}
|
||||
},
|
||||
async adminForceBirth() {
|
||||
const motherCharacterId = this.editableUser.falukantData[0].character.id;
|
||||
if (!this.adminBirthFatherId) {
|
||||
showError(this, this.$t('admin.falukant.edituser.birth.fatherId'));
|
||||
return;
|
||||
}
|
||||
const body = {
|
||||
motherCharacterId,
|
||||
fatherCharacterId: Number(this.adminBirthFatherId),
|
||||
birthContext: this.adminBirthContext,
|
||||
legitimacy: this.adminBirthLegitimacy
|
||||
};
|
||||
if (this.adminBirthGender === 'male' || this.adminBirthGender === 'female') {
|
||||
body.gender = this.adminBirthGender;
|
||||
}
|
||||
try {
|
||||
await apiClient.post('/api/admin/falukant/character/force-birth', body);
|
||||
showSuccess(this, 'tr:admin.falukant.edituser.birth.success');
|
||||
await this.refreshEditableUser();
|
||||
} catch (error) {
|
||||
showApiError(this, error, 'tr:admin.falukant.edituser.birth.error');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,6 +491,43 @@ export default {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.admin-family-tools {
|
||||
margin-top: 1.25rem;
|
||||
padding: 1rem 1rem 0.5rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.admin-family-tools h4 {
|
||||
margin: 0.75rem 0 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.admin-family-tools h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.admin-family-tools__meta {
|
||||
margin: 0.35rem 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admin-family-tools__hint {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.admin-family-tools .form-field {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.admin-family-tools .action-buttons {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="pregnancy"
|
||||
class="family-pregnancy surface-card"
|
||||
>
|
||||
<strong>{{ $t('falukant.family.pregnancy.banner') }}</strong>
|
||||
<p>{{ $t('falukant.family.pregnancy.dueHint') }}: {{ formatPregnancyDue(pregnancy.dueAt) }}</p>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="debtorsPrison.active"
|
||||
class="family-debt-warning surface-card"
|
||||
@@ -407,6 +415,7 @@ export default {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
},
|
||||
pregnancy: null,
|
||||
selectedChild: null,
|
||||
pendingFamilyRefresh: null
|
||||
}
|
||||
@@ -563,11 +572,24 @@ export default {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
};
|
||||
this.pregnancy = response.data.pregnancy || null;
|
||||
} catch (error) {
|
||||
console.error('Error loading family data:', error);
|
||||
}
|
||||
},
|
||||
|
||||
formatPregnancyDue(iso) {
|
||||
if (!iso) return '—';
|
||||
try {
|
||||
return new Date(iso).toLocaleString(this.$i18n?.locale || undefined, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
});
|
||||
} catch (_) {
|
||||
return String(iso);
|
||||
}
|
||||
},
|
||||
|
||||
async spendTimeWithSpouse() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/family/marriage/spend-time');
|
||||
@@ -904,6 +926,18 @@ export default {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.family-pregnancy {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgba(120, 140, 200, 0.35);
|
||||
background: linear-gradient(180deg, rgba(235, 240, 255, 0.95), rgba(248, 250, 255, 0.98));
|
||||
}
|
||||
|
||||
.family-pregnancy p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.family-debt-warning {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 18px;
|
||||
|
||||
Reference in New Issue
Block a user