ваше сообщение коммита
This commit is contained in:
@@ -11,6 +11,7 @@ const db = require('./db'); // Добавляем импорт db
|
|||||||
const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant
|
const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const messagesRoutes = require('./routes/messages');
|
||||||
|
|
||||||
// Проверка и создание директорий для хранения данных контрактов
|
// Проверка и создание директорий для хранения данных контрактов
|
||||||
const ensureDirectoriesExist = () => {
|
const ensureDirectoriesExist = () => {
|
||||||
@@ -77,7 +78,7 @@ app.use(
|
|||||||
'http://127.0.0.1:5173', // Добавляем альтернативный origin
|
'http://127.0.0.1:5173', // Добавляем альтернативный origin
|
||||||
],
|
],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
|
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -163,6 +164,7 @@ app.use('/api/isic', isicRoutes); // Добавленное использова
|
|||||||
app.use('/api/geocoding', geocodingRoutes); // Добавленное использование роута
|
app.use('/api/geocoding', geocodingRoutes); // Добавленное использование роута
|
||||||
app.use('/api/dle', dleRoutes); // Добавляем маршрут DLE
|
app.use('/api/dle', dleRoutes); // Добавляем маршрут DLE
|
||||||
app.use('/api/settings', settingsRoutes); // Добавляем маршрут настроек
|
app.use('/api/settings', settingsRoutes); // Добавляем маршрут настроек
|
||||||
|
app.use('/api/messages', messagesRoutes);
|
||||||
|
|
||||||
const nonceStore = new Map(); // или любая другая реализация хранилища nonce
|
const nonceStore = new Map(); // или любая другая реализация хранилища nonce
|
||||||
|
|
||||||
|
|||||||
@@ -97,10 +97,4 @@ async function saveGuestMessageToDatabase(message, language, guestId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Экспортируем функции для работы с базой данных
|
// Экспортируем функции для работы с базой данных
|
||||||
module.exports = {
|
module.exports = { getQuery, pool, getPool, setPoolChangeCallback };
|
||||||
getPool,
|
|
||||||
getQuery,
|
|
||||||
reinitPoolFromDbSettings,
|
|
||||||
saveGuestMessageToDatabase,
|
|
||||||
setPoolChangeCallback,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Добавление поля preferred_language для хранения языков пользователя (множественный выбор)
|
||||||
|
ALTER TABLE users ADD COLUMN preferred_language jsonb;
|
||||||
23
backend/routes/messages.js
Normal file
23
backend/routes/messages.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const db = require('../db');
|
||||||
|
|
||||||
|
// GET /api/messages?userId=123
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
const userId = req.query.userId;
|
||||||
|
if (!userId) return res.status(400).json({ error: 'userId required' });
|
||||||
|
try {
|
||||||
|
const result = await db.getQuery()(
|
||||||
|
`SELECT id, user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata
|
||||||
|
FROM messages
|
||||||
|
WHERE user_id = $1
|
||||||
|
ORDER BY created_at ASC`,
|
||||||
|
[userId]
|
||||||
|
);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ error: 'DB error', details: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -138,4 +138,54 @@ router.get('/', async (req, res) => {
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// PATCH /api/users/:id — обновить имя и язык
|
||||||
|
router.patch('/:id', async (req, res) => {
|
||||||
|
const userId = req.params.id;
|
||||||
|
const { name, language } = req.body;
|
||||||
|
if (!name && !language) return res.status(400).json({ error: 'Nothing to update' });
|
||||||
|
try {
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
let idx = 1;
|
||||||
|
if (name !== undefined) {
|
||||||
|
// Разделяем имя на first_name и last_name (по пробелу)
|
||||||
|
const [firstName, ...lastNameArr] = name.split(' ');
|
||||||
|
fields.push(`first_name = $${idx++}`);
|
||||||
|
values.push(firstName);
|
||||||
|
fields.push(`last_name = $${idx++}`);
|
||||||
|
values.push(lastNameArr.join(' ') || null);
|
||||||
|
}
|
||||||
|
if (language !== undefined) {
|
||||||
|
fields.push(`preferred_language = $${idx++}`);
|
||||||
|
values.push(Array.isArray(language) ? JSON.stringify(language) : language);
|
||||||
|
}
|
||||||
|
values.push(userId);
|
||||||
|
const sql = `UPDATE users SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`;
|
||||||
|
const result = await db.getQuery()(sql, values);
|
||||||
|
res.json(result.rows[0]);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ error: 'DB error', details: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /api/users/:id — удалить контакт и все связанные данные
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
const userId = req.params.id;
|
||||||
|
const client = await db.getPool().connect();
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
await client.query('DELETE FROM user_identities WHERE user_id = $1', [userId]);
|
||||||
|
await client.query('DELETE FROM messages WHERE user_id = $1', [userId]);
|
||||||
|
// Добавьте другие связанные таблицы, если нужно
|
||||||
|
await client.query('DELETE FROM users WHERE id = $1', [userId]);
|
||||||
|
await client.query('COMMIT');
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (e) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
res.status(500).json({ error: 'DB error', details: e.message });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
320
frontend/src/components/ContactDetails.vue
Normal file
320
frontend/src/components/ContactDetails.vue
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<template>
|
||||||
|
<div class="contact-details-modal">
|
||||||
|
<div class="contact-details-header">
|
||||||
|
<h2>Детали контакта</h2>
|
||||||
|
<button class="close-btn" @click="$emit('close')">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="contact-info-block">
|
||||||
|
<div>
|
||||||
|
<strong>Имя:</strong>
|
||||||
|
<input v-model="editableName" class="edit-input" @blur="saveName" @keyup.enter="saveName" />
|
||||||
|
<span v-if="isSavingName" class="saving">Сохранение...</span>
|
||||||
|
</div>
|
||||||
|
<div><strong>Email:</strong> {{ contact.email || '-' }}</div>
|
||||||
|
<div><strong>Telegram:</strong> {{ contact.telegram || '-' }}</div>
|
||||||
|
<div><strong>Кошелек:</strong> {{ contact.wallet || '-' }}</div>
|
||||||
|
<div>
|
||||||
|
<strong>Язык:</strong>
|
||||||
|
<div class="multi-select">
|
||||||
|
<div class="selected-langs">
|
||||||
|
<span v-for="lang in selectedLanguages" :key="lang" class="lang-tag">
|
||||||
|
{{ getLanguageLabel(lang) }}
|
||||||
|
<span class="remove-tag" @click="removeLanguage(lang)">×</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
v-model="langInput"
|
||||||
|
@focus="showLangDropdown = true"
|
||||||
|
@input="showLangDropdown = true"
|
||||||
|
@keydown.enter.prevent="addLanguageFromInput"
|
||||||
|
class="lang-input"
|
||||||
|
placeholder="Добавить язык..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ul v-if="showLangDropdown" class="lang-dropdown">
|
||||||
|
<li
|
||||||
|
v-for="lang in filteredLanguages"
|
||||||
|
:key="lang.value"
|
||||||
|
@mousedown.prevent="addLanguage(lang.value)"
|
||||||
|
:class="{ selected: selectedLanguages.includes(lang.value) }"
|
||||||
|
>
|
||||||
|
{{ lang.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<span v-if="isSavingLangs" class="saving">Сохранение...</span>
|
||||||
|
</div>
|
||||||
|
<div><strong>Дата создания:</strong> {{ formatDate(contact.created_at) }}</div>
|
||||||
|
<div><strong>Дата последнего сообщения:</strong> {{ formatDate(lastMessageDate) }}</div>
|
||||||
|
<button class="delete-btn" @click="deleteContact">Удалить контакт</button>
|
||||||
|
</div>
|
||||||
|
<div class="messages-block">
|
||||||
|
<h3>История сообщений</h3>
|
||||||
|
<div v-if="isLoading" class="loading">Загрузка...</div>
|
||||||
|
<div v-else-if="messages.length === 0" class="empty">Нет сообщений</div>
|
||||||
|
<div v-else class="messages-list">
|
||||||
|
<Message v-for="msg in messages" :key="msg.id" :message="msg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch, computed } from 'vue';
|
||||||
|
import Message from './Message.vue';
|
||||||
|
import messagesService from '../services/messagesService';
|
||||||
|
import contactsService from '../services/contactsService';
|
||||||
|
const props = defineProps({
|
||||||
|
contact: { type: Object, required: true }
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['close', 'contact-deleted', 'contact-updated']);
|
||||||
|
const messages = ref([]);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const lastMessageDate = ref(null);
|
||||||
|
const editableName = ref(props.contact.name || '');
|
||||||
|
const isSavingName = ref(false);
|
||||||
|
const isSavingLangs = ref(false);
|
||||||
|
|
||||||
|
// --- Языки ---
|
||||||
|
const allLanguages = [
|
||||||
|
{ value: 'ru', label: 'Русский' },
|
||||||
|
{ value: 'en', label: 'English' },
|
||||||
|
{ value: 'de', label: 'Deutsch' },
|
||||||
|
{ value: 'fr', label: 'Français' },
|
||||||
|
{ value: 'es', label: 'Español' },
|
||||||
|
{ value: 'zh', label: '中文' },
|
||||||
|
{ value: 'ar', label: 'العربية' },
|
||||||
|
{ value: 'pt', label: 'Português' },
|
||||||
|
{ value: 'it', label: 'Italiano' },
|
||||||
|
{ value: 'ja', label: '日本語' },
|
||||||
|
{ value: 'tr', label: 'Türkçe' },
|
||||||
|
{ value: 'pl', label: 'Polski' },
|
||||||
|
{ value: 'uk', label: 'Українська' },
|
||||||
|
{ value: 'other', label: 'Другое' }
|
||||||
|
];
|
||||||
|
const selectedLanguages = ref(Array.isArray(props.contact.language) ? props.contact.language : (props.contact.language ? [props.contact.language] : []));
|
||||||
|
const langInput = ref('');
|
||||||
|
const showLangDropdown = ref(false);
|
||||||
|
const filteredLanguages = computed(() => {
|
||||||
|
const input = langInput.value.toLowerCase();
|
||||||
|
return allLanguages.filter(
|
||||||
|
l => !selectedLanguages.value.includes(l.value) && l.label.toLowerCase().includes(input)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
function getLanguageLabel(val) {
|
||||||
|
const found = allLanguages.find(l => l.value === val);
|
||||||
|
return found ? found.label : val;
|
||||||
|
}
|
||||||
|
function addLanguage(lang) {
|
||||||
|
if (!selectedLanguages.value.includes(lang)) {
|
||||||
|
selectedLanguages.value.push(lang);
|
||||||
|
saveLanguages();
|
||||||
|
}
|
||||||
|
langInput.value = '';
|
||||||
|
showLangDropdown.value = false;
|
||||||
|
}
|
||||||
|
function addLanguageFromInput() {
|
||||||
|
const found = filteredLanguages.value[0];
|
||||||
|
if (found) addLanguage(found.value);
|
||||||
|
}
|
||||||
|
function removeLanguage(lang) {
|
||||||
|
selectedLanguages.value = selectedLanguages.value.filter(l => l !== lang);
|
||||||
|
saveLanguages();
|
||||||
|
}
|
||||||
|
function saveLanguages() {
|
||||||
|
isSavingLangs.value = true;
|
||||||
|
contactsService.updateContact(props.contact.id, { language: selectedLanguages.value })
|
||||||
|
.then(() => emit('contact-updated'))
|
||||||
|
.finally(() => { isSavingLangs.value = false; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Имя ---
|
||||||
|
function saveName() {
|
||||||
|
if (editableName.value !== props.contact.name) {
|
||||||
|
isSavingName.value = true;
|
||||||
|
contactsService.updateContact(props.contact.id, { name: editableName.value })
|
||||||
|
.then(() => emit('contact-updated'))
|
||||||
|
.finally(() => { isSavingName.value = false; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Удаление ---
|
||||||
|
function deleteContact() {
|
||||||
|
if (confirm('Удалить контакт?')) {
|
||||||
|
contactsService.deleteContact(props.contact.id)
|
||||||
|
.then(() => emit('contact-deleted', props.contact.id))
|
||||||
|
.catch(() => alert('Ошибка удаления контакта'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
if (!date) return '-';
|
||||||
|
return new Date(date).toLocaleString();
|
||||||
|
}
|
||||||
|
async function loadMessages() {
|
||||||
|
if (!props.contact || !props.contact.id) return;
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
messages.value = await messagesService.getMessagesByUserId(props.contact.id);
|
||||||
|
if (messages.value.length > 0) {
|
||||||
|
lastMessageDate.value = messages.value[messages.value.length - 1].created_at;
|
||||||
|
} else {
|
||||||
|
lastMessageDate.value = null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
messages.value = [];
|
||||||
|
lastMessageDate.value = null;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(loadMessages);
|
||||||
|
watch(() => props.contact, loadMessages);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.contact-details-modal {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 32px rgba(0,0,0,0.12);
|
||||||
|
padding: 32px 24px 24px 24px;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 40px auto;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.contact-details-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #bbb;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.contact-info-block {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
.edit-input {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 8px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
.saving {
|
||||||
|
color: #17a2b8;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.delete-btn {
|
||||||
|
background: #dc3545;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 7px 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-top: 18px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.delete-btn:hover {
|
||||||
|
background: #b52a37;
|
||||||
|
}
|
||||||
|
.multi-select {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
.selected-langs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 36px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.lang-tag {
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #138496;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 0.97rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.remove-tag {
|
||||||
|
margin-left: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #888;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.remove-tag:hover {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
.lang-input {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 1rem;
|
||||||
|
min-width: 80px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.lang-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||||
|
z-index: 10;
|
||||||
|
min-width: 180px;
|
||||||
|
max-height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: 2px;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.lang-dropdown li {
|
||||||
|
padding: 7px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.lang-dropdown li.selected {
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #138496;
|
||||||
|
}
|
||||||
|
.lang-dropdown li:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
.messages-block {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 18px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||||
|
}
|
||||||
|
.messages-list {
|
||||||
|
max-height: 350px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.loading, .empty {
|
||||||
|
color: #888;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<th>Telegram</th>
|
<th>Telegram</th>
|
||||||
<th>Кошелек</th>
|
<th>Кошелек</th>
|
||||||
<th>Дата создания</th>
|
<th>Дата создания</th>
|
||||||
|
<th>Действие</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -21,6 +22,9 @@
|
|||||||
<td>{{ contact.telegram || '-' }}</td>
|
<td>{{ contact.telegram || '-' }}</td>
|
||||||
<td>{{ contact.wallet || '-' }}</td>
|
<td>{{ contact.wallet || '-' }}</td>
|
||||||
<td>{{ formatDate(contact.created_at) }}</td>
|
<td>{{ formatDate(contact.created_at) }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="details-btn" @click="showDetails(contact)">Подробнее</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -28,14 +32,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps } from 'vue';
|
import { defineProps, defineEmits } from 'vue';
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
contacts: { type: Array, required: true }
|
contacts: { type: Array, required: true }
|
||||||
});
|
});
|
||||||
|
const emit = defineEmits(['show-details']);
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
if (!date) return '-';
|
if (!date) return '-';
|
||||||
return new Date(date).toLocaleString();
|
return new Date(date).toLocaleString();
|
||||||
}
|
}
|
||||||
|
function showDetails(contact) {
|
||||||
|
emit('show-details', contact);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -119,4 +127,17 @@ function formatDate(date) {
|
|||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.details-btn {
|
||||||
|
background: #17a2b8;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.details-btn:hover {
|
||||||
|
background: #138496;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -7,5 +7,13 @@ export default {
|
|||||||
return res.data.contacts;
|
return res.data.contacts;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
},
|
||||||
|
async updateContact(id, data) {
|
||||||
|
const res = await api.patch(`/api/users/${id}`, data);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
async deleteContact(id) {
|
||||||
|
const res = await api.delete(`/api/users/${id}`);
|
||||||
|
return res.data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
9
frontend/src/services/messagesService.js
Normal file
9
frontend/src/services/messagesService.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async getMessagesByUserId(userId) {
|
||||||
|
if (!userId) return [];
|
||||||
|
const { data } = await axios.get(`/api/messages?userId=${userId}`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -20,7 +20,8 @@
|
|||||||
<i class="fas fa-address-book"></i> Подробнее
|
<i class="fas fa-address-book"></i> Подробнее
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ContactTable v-if="showContacts" :contacts="contacts" @close="showContacts = false" />
|
<ContactTable v-if="showContacts" :contacts="contacts" @close="showContacts = false" @show-details="openContactDetails" />
|
||||||
|
<ContactDetails v-if="showContactDetails" :contact="selectedContact" @close="showContactDetails = false" @contact-deleted="onContactDeleted" />
|
||||||
</div>
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -36,6 +37,7 @@ import dleService from '../services/dleService';
|
|||||||
import ContactTable from '../components/ContactTable.vue';
|
import ContactTable from '../components/ContactTable.vue';
|
||||||
import contactsService from '../services/contactsService.js';
|
import contactsService from '../services/contactsService.js';
|
||||||
import DleManagement from '../components/DleManagement.vue';
|
import DleManagement from '../components/DleManagement.vue';
|
||||||
|
import ContactDetails from '../components/ContactDetails.vue';
|
||||||
|
|
||||||
// Определяем props
|
// Определяем props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -58,6 +60,8 @@ const showDleManagement = ref(false);
|
|||||||
const showContacts = ref(false);
|
const showContacts = ref(false);
|
||||||
const contacts = ref([]);
|
const contacts = ref([]);
|
||||||
const isLoadingContacts = ref(false);
|
const isLoadingContacts = ref(false);
|
||||||
|
const selectedContact = ref(null);
|
||||||
|
const showContactDetails = ref(false);
|
||||||
|
|
||||||
// Функция для перехода на домашнюю страницу и открытия боковой панели
|
// Функция для перехода на домашнюю страницу и открытия боковой панели
|
||||||
const goToHomeAndShowSidebar = () => {
|
const goToHomeAndShowSidebar = () => {
|
||||||
@@ -135,6 +139,16 @@ async function loadContacts() {
|
|||||||
watch(showContacts, (val) => {
|
watch(showContacts, (val) => {
|
||||||
if (val) loadContacts();
|
if (val) loadContacts();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function openContactDetails(contact) {
|
||||||
|
selectedContact.value = contact;
|
||||||
|
showContactDetails.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onContactDeleted() {
|
||||||
|
showContactDetails.value = false;
|
||||||
|
loadContacts();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user