feat(Moderation): implement moderation reports feature
All checks were successful
Deploy to production / deploy (push) Successful in 2m1s
All checks were successful
Deploy to production / deploy (push) Successful in 2m1s
- Added moderationRouter to handle moderation-related API routes. - Introduced new methods in AdminController for fetching all regions, region types, and creating regions. - Enhanced adminRouter with routes for moderation reports and status updates. - Updated navigationController to include moderation reports in the admin menu. - Implemented frontend components for reporting messages in the forum and managing moderation reports. - Added internationalization support for moderation-related texts in multiple languages.
This commit is contained in:
@@ -63,6 +63,58 @@
|
||||
<SimpleTabs v-model="activeTab" :tabs="tabs" />
|
||||
|
||||
<div v-if="activeTab === 'regions'">
|
||||
<div class="create-region">
|
||||
<h3>{{ $t('admin.falukant.map.createRegion.title') }}</h3>
|
||||
<table class="create-region-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="label-cell">{{ $t('admin.falukant.map.createRegion.type') }}</td>
|
||||
<td class="field-cell">
|
||||
<select v-model.number="newRegion.regionTypeId">
|
||||
<option :value="null" disabled>{{ $t('admin.falukant.map.createRegion.selectType') }}</option>
|
||||
<option v-for="t in regionTypes" :key="`rt-${t.id}`" :value="t.id">
|
||||
{{ regionTypeLabel(t.labelTr) }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">{{ $t('admin.falukant.map.createRegion.parent') }}</td>
|
||||
<td class="field-cell">
|
||||
<select v-model.number="newRegion.parentId" :disabled="!parentRegionEnabled">
|
||||
<option :value="null" :disabled="parentRegionRequired">
|
||||
{{ parentRegionEnabled ? $t('admin.falukant.map.createRegion.selectParent') : $t('admin.falukant.map.createRegion.noParent') }}
|
||||
</option>
|
||||
<option v-for="r in availableParentRegions" :key="`pr-${r.id}`" :value="r.id">
|
||||
{{ r.name }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">{{ $t('admin.falukant.map.createRegion.name') }}</td>
|
||||
<td class="field-cell">
|
||||
<input type="text" v-model.trim="newRegion.name" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="create-region-actions-cell">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="creatingRegion || !canCreateRegion"
|
||||
@click="createRegion"
|
||||
>
|
||||
{{ creatingRegion ? $t('admin.falukant.map.createRegion.creating') : $t('admin.falukant.map.createRegion.create') }}
|
||||
</button>
|
||||
<span v-if="createRegionError" class="error-text">
|
||||
{{ createRegionError }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedRegion" class="details">
|
||||
<h3>{{ selectedRegion.name }}</h3>
|
||||
<p v-if="selectedRegion.map">
|
||||
@@ -225,6 +277,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
regions: [],
|
||||
regionTypes: [],
|
||||
selectedRegion: null,
|
||||
selectedRegionDirty: false,
|
||||
dirtyRegionIds: [],
|
||||
@@ -246,9 +299,52 @@ export default {
|
||||
{ value: 'distances', label: 'admin.falukant.map.tabs.distances' },
|
||||
],
|
||||
pickMode: null,
|
||||
newRegion: {
|
||||
regionTypeId: null,
|
||||
parentId: null,
|
||||
name: '',
|
||||
},
|
||||
creatingRegion: false,
|
||||
createRegionError: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedRegionType() {
|
||||
if (!this.newRegion.regionTypeId) return null;
|
||||
return this.regionTypes.find((t) => t.id === this.newRegion.regionTypeId) || null;
|
||||
},
|
||||
parentRegionEnabled() {
|
||||
return Boolean(this.selectedRegionType && this.selectedRegionType.parentId != null);
|
||||
},
|
||||
parentRegionRequired() {
|
||||
return this.parentRegionEnabled;
|
||||
},
|
||||
availableParentRegions() {
|
||||
if (!this.parentRegionEnabled) return [];
|
||||
const parentTypeId = this.selectedRegionType.parentId;
|
||||
return this.regions.filter((r) => r.regionTypeId === parentTypeId);
|
||||
},
|
||||
canCreateRegion() {
|
||||
if (!this.newRegion.regionTypeId) return false;
|
||||
if (!this.newRegion.name || !String(this.newRegion.name).trim()) return false;
|
||||
if (this.parentRegionRequired && !this.newRegion.parentId) return false;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'newRegion.regionTypeId': function () {
|
||||
// Wenn der Typ keinen Parent erlaubt, Parent zurücksetzen
|
||||
if (!this.parentRegionEnabled) {
|
||||
this.newRegion.parentId = null;
|
||||
} else if (this.newRegion.parentId) {
|
||||
// Falls der gewählte Parent nicht mehr passt (z.B. Typ gewechselt), resetten
|
||||
const ok = this.availableParentRegions.some((r) => r.id === this.newRegion.parentId);
|
||||
if (!ok) this.newRegion.parentId = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadRegionTypes();
|
||||
await this.loadRegions();
|
||||
await this.loadConnections();
|
||||
},
|
||||
@@ -266,9 +362,22 @@ export default {
|
||||
if (!translated || translated === tKey) return mode;
|
||||
return translated;
|
||||
},
|
||||
regionTypeLabel(labelTr) {
|
||||
if (!labelTr) return '';
|
||||
const raw = String(labelTr);
|
||||
const keys = [
|
||||
`falukant.politics.regionLevels.${raw}`,
|
||||
`falukant.regionType.${raw}`,
|
||||
];
|
||||
for (const k of keys) {
|
||||
const tr = this.$t(k);
|
||||
if (tr && tr !== k) return tr;
|
||||
}
|
||||
return raw;
|
||||
},
|
||||
async loadRegions() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/admin/falukant/regions');
|
||||
const { data } = await apiClient.get('/api/admin/falukant/regions/all');
|
||||
// Sicherstellen, dass map-Objekte existieren oder null sind
|
||||
this.regions = (data || []).map(r => ({
|
||||
...r,
|
||||
@@ -278,6 +387,49 @@ export default {
|
||||
console.error('Error loading Falukant regions:', error);
|
||||
}
|
||||
},
|
||||
async loadRegionTypes() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/admin/falukant/region-types');
|
||||
this.regionTypes = data || [];
|
||||
} catch (error) {
|
||||
console.error('Error loading Falukant region types:', error);
|
||||
this.regionTypes = [];
|
||||
}
|
||||
},
|
||||
async createRegion() {
|
||||
if (this.creatingRegion || !this.canCreateRegion) return;
|
||||
this.creatingRegion = true;
|
||||
this.createRegionError = null;
|
||||
try {
|
||||
const payload = {
|
||||
name: this.newRegion.name,
|
||||
regionTypeId: this.newRegion.regionTypeId,
|
||||
parentId: this.parentRegionEnabled ? this.newRegion.parentId : null,
|
||||
};
|
||||
const { data } = await apiClient.post('/api/admin/falukant/regions', payload);
|
||||
|
||||
await this.loadRegions();
|
||||
if (data && data.id) {
|
||||
const created = this.regions.find((r) => r.id === data.id) || null;
|
||||
if (created) {
|
||||
this.activeTab = 'regions';
|
||||
this.selectRegion(created);
|
||||
}
|
||||
}
|
||||
|
||||
// Form zurücksetzen (Typ beibehalten, Name leeren)
|
||||
this.newRegion.name = '';
|
||||
if (this.parentRegionEnabled) {
|
||||
this.newRegion.parentId = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating region:', error);
|
||||
const errKey = error?.response?.data?.error;
|
||||
this.createRegionError = errKey ? String(errKey) : this.$t('admin.falukant.map.createRegion.error');
|
||||
} finally {
|
||||
this.creatingRegion = false;
|
||||
}
|
||||
},
|
||||
async loadConnections() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/admin/falukant/region-distances');
|
||||
@@ -588,6 +740,28 @@ export default {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.create-region {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.create-region-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.create-region-actions-cell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
margin-left: 8px;
|
||||
color: #b00;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
|
||||
Reference in New Issue
Block a user