фикс стилей

This commit is contained in:
Professional 2025-05-26 13:43:48 +07:00
parent c2791255ee
commit 04330f6c2d
4 changed files with 1259 additions and 368 deletions

View File

@ -562,4 +562,183 @@ h3 {
.pagination-info { .pagination-info {
color: #666; color: #666;
} }
/* Адаптивные стили для больших экранов (1200px+) */
@media (min-width: 1200px) {
.admin-conversation-detail {
padding: 2rem;
}
h2 {
font-size: 1.75rem;
}
}
/* Адаптивные стили для средних экранов (992px - 1199px) */
@media (min-width: 992px) and (max-width: 1199px) {
.admin-conversation-detail {
padding: 1.5rem;
}
}
/* Адаптивные стили для планшетов (768px - 991px) */
@media (min-width: 768px) and (max-width: 991px) {
.admin-conversation-detail {
padding: 1.25rem;
}
.info-grid {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.participants-list {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
/* Адаптивные стили для малых планшетов (576px - 767px) */
@media (min-width: 576px) and (max-width: 767px) {
.admin-conversation-detail {
padding: 1rem;
/* Учитываем отступ внизу для нижней навигации на мобильных */
padding-bottom: calc(1rem + 56px + env(safe-area-inset-bottom, 0px));
}
.message-item {
max-width: 85%;
}
.participants-list {
grid-template-columns: 1fr;
}
.pagination {
flex-wrap: wrap;
}
}
/* Адаптивные стили для мобильных устройств (до 575px) */
@media (max-width: 575px) {
.admin-conversation-detail {
padding: 0.75rem;
/* Учитываем отступ внизу для нижней навигации на мобильных */
padding-bottom: calc(0.75rem + 56px + env(safe-area-inset-bottom, 0px));
}
h2 {
font-size: 1.3rem;
margin-bottom: 1rem;
}
h3 {
font-size: 1.1rem;
margin-bottom: 0.75rem;
}
.conversation-info,
.participants-section,
.messages-section {
padding: 1rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.participants-list {
grid-template-columns: 1fr;
}
.participant-card {
padding: 0.75rem;
}
.participant-photo {
width: 50px;
height: 50px;
}
.message-item {
max-width: 90%;
padding: 0.75rem;
}
.pagination {
flex-direction: column;
gap: 0.5rem;
align-items: stretch;
}
.pagination-btn {
width: 100%;
text-align: center;
}
.pagination-info {
text-align: center;
}
}
/* Специальные стили для очень маленьких экранов */
@media (max-width: 360px) {
.admin-conversation-detail {
padding: 0.5rem;
padding-bottom: calc(0.5rem + 56px + env(safe-area-inset-bottom, 0px));
}
.conversation-info,
.participants-section,
.messages-section {
padding: 0.75rem;
}
.message-time {
font-size: 0.7rem;
}
.message-sender {
font-size: 0.8rem;
}
.user-btn {
padding: 0.35rem 0.5rem;
font-size: 0.8rem;
}
}
/* Ландшафтная ориентация на мобильных */
@media (max-height: 450px) and (orientation: landscape) {
.admin-conversation-detail {
padding: 0.75rem;
padding-bottom: calc(0.75rem + 50px + env(safe-area-inset-bottom, 0px));
}
.conversation-container {
gap: 1rem;
}
.header-controls {
margin-bottom: 0.75rem;
}
h2 {
font-size: 1.2rem;
margin-bottom: 0.75rem;
}
h3 {
font-size: 1rem;
margin-bottom: 0.5rem;
}
.conversation-info,
.participants-section,
.messages-section {
padding: 0.75rem;
}
.pagination {
margin-top: 0.75rem;
}
}
</style> </style>

View File

@ -12,14 +12,17 @@
</div> </div>
<div v-if="loading" class="loading-indicator"> <div v-if="loading" class="loading-indicator">
Загрузка диалогов... <div class="spinner"></div>
<p>Загрузка диалогов...</p>
</div> </div>
<div v-if="error" class="error-message"> <div v-if="error" class="error-message">
{{ error }} {{ error }}
</div> </div>
<div v-if="!loading && !error" class="conversations-table-container"> <!-- Десктопная таблица (отображается только на больших экранах) -->
<div v-if="!loading && !error" class="desktop-table">
<div class="conversations-table-container">
<table class="conversations-table"> <table class="conversations-table">
<thead> <thead>
<tr> <tr>
@ -52,12 +55,51 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<div v-if="conversations.length === 0" class="no-results">
Диалоги не найдены
</div> </div>
<div v-if="pagination.pages > 1" class="pagination"> <!-- Мобильные карточки (отображаются только на малых экранах) -->
<div v-if="!loading && !error" class="mobile-cards">
<div
v-for="conversation in conversations"
:key="conversation._id"
class="conversation-card"
@click="viewConversation(conversation._id)"
>
<div class="card-header">
<div class="card-id">ID: {{ shortenId(conversation._id) }}</div>
<div class="card-date">{{ formatDate(conversation.updatedAt) }}</div>
</div>
<div class="card-content">
<div class="card-section">
<div class="section-title">Участники:</div>
<div class="section-content">
<div v-for="participant in conversation.participants" :key="participant._id" class="participant-item">
{{ participant.name }}
</div>
</div>
</div>
<div class="card-section">
<div class="section-title">Последнее сообщение:</div>
<div class="section-content message-preview">
{{ conversation.lastMessage ? truncateText(conversation.lastMessage.text, 50) : 'Нет сообщений' }}
</div>
</div>
</div>
<div class="card-footer">
<span>Просмотр диалога</span>
<i class="bi-arrow-right"></i>
</div>
</div>
</div>
<div v-if="!loading && !error && conversations.length === 0" class="no-results">
<i class="bi-chat-dots"></i>
<h3>Диалоги не найдены</h3>
<p>Попробуйте изменить параметры поиска</p>
</div>
<div v-if="!loading && !error && pagination.pages > 1" class="pagination">
<button <button
@click="changePage(currentPage - 1)" @click="changePage(currentPage - 1)"
:disabled="currentPage === 1" :disabled="currentPage === 1"
@ -79,7 +121,6 @@
</button> </button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -202,6 +243,7 @@ export default {
<style scoped> <style scoped>
.admin-conversations { .admin-conversations {
padding: 1.5rem;
padding-bottom: 2rem; padding-bottom: 2rem;
} }
@ -209,6 +251,21 @@ h2 {
margin-top: 0; margin-top: 0;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
color: #333; color: #333;
font-weight: 600;
position: relative;
padding-left: 0.5rem;
display: inline-block;
}
h2::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 3px;
background: linear-gradient(to bottom, #ff3e68, #ff5252);
border-radius: 3px;
} }
.search-bar { .search-bar {
@ -234,12 +291,35 @@ h2 {
font-weight: 500; font-weight: 500;
} }
.search-btn:hover {
background-color: #e9365e;
}
.loading-indicator { .loading-indicator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: #666; color: #666;
} }
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 62, 104, 0.2);
border-left: 4px solid #ff3e68;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message { .error-message {
padding: 1rem; padding: 1rem;
background-color: #ffebee; background-color: #ffebee;
@ -248,8 +328,11 @@ h2 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
/* Стили для десктопной таблицы */
.conversations-table-container { .conversations-table-container {
overflow-x: auto; overflow-x: auto;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
} }
.conversations-table { .conversations-table {
@ -270,6 +353,10 @@ h2 {
color: #444; color: #444;
} }
.conversations-table tr:hover {
background-color: #f9f9f9;
}
.participants-cell { .participants-cell {
max-width: 300px; max-width: 300px;
} }
@ -300,12 +387,127 @@ h2 {
color: #1976d2; color: #1976d2;
} }
.view-btn:hover {
background-color: #bbdefb;
}
/* Стили для мобильных карточек */
.mobile-cards {
display: none;
flex-direction: column;
gap: 1rem;
}
.conversation-card {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.conversation-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
cursor: pointer;
}
.card-header {
display: flex;
justify-content: space-between;
padding: 0.75rem;
background: #f8f9fa;
border-bottom: 1px solid #eee;
}
.card-id {
font-weight: 500;
color: #1976d2;
}
.card-date {
font-size: 0.85rem;
color: #666;
}
.card-content {
padding: 0.75rem;
}
.card-section {
margin-bottom: 0.75rem;
}
.card-section:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 0.85rem;
color: #666;
margin-bottom: 0.25rem;
}
.section-content {
color: #333;
}
.participant-item {
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.message-preview {
font-style: italic;
color: #495057;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background-color: #f8f9fa;
border-top: 1px solid #eee;
font-weight: 500;
color: #1976d2;
}
.card-footer i {
transition: transform 0.2s;
}
.conversation-card:hover .card-footer i {
transform: translateX(2px);
}
/* Стили для пустого результата */
.no-results { .no-results {
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: #666; color: #666;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
} }
.no-results i {
font-size: 3rem;
color: #ccc;
margin-bottom: 1rem;
}
.no-results h3 {
margin: 0 0 0.5rem;
color: #333;
}
.no-results p {
margin: 0;
color: #666;
}
/* Стили для пагинации */
.pagination { .pagination {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -320,6 +522,11 @@ h2 {
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s;
}
.pagination-btn:hover:not(:disabled) {
background-color: #e9e9e9;
} }
.pagination-btn:disabled { .pagination-btn:disabled {
@ -330,4 +537,115 @@ h2 {
.pagination-info { .pagination-info {
color: #666; color: #666;
} }
/* Адаптивные стили */
@media (max-width: 1199px) {
.admin-conversations {
padding: 1.25rem;
}
}
@media (max-width: 991px) {
/* На планшетах и меньше */
.desktop-table {
display: none;
}
.mobile-cards {
display: flex;
}
.admin-conversations {
padding: 1rem;
/* Добавляем отступ для нижней навигации */
padding-bottom: calc(1rem + 56px + env(safe-area-inset-bottom, 0px));
}
}
@media (min-width: 992px) {
/* На десктопах */
.desktop-table {
display: block;
}
.mobile-cards {
display: none;
}
}
@media (max-width: 767px) {
/* Мобильные устройства */
h2 {
font-size: 1.3rem;
}
.search-bar {
flex-direction: column;
gap: 0.5rem;
}
.search-btn {
width: 100%;
padding: 0.75rem;
}
.pagination {
flex-wrap: wrap;
gap: 0.5rem;
}
}
@media (max-width: 480px) {
/* Маленькие мобильные устройства */
.admin-conversations {
padding: 0.75rem;
padding-bottom: calc(0.75rem + 56px + env(safe-area-inset-bottom, 0px));
}
h2 {
font-size: 1.2rem;
margin-bottom: 1rem;
}
.search-bar input {
padding: 0.6rem;
}
.card-header {
flex-direction: column;
gap: 0.25rem;
}
.card-date {
font-size: 0.8rem;
}
.participant-item {
font-size: 0.85rem;
}
.pagination-btn {
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
}
}
/* Очень маленькие экраны */
@media (max-width: 360px) {
.admin-conversations {
padding: 0.5rem;
padding-bottom: calc(0.5rem + 56px + env(safe-area-inset-bottom, 0px));
}
.card-content {
padding: 0.6rem;
}
}
/* Ландшафтная ориентация на мобильных */
@media (max-height: 450px) and (orientation: landscape) {
.admin-conversations {
padding-bottom: calc(1rem + 50px + env(safe-area-inset-bottom, 0px));
}
}
</style> </style>

View File

@ -3,10 +3,12 @@
<h2>Статистика приложения</h2> <h2>Статистика приложения</h2>
<div v-if="loading" class="loading-indicator"> <div v-if="loading" class="loading-indicator">
Загрузка статистики... <div class="loading-spinner"></div>
<div>Загрузка статистики...</div>
</div> </div>
<div v-if="error" class="error-message"> <div v-if="error" class="error-message">
<i class="bi-exclamation-triangle"></i>
{{ error }} {{ error }}
</div> </div>
@ -15,36 +17,34 @@
<h3>Пользователи</h3> <h3>Пользователи</h3>
<div class="stat-cards"> <div class="stat-cards">
<div class="stat-card"> <div class="stat-card users">
<div class="stat-value">{{ statistics.users.total }}</div> <div class="stat-value">{{ statistics.users.total }}</div>
<div class="stat-label">Всего пользователей</div> <div class="stat-label">Всего пользователей</div>
</div> </div>
<div class="stat-card"> <div class="stat-card active">
<div class="stat-value">{{ statistics.users.active }}</div> <div class="stat-value">{{ statistics.users.active }}</div>
<div class="stat-label">Активных пользователей</div> <div class="stat-label">Активных</div>
</div> </div>
<div class="stat-card"> <div class="stat-card blocked">
<div class="stat-value">{{ statistics.users.inactive }}</div> <div class="stat-value">{{ statistics.users.inactive }}</div>
<div class="stat-label">Заблокированных</div> <div class="stat-label">Заблокированных</div>
</div> </div>
<div class="stat-card"> <div class="stat-card new">
<div class="stat-value">{{ statistics.users.newIn30Days }}</div> <div class="stat-value">{{ statistics.users.newIn30Days }}</div>
<div class="stat-label">Новых за 30 дней</div> <div class="stat-label">Новых за 30 дней</div>
</div> </div>
</div> </div>
<div class="gender-distribution"> <div class="gender-distribution">
<h4>Распределение по полу</h4> <h4>Распределение пользователей по полу</h4>
<div class="gender-chart"> <div class="gender-chart">
<div class="gender-bar"> <div class="gender-bar">
<div <div
class="gender-male" class="gender-male"
:style="{ :style="{ width: calculateGenderPercentage('male') + '%' }"
width: calculateGenderPercentage('male') + '%'
}"
> >
<span v-if="calculateGenderPercentage('male') > 10"> <span v-if="calculateGenderPercentage('male') > 10">
{{ statistics.users.genderDistribution.male }} {{ statistics.users.genderDistribution.male }}
@ -52,9 +52,7 @@
</div> </div>
<div <div
class="gender-female" class="gender-female"
:style="{ :style="{ width: calculateGenderPercentage('female') + '%' }"
width: calculateGenderPercentage('female') + '%'
}"
> >
<span v-if="calculateGenderPercentage('female') > 10"> <span v-if="calculateGenderPercentage('female') > 10">
{{ statistics.users.genderDistribution.female }} {{ statistics.users.genderDistribution.female }}
@ -62,9 +60,7 @@
</div> </div>
<div <div
class="gender-other" class="gender-other"
:style="{ :style="{ width: calculateGenderPercentage('other') + '%' }"
width: calculateGenderPercentage('other') + '%'
}"
> >
<span v-if="calculateGenderPercentage('other') > 10"> <span v-if="calculateGenderPercentage('other') > 10">
{{ statistics.users.genderDistribution.other }} {{ statistics.users.genderDistribution.other }}
@ -93,22 +89,23 @@
<h3>Активность пользователей</h3> <h3>Активность пользователей</h3>
<div class="stat-cards"> <div class="stat-cards">
<div class="stat-card"> <div class="stat-card messages">
<div class="stat-value">{{ statistics.activity.totalMessages }}</div> <div class="stat-value">{{ statistics.activity.totalMessages }}</div>
<div class="stat-label">Всего сообщений</div> <div class="stat-label">Всего сообщений</div>
</div> </div>
<div class="stat-card"> <div class="stat-card conversations">
<div class="stat-value">{{ statistics.activity.totalConversations }}</div> <div class="stat-value">{{ statistics.activity.totalConversations }}</div>
<div class="stat-label">Всего диалогов</div> <div class="stat-label">Всего диалогов</div>
</div> </div>
<div class="stat-card"> <div class="stat-card views">
<div class="stat-value">{{ statistics.activity.totalProfileViews }}</div> <div class="stat-value">{{ statistics.activity.totalProfileViews }}</div>
<div class="stat-label">Просмотров профилей</div> <div class="stat-label">Просмотров профилей</div>
</div> </div>
</div> </div>
<div class="average-stats-container">
<h4>Средняя активность</h4> <h4>Средняя активность</h4>
<div class="average-stats"> <div class="average-stats">
<div class="average-item"> <div class="average-item">
@ -123,6 +120,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -189,6 +187,7 @@ export default {
<style scoped> <style scoped>
.admin-statistics { .admin-statistics {
padding: 1.5rem;
padding-bottom: 2rem; padding-bottom: 2rem;
} }
@ -213,12 +212,49 @@ h2::before {
border-radius: 3px; border-radius: 3px;
} }
h3 {
margin-top: 0;
margin-bottom: 1.25rem;
color: #333;
font-weight: 600;
font-size: 1.3rem;
}
h4 {
margin: 1.5rem 0 1rem 0;
color: #333;
font-weight: 500;
font-size: 1.1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.loading-indicator { .loading-indicator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: #666; color: #666;
} }
.loading-spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 62, 104, 0.2);
border-left: 4px solid #ff3e68;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message { .error-message {
padding: 1rem; padding: 1rem;
background-color: #ffebee; background-color: #ffebee;
@ -226,6 +262,14 @@ h2::before {
color: #d32f2f; color: #d32f2f;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
border-radius: 4px; border-radius: 4px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.error-message i {
color: #d32f2f;
font-size: 1.2rem;
} }
.statistics-container { .statistics-container {
@ -234,40 +278,67 @@ h2::before {
gap: 2rem; gap: 2rem;
} }
.chart-container { .stat-section {
background-color: white; background-color: white;
border-radius: 8px; border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1); box-shadow: 0 2px 8px rgba(0,0,0,0.08);
padding: 1.5rem; padding: 1.5rem;
overflow: hidden; border: 1px solid #f1f3f5;
} }
.section-title { .stat-cards {
margin-top: 0;
margin-bottom: 1rem;
font-weight: 600;
color: #333;
}
.stats-section {
border-radius: 8px;
margin-bottom: 2rem;
}
.stats-cards {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem; gap: 1rem;
} }
.stat-card { .stat-card {
background-color: white; background-color: #f8f9fa;
border-radius: 8px; border-radius: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
padding: 1.5rem; padding: 1.5rem;
text-align: center; text-align: center;
transition: all 0.3s ease; transition: all 0.3s ease;
border-top: 3px solid transparent; border: 1px solid #f1f3f5;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(to right, transparent, #ff3e68, transparent);
}
.stat-card.users::before {
background: linear-gradient(to right, #ff3e68, #ff5252);
}
.stat-card.active::before {
background: linear-gradient(to right, #4caf50, #8bc34a);
}
.stat-card.blocked::before {
background: linear-gradient(to right, #f44336, #e91e63);
}
.stat-card.new::before {
background: linear-gradient(to right, #ff9800, #ffc107);
}
.stat-card.messages::before {
background: linear-gradient(to right, #2196f3, #03a9f4);
}
.stat-card.conversations::before {
background: linear-gradient(to right, #9c27b0, #673ab7);
}
.stat-card.views::before {
background: linear-gradient(to right, #009688, #4caf50);
} }
.stat-card:hover { .stat-card:hover {
@ -275,22 +346,6 @@ h2::before {
box-shadow: 0 4px 12px rgba(0,0,0,0.08); box-shadow: 0 4px 12px rgba(0,0,0,0.08);
} }
.stat-card.users {
border-color: #ff3e68;
}
.stat-card.messages {
border-color: #4caf50;
}
.stat-card.conversations {
border-color: #2196f3;
}
.stat-card.views {
border-color: #ff9800;
}
.stat-value { .stat-value {
font-size: 2.2rem; font-size: 2.2rem;
font-weight: bold; font-weight: bold;
@ -302,48 +357,45 @@ h2::before {
color: #ff3e68; color: #ff3e68;
} }
.stat-card.messages .stat-value { .stat-card.active .stat-value {
color: #4caf50; color: #4caf50;
} }
.stat-card.conversations .stat-value { .stat-card.blocked .stat-value {
color: #f44336;
}
.stat-card.new .stat-value {
color: #ff9800;
}
.stat-card.messages .stat-value {
color: #2196f3; color: #2196f3;
} }
.stat-card.conversations .stat-value {
color: #9c27b0;
}
.stat-card.views .stat-value { .stat-card.views .stat-value {
color: #ff9800; color: #009688;
} }
.stat-label { .stat-label {
color: #777; color: #777;
font-size: 0.9rem; font-size: 0.95rem;
font-weight: 500;
} }
/* Стили для блока распределения по полу */ /* Стили для блока распределения по полу */
.gender-distribution { .gender-distribution {
margin-top: 2rem; margin-top: 2rem;
background-color: white; background-color: #f8f9fa;
border-radius: 12px; border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
padding: 1.5rem; padding: 1.5rem;
border: 1px solid #f1f3f5; border: 1px solid #f1f3f5;
} }
.gender-distribution h4 {
margin: 0 0 1.5rem 0;
color: #333;
font-weight: 600;
font-size: 1.1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.gender-distribution h4::before {
content: '👥';
font-size: 1.2rem;
}
.gender-chart { .gender-chart {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -356,7 +408,7 @@ h2::before {
height: 40px; height: 40px;
border-radius: 20px; border-radius: 20px;
overflow: hidden; overflow: hidden;
background-color: #f8f9fa; background-color: #f1f3f5;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.05); box-shadow: inset 0 2px 4px rgba(0,0,0,0.05);
position: relative; position: relative;
} }
@ -369,8 +421,8 @@ h2::before {
justify-content: center; justify-content: center;
color: white; color: white;
font-weight: 600; font-weight: 600;
font-size: 0.85rem; font-size: 0.9rem;
text-shadow: 0 1px 2px rgba(0,0,0,0.3); text-shadow: 0 1px 2px rgba(0,0,0,0.2);
transition: all 0.3s ease; transition: all 0.3s ease;
min-width: 0; min-width: 0;
position: relative; position: relative;
@ -408,7 +460,7 @@ h2::before {
.gender-legend { .gender-legend {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 0.75rem;
margin-top: 1rem; margin-top: 1rem;
justify-content: center; justify-content: center;
} }
@ -418,16 +470,17 @@ h2::before {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
background-color: #f8f9fa; background-color: white;
border-radius: 8px; border-radius: 8px;
transition: all 0.2s ease; transition: all 0.2s ease;
border: 1px solid transparent; border: 1px solid #f1f3f5;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
} }
.legend-item:hover { .legend-item:hover {
background-color: white; background-color: white;
border-color: #e9ecef; border-color: #e9ecef;
box-shadow: 0 2px 4px rgba(0,0,0,0.05); box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transform: translateY(-1px); transform: translateY(-1px);
} }
@ -458,82 +511,108 @@ h2::before {
white-space: nowrap; white-space: nowrap;
} }
.averages-section { /* Блок средних значений */
.average-stats-container {
margin-top: 1.5rem; margin-top: 1.5rem;
background-color: #f8f9fa;
border-radius: 10px;
padding: 1.5rem;
border: 1px solid #f1f3f5;
} }
.averages-title { .average-stats {
margin-top: 0; display: grid;
margin-bottom: 1rem; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
font-weight: 500;
color: #333;
font-size: 1.1rem;
}
.averages-container {
display: flex;
flex-wrap: wrap;
gap: 1rem; gap: 1rem;
} }
.average-item { .average-item {
background-color: #f8f9fa;
padding: 0.8rem 1.2rem;
border-radius: 8px;
flex: 1;
min-width: 200px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background-color: white;
padding: 1rem 1.25rem;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid #f1f3f5;
transition: all 0.2s ease;
}
.average-item:hover {
transform: translateY(-2px);
box-shadow: 0 3px 6px rgba(0,0,0,0.08);
} }
.average-label { .average-label {
color: #555; color: #555;
font-weight: 500; font-weight: 500;
font-size: 0.95rem;
} }
.average-value { .average-value {
font-weight: bold; font-weight: bold;
color: #ff3e68; color: #ff3e68;
}
.donut-chart {
max-width: 300px;
margin: 0 auto;
}
.subcards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
margin-top: 1rem;
}
.subcard {
background-color: #f8f9fa;
padding: 0.8rem;
border-radius: 6px;
text-align: center;
}
.subcard-value {
font-weight: bold;
color: #ff3e68;
font-size: 1.2rem; font-size: 1.2rem;
} }
.subcard-label { /* Адаптивное отображение */
font-size: 0.75rem; @media (max-width: 1199px) {
color: #777; .admin-statistics {
margin-top: 0.25rem; padding: 1.25rem;
}
.stat-cards {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
}
@media (max-width: 991px) {
.admin-statistics {
padding: 1rem;
/* Добавляем отступ для нижней навигации */
padding-bottom: calc(1rem + 56px + env(safe-area-inset-bottom, 0px));
}
.stat-card {
padding: 1.25rem;
}
.stat-cards {
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.stat-value {
font-size: 2rem;
}
.gender-distribution,
.average-stats-container {
padding: 1.25rem;
}
.average-stats {
grid-template-columns: 1fr;
}
} }
/* Адаптивное отображение */
@media (max-width: 767px) { @media (max-width: 767px) {
.stat-cards {
grid-template-columns: repeat(2, 1fr);
gap: 0.8rem;
}
.stat-value { .stat-value {
font-size: 1.8rem; font-size: 1.8rem;
} }
.stat-label {
font-size: 0.85rem;
}
.stat-section {
padding: 1.25rem;
}
.gender-bar { .gender-bar {
height: 35px; height: 35px;
} }
@ -544,25 +623,38 @@ h2::before {
font-size: 0.8rem; font-size: 0.8rem;
} }
.gender-distribution { .gender-distribution,
padding: 1rem; .average-stats-container {
padding: 1.25rem;
margin-top: 1.5rem;
} }
.legend-label { .legend-item {
font-size: 0.85rem; padding: 0.4rem 0.6rem;
flex: 1;
justify-content: center;
} }
h2 { h2 {
font-size: 1.4rem; font-size: 1.3rem;
margin-bottom: 1.2rem; margin-bottom: 1.2rem;
} }
.section-title { h3 {
font-size: 1.1rem; font-size: 1.2rem;
margin-bottom: 1rem;
} }
.stats-cards { h4 {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); font-size: 1rem;
margin: 0.75rem 0;
}
.average-item {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
padding: 0.75rem 1rem;
} }
.admin-statistics { .admin-statistics {
@ -572,20 +664,31 @@ h2::before {
@media (max-width: 576px) { @media (max-width: 576px) {
.stat-card { .stat-card {
padding: 1.2rem; padding: 1rem;
} }
.stat-value { .stat-value {
font-size: 1.6rem; font-size: 1.5rem;
margin-bottom: 0.25rem;
} }
.stats-cards { .stat-label {
grid-template-columns: repeat(2, 1fr); font-size: 0.8rem;
} }
.gender-distribution { .admin-statistics {
padding: 0.75rem;
padding-bottom: calc(0.75rem + 56px + env(safe-area-inset-bottom, 0px));
}
.stat-section {
padding: 1rem; padding: 1rem;
margin-top: 1.5rem; }
.gender-distribution,
.average-stats-container {
padding: 1rem;
margin-top: 1rem;
} }
.gender-bar { .gender-bar {
@ -600,17 +703,72 @@ h2::before {
.gender-legend { .gender-legend {
flex-direction: column; flex-direction: column;
gap: 0.75rem; gap: 0.5rem;
align-items: center;
} }
.legend-item { .legend-item {
justify-content: center; width: 100%;
min-width: 120px;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
margin-bottom: 1rem;
}
h3 {
font-size: 1.1rem;
margin-bottom: 0.8rem;
}
h4 {
font-size: 0.95rem;
margin: 0.5rem 0;
}
}
@media (max-width: 480px) {
.stat-cards {
gap: 0.6rem;
}
.stat-card {
border-radius: 8px;
padding: 0.8rem;
}
.stat-value {
font-size: 1.3rem;
margin-bottom: 0.2rem;
}
.stat-label {
font-size: 0.75rem;
}
.average-label {
font-size: 0.85rem;
}
.average-value {
font-size: 1rem;
}
}
@media (max-width: 360px) {
.admin-statistics {
padding: 0.5rem;
padding-bottom: calc(0.5rem + 56px + env(safe-area-inset-bottom, 0px));
}
.stat-section,
.gender-distribution,
.average-stats-container {
padding: 0.75rem;
border-radius: 8px;
}
.gender-bar {
height: 25px;
} }
} }
@ -626,5 +784,29 @@ h2::before {
.admin-statistics { .admin-statistics {
padding-bottom: calc(50px + env(safe-area-inset-bottom, 0px)); padding-bottom: calc(50px + env(safe-area-inset-bottom, 0px));
} }
.stat-cards {
grid-template-columns: repeat(4, 1fr);
}
.stat-card {
padding: 0.75rem;
}
.stat-value {
font-size: 1.5rem;
}
.stat-label {
font-size: 0.75rem;
}
.gender-distribution {
margin-top: 1rem;
}
.gender-legend {
flex-direction: row;
}
} }
</style> </style>

View File

@ -26,14 +26,18 @@
</div> </div>
<div v-if="loading" class="loading-indicator"> <div v-if="loading" class="loading-indicator">
Загрузка пользователей... <div class="loading-spinner"></div>
<span>Загрузка пользователей...</span>
</div> </div>
<div v-if="error" class="error-message"> <div v-if="error" class="error-message">
<i class="bi-exclamation-triangle"></i>
{{ error }} {{ error }}
</div> </div>
<div v-if="!loading && !error" class="users-table-container"> <!-- Десктопная таблица (отображается только на больших экранах) -->
<div v-if="!loading && !error" class="desktop-table">
<div class="users-table-container">
<table class="users-table"> <table class="users-table">
<thead> <thead>
<tr> <tr>
@ -73,22 +77,75 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<div v-if="users.length === 0" class="no-results">
Пользователи не найдены
</div> </div>
<div v-if="pagination.pages > 1" class="pagination"> <!-- Мобильное карточное представление (отображается только на маленьких экранах) -->
<div v-if="!loading && !error" class="mobile-cards">
<div
v-for="user in users"
:key="user._id"
class="user-card"
>
<div class="card-header">
<div class="user-name">{{ user.name }}</div>
<span class="status-badge" :class="{ 'active': user.isActive, 'blocked': !user.isActive }">
{{ user.isActive ? 'Активен' : 'Заблокирован' }}
</span>
</div>
<div class="card-content">
<div class="info-row">
<div class="info-label">ID:</div>
<div class="info-value id-value">{{ shortenId(user._id) }}</div>
</div>
<div class="info-row">
<div class="info-label">Email:</div>
<div class="info-value">{{ user.email }}</div>
</div>
<div class="info-row">
<div class="info-label">Дата регистрации:</div>
<div class="info-value">{{ formatDate(user.createdAt) }}</div>
</div>
</div>
<div class="card-actions">
<button @click="viewUser(user._id)" class="card-btn view-btn">
<i class="bi-eye"></i>
<span>Просмотр</span>
</button>
<button
@click="toggleUserStatus(user._id, user.isActive)"
class="card-btn"
:class="user.isActive ? 'block-btn' : 'unblock-btn'"
>
<i :class="user.isActive ? 'bi-lock-fill' : 'bi-unlock-fill'"></i>
<span>{{ user.isActive ? 'Заблокировать' : 'Разблокировать' }}</span>
</button>
</div>
</div>
</div>
<div v-if="!loading && !error && users.length === 0" class="no-results">
<i class="bi-person-slash"></i>
<h3>Пользователи не найдены</h3>
<p>Попробуйте изменить параметры поиска или фильтрации</p>
</div>
<div v-if="!loading && !error && pagination.pages > 1" class="pagination">
<button <button
@click="changePage(currentPage - 1)" @click="changePage(currentPage - 1)"
:disabled="currentPage === 1" :disabled="currentPage === 1"
class="pagination-btn" class="pagination-btn"
> >
&laquo; Назад <i class="bi-chevron-left"></i>
<span>Назад</span>
</button> </button>
<span class="pagination-info"> <span class="pagination-info">
Страница {{ currentPage }} из {{ pagination.pages }} {{ currentPage }} из {{ pagination.pages }}
</span> </span>
<button <button
@ -96,11 +153,11 @@
:disabled="currentPage === pagination.pages" :disabled="currentPage === pagination.pages"
class="pagination-btn" class="pagination-btn"
> >
Вперёд &raquo; <span>Вперёд</span>
<i class="bi-chevron-right"></i>
</button> </button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -264,6 +321,7 @@ export default {
<style scoped> <style scoped>
.admin-users { .admin-users {
padding: 1.5rem;
padding-bottom: 2rem; padding-bottom: 2rem;
max-width: 100%; max-width: 100%;
} }
@ -339,11 +397,30 @@ h2::before {
} }
.loading-indicator { .loading-indicator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: #666; color: #666;
} }
.loading-spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 62, 104, 0.2);
border-left: 4px solid #ff3e68;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message { .error-message {
padding: 1rem; padding: 1rem;
background-color: #ffebee; background-color: #ffebee;
@ -351,11 +428,22 @@ h2::before {
color: #d32f2f; color: #d32f2f;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
border-radius: 4px; border-radius: 4px;
display: flex;
align-items: center;
gap: 0.5rem;
} }
.error-message i {
color: #f44336;
font-size: 1.2rem;
}
/* Стили для десктопной таблицы */
.users-table-container { .users-table-container {
overflow-x: auto; overflow-x: auto;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
} }
.users-table { .users-table {
@ -363,8 +451,6 @@ h2::before {
border-collapse: collapse; border-collapse: collapse;
min-width: 800px; min-width: 800px;
background: white; background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
} }
.users-table th, .users-table td { .users-table th, .users-table td {
@ -467,12 +553,116 @@ h2::before {
background-color: rgba(46, 125, 50, 0.15); background-color: rgba(46, 125, 50, 0.15);
} }
.no-results { /* Стили для мобильных карточек */
padding: 2rem; .mobile-cards {
text-align: center; display: none;
color: #666; flex-direction: column;
background-color: #f9f9f9; gap: 1rem;
margin-bottom: 1.5rem;
}
.user-card {
background: white;
border-radius: 8px; border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #eee;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #fafafa;
border-bottom: 1px solid #eee;
}
.user-name {
font-weight: 600;
font-size: 1.1rem;
color: #333;
}
.card-content {
padding: 1rem;
}
.info-row {
display: flex;
margin-bottom: 0.5rem;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
flex: 0 0 40%;
color: #666;
font-size: 0.9rem;
}
.info-value {
flex: 1;
font-weight: 500;
}
.id-value {
font-family: monospace;
color: #1976d2;
}
.card-actions {
display: flex;
padding: 0.75rem;
gap: 0.5rem;
border-top: 1px solid #eee;
background-color: #fafafa;
}
.card-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.6rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease;
}
.card-btn:hover {
transform: translateY(-2px);
}
.no-results {
text-align: center;
padding: 3rem 1rem;
color: #666;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.no-results i {
font-size: 3rem;
color: #ccc;
margin-bottom: 1rem;
}
.no-results h3 {
margin: 0 0 0.5rem;
color: #333;
}
.no-results p {
margin: 0;
color: #666;
} }
.pagination { .pagination {
@ -493,6 +683,9 @@ h2::before {
transition: all 0.2s ease; transition: all 0.2s ease;
color: #495057; color: #495057;
font-weight: 500; font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
} }
.pagination-btn:hover:not(:disabled) { .pagination-btn:hover:not(:disabled) {
@ -507,122 +700,141 @@ h2::before {
.pagination-info { .pagination-info {
color: #6c757d; color: #6c757d;
font-weight: 500;
}
/* Адаптивные стили */
@media (max-width: 1199px) {
.admin-users {
padding: 1.25rem;
}
} }
/* Адаптивное отображение */
@media (max-width: 991px) { @media (max-width: 991px) {
.users-table { /* На планшетах и меньше */
font-size: 0.9rem; .desktop-table {
}
.users-table th, .users-table td {
padding: 0.6rem;
}
.btn {
padding: 0.3rem 0.6rem;
}
}
/* Планшеты и маленькие экраны */
@media (max-width: 767px) {
h2 {
font-size: 1.5rem;
}
/* Скрываем менее важные данные на мобильных */
.hide-sm {
display: none; display: none;
} }
.email-cell { .mobile-cards {
max-width: 140px; display: flex;
overflow: hidden; }
text-overflow: ellipsis;
white-space: nowrap; .admin-users {
padding: 1rem;
/* Добавляем отступ для нижней навигации */
padding-bottom: calc(1rem + 56px + env(safe-area-inset-bottom, 0px));
}
}
@media (min-width: 992px) {
/* На десктопах */
.desktop-table {
display: block;
}
.mobile-cards {
display: none;
}
}
@media (max-width: 767px) {
/* Мобильные устройства */
h2 {
font-size: 1.3rem;
} }
.filter-options { .filter-options {
justify-content: space-between; display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
width: 100%; width: 100%;
} }
.users-table { .filter-options label {
min-width: auto;
font-size: 0.85rem;
}
.status-badge {
font-size: 0.75rem;
padding: 0.2rem 0.5rem;
}
.btn-text {
display: none;
}
.btn {
padding: 0.4rem;
display: inline-flex;
align-items: center;
justify-content: center; justify-content: center;
} width: 100%;
font-size: 0.9rem;
.btn i { padding: 0.5rem 0;
font-size: 1.1rem;
margin: 0;
} }
.pagination { .pagination {
gap: 0.5rem; gap: 0.75rem;
}
.pagination-btn {
padding: 0.4rem 0.7rem;
font-size: 0.85rem;
}
h2 {
font-size: 1.3rem;
margin-bottom: 1rem;
}
/* На мобильных добавляем отступ снизу для нижней навигации */
.admin-users {
padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
} }
} }
/* Мелкие мобильные устройства */ @media (max-width: 480px) {
@media (max-width: 420px) { /* Маленькие мобильные устройства */
.users-table th, .users-table td { .admin-users {
padding: 0.5rem 0.4rem; padding: 0.75rem;
} padding-bottom: calc(0.75rem + 56px + env(safe-area-inset-bottom, 0px));
.email-cell {
max-width: 80px;
} }
h2 { h2 {
font-size: 1.2rem; font-size: 1.2rem;
margin-bottom: 1rem;
}
.search-bar input {
padding: 0.6rem;
}
.filter-options {
grid-template-columns: 1fr 1fr 1fr;
gap: 0.3rem;
}
.filter-options label {
font-size: 0.8rem;
padding: 0.5rem 0.3rem;
}
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.card-actions {
flex-direction: column;
}
.pagination {
flex-direction: row;
justify-content: space-between;
}
.pagination-btn {
padding: 0.5rem;
} }
} }
/* Устройства с вырезом (notch) */ /* Очень маленькие экраны */
@supports (padding: env(safe-area-inset-bottom)) { @media (max-width: 360px) {
.admin-users { .admin-users {
padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); padding: 0.5rem;
padding-bottom: calc(0.5rem + 56px + env(safe-area-inset-bottom, 0px));
}
.info-row {
flex-direction: column;
gap: 0.25rem;
margin-bottom: 0.75rem;
}
.info-label {
flex: none;
} }
} }
/* Ландшафтная ориентация на мобильных */ /* Ландшафтная ориентация на мобильных */
@media (max-height: 450px) and (orientation: landscape) { @media (max-height: 450px) and (orientation: landscape) {
.users-table { .admin-users {
font-size: 0.8rem; padding-bottom: calc(1rem + 50px + env(safe-area-inset-bottom, 0px));
} }
.admin-users { .card-actions {
padding-bottom: calc(50px + env(safe-area-inset-bottom, 0px)); flex-direction: row;
} }
} }
</style> </style>