style(admin): improve moderation UI and catalog button alignment
Added dismissed card styling and reorganized error/empty state rendering in moderation. Added fixed width for catalog action buttons.
This commit is contained in:
@@ -444,3 +444,10 @@ const handleDeleteTeam = async () => {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.text-end .btn {
|
||||
margin: 0 !important;
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,9 +28,9 @@ const dismissModal = reactive({
|
||||
})
|
||||
|
||||
const { data: flagsRes, pending, refresh } = useAsyncData(
|
||||
'admin-moderation-flags',
|
||||
() => getModerationFlags({ page: page.value, per_page: perPage.value, status: statusFilter.value || undefined }),
|
||||
{ watch: [page, statusFilter] }
|
||||
'admin-moderation-flags',
|
||||
() => getModerationFlags({ page: page.value, per_page: perPage.value, status: statusFilter.value || undefined }),
|
||||
{ watch: [page, statusFilter] }
|
||||
)
|
||||
|
||||
const openResolveModal = (id: number, rejectReport: boolean) => {
|
||||
@@ -54,15 +54,14 @@ const handleResolve = async () => {
|
||||
actionError.value = ''
|
||||
|
||||
try {
|
||||
const res = await updateModerationFlag(resolveModal.flagId, {
|
||||
status: 'RESOLVED',
|
||||
await updateModerationFlag(resolveModal.flagId, {
|
||||
status: 'RESOLVED',
|
||||
reject_report: resolveModal.rejectReport,
|
||||
resolution_note: resolveModal.note || undefined
|
||||
})
|
||||
if (res.ok) {
|
||||
await refresh()
|
||||
closeResolveModal()
|
||||
}
|
||||
|
||||
await refresh()
|
||||
closeResolveModal()
|
||||
} catch (err) {
|
||||
console.error('Failed to resolve flag', err)
|
||||
actionError.value = 'Errore durante la risoluzione della segnalazione.'
|
||||
@@ -89,11 +88,10 @@ const handleDismiss = async () => {
|
||||
actionError.value = ''
|
||||
|
||||
try {
|
||||
const res = await updateModerationFlag(dismissModal.flagId, { status: 'DISMISSED' })
|
||||
if (res.ok) {
|
||||
await refresh()
|
||||
closeDismissModal()
|
||||
}
|
||||
await updateModerationFlag(dismissModal.flagId, { status: 'DISMISSED' })
|
||||
|
||||
await refresh()
|
||||
closeDismissModal()
|
||||
} catch (err) {
|
||||
console.error('Failed to dismiss flag', err)
|
||||
actionError.value = 'Errore durante l\'aggiornamento.'
|
||||
@@ -103,7 +101,7 @@ const handleDismiss = async () => {
|
||||
}
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return new Date(dateStr).toLocaleString('it-IT', {
|
||||
return new Date(dateStr).toLocaleString('it-IT', {
|
||||
day: '2-digit', month: '2-digit', year: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
})
|
||||
@@ -129,23 +127,30 @@ const formatDate = (dateStr: string) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spostato in modo che non interrompa l'if-else dei caricamenti -->
|
||||
<div v-if="actionError" class="alert alert-danger mb-4">
|
||||
{{ actionError }}
|
||||
</div>
|
||||
|
||||
<div v-if="pending" class="text-center py-5">
|
||||
<div class="spinner-border text-danger" role="status"></div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!flagsRes?.flags || flagsRes.flags.length === 0" class="alert alert-success">
|
||||
<div v-else-if="!flagsRes || !flagsRes.flags || flagsRes.flags.length === 0" class="alert alert-success">
|
||||
Nessun elemento da moderare al momento.
|
||||
</div>
|
||||
|
||||
<div v-if="actionError" class="alert alert-danger">
|
||||
{{ actionError }}
|
||||
</div>
|
||||
|
||||
<!-- Questo v-else ora è scatenato in modo pulito solo quando i dati esistono davvero -->
|
||||
<div v-else>
|
||||
<div class="card mb-3 shadow-sm border-0" v-for="flag in flagsRes.flags" :key="flag.id">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<div
|
||||
class="card mb-3 border-0"
|
||||
:class="flag.status === 'DISMISSED' ? 'dismissed-card shadow-none' : 'shadow-sm'"
|
||||
v-for="flag in flagsRes.flags"
|
||||
:key="flag.id"
|
||||
>
|
||||
<div class="card-header d-flex justify-content-between align-items-center" :class="flag.status === 'DISMISSED' ? 'bg-transparent' : 'bg-light'">
|
||||
<div>
|
||||
<strong>Flag #{{ flag.id }}</strong> -
|
||||
<strong>Flag #{{ flag.id }}</strong> -
|
||||
<span class="badge" :class="{
|
||||
'bg-warning': flag.status === 'OPEN',
|
||||
'bg-success': flag.status === 'RESOLVED',
|
||||
@@ -161,8 +166,8 @@ const formatDate = (dateStr: string) => {
|
||||
<p class="card-text text-muted mb-2">
|
||||
{{ flag.details || 'Nessun dettaglio aggiuntivo fornito.' }}
|
||||
</p>
|
||||
|
||||
<div class="mt-3">
|
||||
|
||||
<div class="mt-info">
|
||||
<span class="badge bg-info me-2">{{ flag.content_type }}</span>
|
||||
<span v-if="flag.report_id">
|
||||
Rif:
|
||||
@@ -174,17 +179,17 @@ const formatDate = (dateStr: string) => {
|
||||
| Messaggio #{{ flag.message_id }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 small">
|
||||
|
||||
<div class="mt-info small text-muted">
|
||||
Segnalato da: {{ flag.created_by_email }} (ID: {{ flag.created_by_user_id }})
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="flag.status !== 'OPEN'" class="mt-2 small text-muted border-top pt-2">
|
||||
<strong>Risolto/Ignorato da:</strong> {{ flag.resolved_by_email }} in data {{ flag.resolved_at ? formatDate(flag.resolved_at) : 'N/D' }}
|
||||
<div v-if="flag.resolution_note"><strong>Nota:</strong> {{ flag.resolution_note }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-4 text-end" v-if="flag.status === 'OPEN'">
|
||||
<div class="d-flex flex-column gap-2 mt-3 mt-md-0">
|
||||
<button class="btn btn-outline-danger btn-sm" @click="openResolveModal(flag.id, true)">
|
||||
@@ -202,12 +207,12 @@ const formatDate = (dateStr: string) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
v-if="(flagsRes.total || flagsRes.flags.length) > perPage"
|
||||
:current-page="page"
|
||||
:per-page="perPage"
|
||||
:total-items="flagsRes.total || (page * perPage + 1)"
|
||||
@page-change="page = $event"
|
||||
<Pagination
|
||||
v-if="(flagsRes.total || flagsRes.flags.length) > perPage"
|
||||
:current-page="page"
|
||||
:per-page="perPage"
|
||||
:total-items="flagsRes.total || (page * perPage + 1)"
|
||||
@page-change="page = $event"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -235,14 +240,46 @@ const formatDate = (dateStr: string) => {
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
:open="dismissModal.open"
|
||||
title="Ignora flag"
|
||||
message="Vuoi ignorare questo flag? Azione registrata in moderazione."
|
||||
confirm-label="Ignora flag"
|
||||
confirm-class="btn-outline-secondary"
|
||||
:pending="dismissModal.pending"
|
||||
@cancel="closeDismissModal"
|
||||
@confirm="handleDismiss"
|
||||
:open="dismissModal.open"
|
||||
title="Ignora flag"
|
||||
message="Vuoi ignorare questo flag? Azione registrata in moderazione."
|
||||
confirm-label="Ignora flag"
|
||||
confirm-class="btn-outline-secondary"
|
||||
:pending="dismissModal.pending"
|
||||
@cancel="closeDismissModal"
|
||||
@confirm="handleDismiss"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dismissed-card {
|
||||
background-color: #f8f9fa;
|
||||
opacity: 0.7;
|
||||
border: 1px solid #e9ecef !important;
|
||||
}
|
||||
|
||||
.dismissed-card .card-header {
|
||||
padding-top: 0.35rem;
|
||||
padding-bottom: 0.35rem;
|
||||
border-bottom: 1px solid rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.dismissed-card .card-body {
|
||||
padding-top: 0.6rem;
|
||||
padding-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.dismissed-card h5.card-title {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.mt-info {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.dismissed-card .mt-info {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user