diff --git a/backend/communications/management/commands/README.md b/backend/communications/management/commands/README.md index f606c7f8..c91bb96c 100644 --- a/backend/communications/management/commands/README.md +++ b/backend/communications/management/commands/README.md @@ -85,6 +85,61 @@ cd backend --- +## check_chats + +Проверка и очистка чатов с некорректными типами. + +### Примеры использования + +#### Найти все чаты с некорректными типами + +```bash +.venv/bin/python manage.py check_chats --find +``` + +#### Проверить конкретный чат + +```bash +.venv/bin/python manage.py check_chats --check 6 +``` + +#### Удалить все чаты с некорректными типами (с подтверждением) + +```bash +.venv/bin/python manage.py check_chats --cleanup +``` + +#### Удалить все чаты с некорректными типами (без подтверждения) + +```bash +.venv/bin/python manage.py check_chats --cleanup --no-confirm +``` + +#### Удалить конкретный чат + +```bash +.venv/bin/python manage.py check_chats --delete 6 +``` + +### Допустимые типы чатов + +- `private` - Личный диалог +- `group` - Групповой чат +- `channel` - Канал +- `announcement` - Объявления +- `global` - Глобальный чат +- `comments` - Комментарии + +### Опции + +- `--find` - Найти все чаты с некорректными типами +- `--check CHAT_ID` - Проверить конкретный чат по ID +- `--cleanup` - Удалить все чаты с некорректными типами +- `--delete CHAT_ID` - Удалить конкретный чат по ID +- `--no-confirm` - Не запрашивать подтверждение при удалении + +--- + ## Другие команды - `init_reactions.py` - Инициализация реакций diff --git a/backend/communications/management/commands/check_chats.py b/backend/communications/management/commands/check_chats.py new file mode 100644 index 00000000..f94e809d --- /dev/null +++ b/backend/communications/management/commands/check_chats.py @@ -0,0 +1,161 @@ +""" +Management команда для проверки и очистки чатов с некорректными типами. + +Использование: + # Найти все чаты с некорректными типами + python manage.py check_chats --find + + # Проверить конкретный чат + python manage.py check_chats --check 6 + + # Удалить все чаты с некорректными типами + python manage.py check_chats --cleanup + + # Удалить конкретный чат + python manage.py check_chats --delete 6 +""" + +from django.core.management.base import BaseCommand, CommandError +from communications.models import Chat + +VALID_TYPES = ["private", "group", "channel", "announcement", "global", "comments"] + + +class Command(BaseCommand): + help = 'Проверка и очистка чатов с некорректными типами' + + def add_arguments(self, parser): + parser.add_argument( + '--find', + action='store_true', + help='Найти все чаты с некорректными типами', + ) + parser.add_argument( + '--check', + type=int, + metavar='CHAT_ID', + help='Проверить конкретный чат по ID', + ) + parser.add_argument( + '--cleanup', + action='store_true', + help='Удалить все чаты с некорректными типами', + ) + parser.add_argument( + '--delete', + type=int, + metavar='CHAT_ID', + help='Удалить конкретный чат по ID', + ) + parser.add_argument( + '--no-confirm', + action='store_true', + help='Не запрашивать подтверждение при удалении', + ) + + def handle(self, *args, **options): + if options['find']: + self.find_invalid_chats() + elif options['check']: + self.check_chat(options['check']) + elif options['cleanup']: + self.cleanup_invalid_chats(no_confirm=options['no_confirm']) + elif options['delete']: + self.delete_chat(options['delete'], no_confirm=options['no_confirm']) + else: + self.stdout.write(self.style.ERROR('Укажите действие: --find, --check, --cleanup или --delete')) + self.stdout.write('Используйте --help для справки') + + def find_invalid_chats(self): + """Находит все чаты с некорректными типами""" + invalid_chats = Chat.objects.exclude(type__in=VALID_TYPES) + + if not invalid_chats.exists(): + self.stdout.write(self.style.SUCCESS('✅ Все чаты имеют корректные типы')) + return + + count = invalid_chats.count() + self.stdout.write(self.style.WARNING(f'⚠️ Найдено {count} чатов с некорректными типами:\n')) + + for chat in invalid_chats: + self.stdout.write(f'ID {chat.id}:') + self.stdout.write(f' Название: {chat.name}') + self.stdout.write(f' Тип: {chat.type}') + self.stdout.write(f' Участников: {chat.participants.count()}') + self.stdout.write(f' Создан: {chat.created_at}') + self.stdout.write('') + + self.stdout.write(self.style.WARNING(f'Всего найдено: {count} чатов')) + self.stdout.write(self.style.NOTICE('Для удаления используйте: python manage.py check_chats --cleanup')) + + def check_chat(self, chat_id): + """Проверяет конкретный чат""" + try: + chat = Chat.objects.get(id=chat_id) + except Chat.DoesNotExist: + raise CommandError(f'❌ Чат с ID {chat_id} не найден') + + self.stdout.write(f'Чат ID {chat_id}:') + self.stdout.write(f' Название: {chat.name}') + self.stdout.write(f' Тип: {chat.type}') + self.stdout.write(f' Участников: {chat.participants.count()}') + self.stdout.write(f' Создан: {chat.created_at}') + + if chat.type not in VALID_TYPES: + self.stdout.write(self.style.ERROR(f'\n⚠️ Чат имеет некорректный тип: {chat.type}')) + self.stdout.write(self.style.NOTICE(f'Допустимые типы: {", ".join(VALID_TYPES)}')) + self.stdout.write(self.style.NOTICE(f'Для удаления используйте: python manage.py check_chats --delete {chat_id}')) + else: + self.stdout.write(self.style.SUCCESS(f'\n✅ Чат в порядке, тип корректен')) + + def cleanup_invalid_chats(self, no_confirm=False): + """Удаляет все чаты с некорректными типами""" + invalid_chats = Chat.objects.exclude(type__in=VALID_TYPES) + + if not invalid_chats.exists(): + self.stdout.write(self.style.SUCCESS('✅ Все чаты имеют корректные типы')) + return + + count = invalid_chats.count() + self.stdout.write(self.style.WARNING(f'⚠️ Найдено {count} чатов с некорректными типами\n')) + self.stdout.write('Чаты для удаления:') + + for chat in invalid_chats: + self.stdout.write(f' • ID {chat.id}: {chat.name} (тип: {chat.type})') + + if not no_confirm: + confirm = input(f'\n⚠️ Удалить {count} чатов? (yes/no): ') + if confirm.lower() not in ['yes', 'y', 'да']: + self.stdout.write(self.style.ERROR('❌ Отменено')) + return + + deleted, details = invalid_chats.delete() + self.stdout.write(self.style.SUCCESS(f'\n✅ Успешно удалено {deleted} объектов')) + + # Показываем детали удаления + if details: + self.stdout.write('\nУдалено объектов по типам:') + for model, count in details.items(): + self.stdout.write(f' • {model}: {count}') + + def delete_chat(self, chat_id, no_confirm=False): + """Удаляет конкретный чат""" + try: + chat = Chat.objects.get(id=chat_id) + except Chat.DoesNotExist: + raise CommandError(f'❌ Чат с ID {chat_id} не найден') + + self.stdout.write(f'Удаление чата ID {chat_id}:') + self.stdout.write(f' Название: {chat.name}') + self.stdout.write(f' Тип: {chat.type}') + self.stdout.write(f' Участников: {chat.participants.count()}') + + if not no_confirm: + confirm = input('\n⚠️ Подтвердите удаление (yes/no): ') + if confirm.lower() not in ['yes', 'y', 'да']: + self.stdout.write(self.style.ERROR('❌ Отменено')) + return + + chat_name = chat.name + chat.delete() + self.stdout.write(self.style.SUCCESS(f'\n✅ Чат "{chat_name}" (ID {chat_id}) успешно удален')) diff --git a/frontend/src/app/messages/page.tsx b/frontend/src/app/messages/page.tsx index b6c6abf7..a77fd735 100644 --- a/frontend/src/app/messages/page.tsx +++ b/frontend/src/app/messages/page.tsx @@ -5,7 +5,7 @@ import { AppShell } from "../../components/AppShell"; import { apiClient } from "@/lib/api"; import { resolveMediaUrl } from "@/lib/url"; import type { Chat } from "@/types/api"; -import { Search, MessageCircle, Pin, BellOff, Filter, Plus, X, Users, Globe, Radio, Upload, Image as ImageIcon } from "lucide-react"; +import { Search, MessageCircle, Pin, BellOff, Filter, Plus, X, Users, Globe, Radio, Upload, Image as ImageIcon, Megaphone, MessageSquare } from "lucide-react"; import Link from "next/link"; import Image from "next/image"; import { useUser } from "@/contexts/UserContext"; @@ -111,6 +111,27 @@ function getChatInitials(chat: Chat, currentUserId?: number, currentUser?: { fir .join("") || "Ч"; } +function getChatTypeIcon(chat: Chat) { + const chatType = chat.chat_type || chat.type; + switch (chatType) { + case 'global': + return ; + case 'channel': + return ; + case 'group': + return ; + case 'private': + case 'direct': + return ; + case 'announcement': + return ; + case 'comments': + return ; + default: + return null; + } +} + function formatTime(date?: string): string { if (!date) return ""; const d = new Date(date); @@ -685,9 +706,15 @@ export default function MessagesPage() { getChatInitials(chat, currentUserId, currentUserForMatch) )} + {/* Иконка типа чата */} + + {getChatTypeIcon(chat)} + + {/* Онлайн-статус */} {isChatOnline(chat, user?.id) ? ( ) : null} + {/* Счетчик непрочитанных */} {(chat.unread_count ?? 0) > 0 ? ( {chat.unread_count! > 99 ? '99+' : chat.unread_count}