This commit is contained in:
Oscar
2026-06-08 13:23:20 +03:00
commit 637dddf656
160 changed files with 56097 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useUiStore } from '@/stores/ui.store';
import { apiClient } from '@/api/client';
import AppButton from '@/components/common/AppButton.vue';
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
import EmptyState from '@/components/common/EmptyState.vue';
interface Report {
id: string;
sourceProfileId: string;
entityId: string;
entityType: 'profile' | 'message';
description?: string;
createdAt: string;
resolved?: boolean;
reporterName?: string;
}
const uiStore = useUiStore();
const reports = ref<Report[]>([]);
const loading = ref(false);
onMounted(async () => {
loading.value = true;
try {
const res = await apiClient.api.reportsControllerGetAll() as unknown as Report[];
reports.value = res;
} catch {
uiStore.addToast('Не удалось загрузить жалобы', 'error');
} finally {
loading.value = false;
}
});
async function banUser(userId: string) {
try {
await apiClient.api.usersControllerBan(userId);
uiStore.addToast('Пользователь заблокирован', 'success');
} catch {
uiStore.addToast('Не удалось заблокировать пользователя', 'error');
}
}
function formatDate(iso: string) {
return new Date(iso).toLocaleDateString('ru', { day: 'numeric', month: 'short', year: 'numeric' });
}
</script>
<template>
<div class="reports-admin">
<header class="reports-admin__header">
<h1 class="reports-admin__title">Жалобы</h1>
<span class="meta">{{ reports.length }} всего</span>
</header>
<div v-if="loading" class="reports-admin__loading">
<LoadingSpinner size="lg" />
</div>
<EmptyState
v-else-if="reports.length === 0"
title="Нет жалоб"
description="Жалобы от пользователей появятся здесь"
icon="default"
/>
<div v-else class="reports-admin__table-wrap">
<table class="reports-table">
<thead>
<tr>
<th>Дата</th>
<th>Тип</th>
<th>Объект</th>
<th>Описание</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<tr v-for="report in reports" :key="report.id" :class="{ 'reports-table__row--resolved': report.resolved }">
<td class="reports-table__date meta">{{ formatDate(report.createdAt) }}</td>
<td>
<span class="reports-table__type" :class="`reports-table__type--${report.entityType}`">
{{ report.entityType === 'profile' ? 'Профиль' : 'Сообщение' }}
</span>
</td>
<td class="reports-table__entity">
<RouterLink
v-if="report.entityType === 'profile'"
:to="`/profile/${report.entityId}`"
class="reports-table__link"
>Открыть</RouterLink>
<span v-else class="meta">{{ report.entityId.slice(0, 8) }}</span>
</td>
<td class="reports-table__desc">{{ report.description ?? '—' }}</td>
<td>
<AppButton
v-if="report.entityType === 'profile'"
variant="danger"
size="sm"
@click="banUser(report.sourceProfileId)"
>Заблокировать</AppButton>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<style scoped lang="scss">
.reports-admin {
height: 100%;
display: flex;
flex-direction: column;
&__header {
display: flex;
align-items: baseline;
gap: 12px;
padding: 20px 24px 16px;
border-bottom: 1px solid var(--color-border);
flex-shrink: 0;
}
&__title {
font-family: var(--font-display);
font-size: 1.5rem;
color: var(--color-cream);
margin: 0;
}
&__loading {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
&__table-wrap {
flex: 1;
overflow: auto;
padding: 16px 24px;
}
}
.reports-table {
width: 100%;
border-collapse: collapse;
font-family: var(--font-mono);
font-size: 0.8125rem;
th {
text-align: left;
font-weight: 500;
letter-spacing: 0.06em;
text-transform: uppercase;
font-size: 0.625rem;
color: var(--color-muted);
padding: 8px 12px;
border-bottom: 1px solid var(--color-border);
}
td {
padding: 10px 12px;
color: var(--color-cream);
border-bottom: 1px solid var(--color-border);
vertical-align: middle;
}
tr:last-child td { border-bottom: none; }
&__row--resolved td { opacity: 0.5; }
&__date { color: var(--color-muted) !important; font-variant-numeric: tabular-nums; }
&__type {
padding: 2px 8px;
border-radius: var(--radius-full);
font-size: 0.625rem;
font-weight: 500;
letter-spacing: 0.06em;
text-transform: uppercase;
&--profile { background: var(--color-signal-bg); color: var(--color-signal); }
&--message { background: rgba(240, 235, 224, 0.06); color: var(--color-muted); }
}
&__link {
color: var(--color-cream);
text-decoration: underline;
text-underline-offset: 3px;
text-decoration-color: var(--color-border);
transition: color var(--transition-fast);
&:hover { color: var(--color-signal); }
}
&__desc {
max-width: 280px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--color-muted) !important;
}
}
</style>