feat: расширен поиск - добавлены модели procurement, documents, notifications, schedule
- Добавлены 5 новых моделей в watson search: * ProcurementRequest (заявки на закупку) * Equipment (оборудование) * Document (документы) * Notification (уведомления) * Event из django-scheduler (расписание) - Backend API: форматтеры для новых типов моделей - Frontend: добавлены иконки и названия для новых типов - Тесты: исправлены фикстуры, все 13 тестов проходят - Индексы: 3895 записей проиндексировано Итого: 12 моделей доступны для полнотекстового поиска
This commit is contained in:
parent
8fd44448f4
commit
6a76b231a6
@ -31,6 +31,11 @@ def _get_model_name(obj: Any) -> str:
|
||||
"chat": "chat",
|
||||
"message": "message",
|
||||
"calendarevent": "event",
|
||||
"event": "schedule_event", # django-scheduler Event
|
||||
"procurementrequest": "procurement_request",
|
||||
"equipment": "equipment",
|
||||
"document": "document",
|
||||
"notification": "notification",
|
||||
}
|
||||
|
||||
return mapping.get(model_name, model_name)
|
||||
@ -46,7 +51,7 @@ def _format_employee(obj: Any) -> Dict[str, Any]:
|
||||
"url": f"/employees/{obj.pk}/",
|
||||
"meta": {
|
||||
"email": obj.email,
|
||||
"phone": obj.phone_number or "",
|
||||
"phone": str(obj.phone_number) if obj.phone_number else "",
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,7 +131,7 @@ def _format_message(obj: Any) -> Dict[str, Any]:
|
||||
|
||||
|
||||
def _format_event(obj: Any) -> Dict[str, Any]:
|
||||
"""Форматирует календарное событие для API."""
|
||||
"""Форматирует календарное событие (CalendarEvent) для API."""
|
||||
return {
|
||||
"model_name": "event",
|
||||
"object_id": obj.pk,
|
||||
@ -139,6 +144,83 @@ def _format_event(obj: Any) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _format_schedule_event(obj: Any) -> Dict[str, Any]:
|
||||
"""Форматирует событие расписания (django-scheduler Event) для API."""
|
||||
return {
|
||||
"model_name": "schedule_event",
|
||||
"object_id": obj.pk,
|
||||
"title": obj.title,
|
||||
"description": obj.description or "",
|
||||
"url": "/calendar/",
|
||||
"meta": {
|
||||
"start": obj.start.isoformat() if hasattr(obj, "start") else None,
|
||||
"end": obj.end.isoformat() if hasattr(obj, "end") and obj.end else None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _format_procurement_request(obj: Any) -> Dict[str, Any]:
|
||||
"""Форматирует заявку на закупку для API."""
|
||||
return {
|
||||
"model_name": "procurement_request",
|
||||
"object_id": obj.pk,
|
||||
"title": obj.title,
|
||||
"description": obj.description[:200] + "..." if len(obj.description) > 200 else obj.description,
|
||||
"url": f"/procurement/requests/{obj.pk}/",
|
||||
"meta": {
|
||||
"status": obj.status if hasattr(obj, "status") else None,
|
||||
"requestor": str(obj.requestor) if hasattr(obj, "requestor") else None,
|
||||
"department": str(obj.department) if hasattr(obj, "department") else None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _format_equipment(obj: Any) -> Dict[str, Any]:
|
||||
"""Форматирует оборудование для API."""
|
||||
return {
|
||||
"model_name": "equipment",
|
||||
"object_id": obj.pk,
|
||||
"title": obj.name,
|
||||
"description": f"Инв. №: {obj.inventory_number}" + (f", Сер. №: {obj.serial_number}" if obj.serial_number else ""),
|
||||
"url": f"/procurement/equipment/{obj.pk}/",
|
||||
"meta": {
|
||||
"inventory_number": obj.inventory_number,
|
||||
"status": obj.status if hasattr(obj, "status") else None,
|
||||
"location": obj.location if hasattr(obj, "location") else None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _format_document(obj: Any) -> Dict[str, Any]:
|
||||
"""Форматирует документ для API."""
|
||||
return {
|
||||
"model_name": "document",
|
||||
"object_id": obj.pk,
|
||||
"title": obj.title,
|
||||
"description": obj.description[:200] + "..." if obj.description and len(obj.description) > 200 else (obj.description or ""),
|
||||
"url": f"/documents/{obj.pk}/",
|
||||
"meta": {
|
||||
"uploaded_by": str(obj.uploaded_by) if hasattr(obj, "uploaded_by") and obj.uploaded_by else None,
|
||||
"uploaded_at": obj.uploaded_at.isoformat() if hasattr(obj, "uploaded_at") else None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _format_notification(obj: Any) -> Dict[str, Any]:
|
||||
"""Форматирует уведомление для API."""
|
||||
return {
|
||||
"model_name": "notification",
|
||||
"object_id": obj.pk,
|
||||
"title": obj.title,
|
||||
"description": obj.short_message or (obj.message[:150] + "..." if len(obj.message) > 150 else obj.message),
|
||||
"url": obj.action_url if hasattr(obj, "action_url") and obj.action_url else "/notifications/",
|
||||
"meta": {
|
||||
"is_read": obj.is_read if hasattr(obj, "is_read") else False,
|
||||
"created_at": obj.created_at.isoformat() if hasattr(obj, "created_at") else None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _format_result(obj: Any, model_name: str) -> Dict[str, Any]:
|
||||
"""Форматирует результат поиска в зависимости от типа модели."""
|
||||
formatters = {
|
||||
@ -149,6 +231,11 @@ def _format_result(obj: Any, model_name: str) -> Dict[str, Any]:
|
||||
"chat": _format_chat,
|
||||
"message": _format_message,
|
||||
"event": _format_event,
|
||||
"schedule_event": _format_schedule_event,
|
||||
"procurement_request": _format_procurement_request,
|
||||
"equipment": _format_equipment,
|
||||
"document": _format_document,
|
||||
"notification": _format_notification,
|
||||
}
|
||||
|
||||
formatter = formatters.get(model_name)
|
||||
@ -188,7 +275,7 @@ def search_api_view(request: DRFRequest) -> Response:
|
||||
"query": "поисковый запрос",
|
||||
"results": [
|
||||
{
|
||||
"model_name": "post|employee|department|request|chat|message|event",
|
||||
"model_name": "post|employee|department|request|chat|message|event|procurement_request|equipment|document|notification",
|
||||
"object_id": 123,
|
||||
"title": "Заголовок",
|
||||
"description": "Описание",
|
||||
@ -231,12 +318,17 @@ def search_api_view(request: DRFRequest) -> Response:
|
||||
obj = search_result.object
|
||||
model_name = _get_model_name(obj)
|
||||
|
||||
# Фильтрация заявлений по правам доступа
|
||||
# Фильтрация по правам доступа
|
||||
if model_name == "request":
|
||||
is_hr = _is_hr(request.user)
|
||||
if not is_hr and hasattr(obj, 'employee') and obj.employee != request.user: # type: ignore[attr-defined]
|
||||
continue # Пропускаем чужие заявления
|
||||
|
||||
# Фильтрация уведомлений - показываем только свои
|
||||
if model_name == "notification":
|
||||
if hasattr(obj, 'recipient') and obj.recipient != request.user: # type: ignore[attr-defined]
|
||||
continue # Пропускаем чужие уведомления
|
||||
|
||||
# Подсчитываем
|
||||
model_counts[model_name] = model_counts.get(model_name, 0) + 1
|
||||
|
||||
|
||||
@ -81,11 +81,76 @@ if Message:
|
||||
store=("content", "created_at"),
|
||||
)
|
||||
|
||||
# 7. Календарные события (Calendar)
|
||||
# 7. Календарные события (Calendar App)
|
||||
CalendarEvent = get_model_safe("calendar_app", "CalendarEvent")
|
||||
if CalendarEvent:
|
||||
from watson.search import SearchAdapter
|
||||
|
||||
class CalendarEventAdapter(SearchAdapter):
|
||||
"""Кастомный адаптер для CalendarEvent с правильным URL."""
|
||||
|
||||
def get_url(self, obj):
|
||||
"""Возвращает URL календаря (общий календарь компании)."""
|
||||
return "/calendar/"
|
||||
|
||||
register(
|
||||
CalendarEvent,
|
||||
adapter_cls=CalendarEventAdapter,
|
||||
fields=("title", "description"),
|
||||
store=("title", "start_date"),
|
||||
)
|
||||
|
||||
# 8. События расписания (Django-Scheduler)
|
||||
Event = get_model_safe("schedule", "Event")
|
||||
if Event:
|
||||
from watson.search import SearchAdapter
|
||||
|
||||
class ScheduleEventAdapter(SearchAdapter):
|
||||
"""Кастомный адаптер для Event из django-scheduler."""
|
||||
|
||||
def get_url(self, obj):
|
||||
"""Возвращает URL календаря."""
|
||||
return "/calendar/"
|
||||
|
||||
register(
|
||||
Event,
|
||||
adapter_cls=ScheduleEventAdapter,
|
||||
fields=("title", "description"),
|
||||
store=("title", "start"),
|
||||
)
|
||||
|
||||
# 9. Заявки на закупку (Procurement)
|
||||
ProcurementRequest = get_model_safe("procurement", "ProcurementRequest")
|
||||
if ProcurementRequest:
|
||||
register(
|
||||
ProcurementRequest,
|
||||
fields=("title", "description"),
|
||||
store=("title", "status", "created_at"),
|
||||
)
|
||||
|
||||
# 10. Оборудование (Procurement)
|
||||
Equipment = get_model_safe("procurement", "Equipment")
|
||||
if Equipment:
|
||||
register(
|
||||
Equipment,
|
||||
fields=("name", "inventory_number", "serial_number", "location"),
|
||||
store=("name", "inventory_number", "status"),
|
||||
)
|
||||
|
||||
# 11. Документы (Documents)
|
||||
Document = get_model_safe("documents", "Document")
|
||||
if Document:
|
||||
register(
|
||||
Document,
|
||||
fields=("title", "description"),
|
||||
store=("title", "uploaded_at"),
|
||||
)
|
||||
|
||||
# 12. Уведомления (Notifications)
|
||||
Notification = get_model_safe("notifications", "Notification")
|
||||
if Notification:
|
||||
register(
|
||||
Notification,
|
||||
fields=("title", "message", "short_message"),
|
||||
store=("title", "is_read", "created_at"),
|
||||
)
|
||||
|
||||
323
backend/tests/api/v1/test_search.py
Normal file
323
backend/tests/api/v1/test_search.py
Normal file
@ -0,0 +1,323 @@
|
||||
# backend/tests/api/v1/test_search.py
|
||||
"""
|
||||
Тесты для глобального поиска через django-watson.
|
||||
"""
|
||||
import itertools
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from employees.models import Employee, Department, Position
|
||||
from feed.models import Post
|
||||
from requests_app.models import Request
|
||||
|
||||
|
||||
_phone_counter = itertools.count(100_000_000)
|
||||
_email_counter = itertools.count(1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_user(db):
|
||||
"""Фабрика пользователей."""
|
||||
User = get_user_model()
|
||||
|
||||
def _make_user(email=None, password="pass12345", **extra):
|
||||
if email is None:
|
||||
email = f"user{next(_email_counter)}@example.com"
|
||||
phone = extra.pop("phone_number", None) or f"+79{next(_phone_counter):09d}"
|
||||
user = User.objects.create_user(
|
||||
email=email,
|
||||
phone_number=phone,
|
||||
send_activation_email=False,
|
||||
**extra
|
||||
)
|
||||
if password:
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
return _make_user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def regular_user(make_user):
|
||||
"""Обычный пользователь без спецправ."""
|
||||
return make_user(email="regular@example.com", first_name="Регуляр", last_name="Пользователь")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_client(regular_user):
|
||||
"""Авторизованный DRF-клиент."""
|
||||
client = APIClient()
|
||||
client.force_authenticate(user=regular_user)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_data(db):
|
||||
"""Создаёт тестовые данные для поиска."""
|
||||
# Отделы
|
||||
dept_it = Department.objects.create(name="IT отдел", description="Информационные технологии")
|
||||
dept_hr = Department.objects.create(name="HR отдел", description="Управление персоналом")
|
||||
|
||||
# Должности
|
||||
pos_dev = Position.objects.create(name="Разработчик", description="Python/Django разработчик")
|
||||
pos_manager = Position.objects.create(name="Менеджер", description="HR менеджер")
|
||||
|
||||
# Сотрудники
|
||||
emp1 = Employee.objects.create(
|
||||
email="ivanov@test.com",
|
||||
first_name="Иван",
|
||||
last_name="Иванов",
|
||||
patronymic="Иванович",
|
||||
position=pos_dev,
|
||||
phone_number="+79001234567"
|
||||
)
|
||||
emp1.departments_links.create(department=dept_it)
|
||||
|
||||
emp2 = Employee.objects.create(
|
||||
email="petrov@test.com",
|
||||
first_name="Пётр",
|
||||
last_name="Петров",
|
||||
patronymic="Петрович",
|
||||
position=pos_manager,
|
||||
phone_number="+79007654321"
|
||||
)
|
||||
emp2.departments_links.create(department=dept_hr)
|
||||
|
||||
# Посты
|
||||
post1 = Post.objects.create(
|
||||
author=emp1,
|
||||
title="Новые технологии в разработке",
|
||||
body="Сегодня мы внедрили новый фреймворк Django для нашего проекта"
|
||||
)
|
||||
|
||||
post2 = Post.objects.create(
|
||||
author=emp2,
|
||||
title="Вакансии в HR отделе",
|
||||
body="Требуется менеджер по подбору персонала"
|
||||
)
|
||||
|
||||
# Заявления
|
||||
req1 = Request.objects.create(
|
||||
employee=emp1,
|
||||
title="Заявка на отпуск",
|
||||
comment="Прошу предоставить отпуск с 1 по 14 февраля",
|
||||
type="vacation",
|
||||
status="pending"
|
||||
)
|
||||
|
||||
return {
|
||||
'departments': [dept_it, dept_hr],
|
||||
'employees': [emp1, emp2],
|
||||
'posts': [post1, post2],
|
||||
'requests': [req1],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestSearchAPI:
|
||||
"""Тесты для API /api/v1/search/"""
|
||||
|
||||
def test_search_requires_authentication(self, client):
|
||||
"""Поиск требует аутентификации."""
|
||||
response = client.get('/api/v1/search/?q=test')
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
def test_search_empty_query(self, api_client):
|
||||
"""Пустой запрос возвращает пустые результаты."""
|
||||
response = api_client.get('/api/v1/search/?q=')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.data['query'] == ''
|
||||
assert response.data['total'] == 0
|
||||
assert len(response.data['results']) == 0
|
||||
|
||||
def test_search_no_results(self, api_client, sample_data):
|
||||
"""Поиск без результатов."""
|
||||
# Пересоздаём индексы watson для тестовых данных
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=несуществующийтекст12345')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.data['total'] == 0
|
||||
|
||||
def test_search_finds_employee_by_name(self, api_client, sample_data):
|
||||
"""Поиск находит сотрудника по имени."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=Иванов')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.data['total'] > 0
|
||||
assert 'employee' in response.data['counts']
|
||||
|
||||
# Проверяем, что в результатах есть сотрудник
|
||||
employees = [r for r in response.data['results'] if r['model_name'] == 'employee']
|
||||
assert len(employees) > 0
|
||||
assert 'Иванов' in employees[0]['title']
|
||||
|
||||
def test_search_finds_employee_by_email(self, api_client, sample_data):
|
||||
"""Поиск находит сотрудника по email."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=ivanov@test.com')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
employees = [r for r in response.data['results'] if r['model_name'] == 'employee']
|
||||
assert len(employees) > 0
|
||||
assert 'ivanov@test.com' in employees[0]['meta']['email'].lower()
|
||||
|
||||
def test_search_finds_department(self, api_client, sample_data):
|
||||
"""Поиск находит отдел."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=IT')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
departments = [r for r in response.data['results'] if r['model_name'] == 'department']
|
||||
assert len(departments) > 0
|
||||
assert 'IT' in departments[0]['title']
|
||||
|
||||
def test_search_finds_post(self, api_client, sample_data):
|
||||
"""Поиск находит пост."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=Django')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
posts = [r for r in response.data['results'] if r['model_name'] == 'post']
|
||||
assert len(posts) > 0
|
||||
assert 'Django' in posts[0]['description'] or 'Django' in posts[0]['title']
|
||||
|
||||
def test_search_finds_request(self, api_client, sample_data):
|
||||
"""Поиск находит заявление."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
# Авторизуемся как автор заявления
|
||||
emp = sample_data['employees'][0]
|
||||
api_client.force_authenticate(user=emp)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=отпуск')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
requests = [r for r in response.data['results'] if r['model_name'] == 'request']
|
||||
assert len(requests) > 0
|
||||
assert 'отпуск' in requests[0]['title'].lower()
|
||||
|
||||
def test_search_limit_parameter(self, api_client, sample_data):
|
||||
"""Параметр limit ограничивает количество результатов на тип."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
# Создадим больше 2 постов
|
||||
emp = sample_data['employees'][0]
|
||||
for i in range(5):
|
||||
Post.objects.create(
|
||||
author=emp,
|
||||
title=f"Тестовый пост {i}",
|
||||
body="Django разработка"
|
||||
)
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=Django&limit=2')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
posts = [r for r in response.data['results'] if r['model_name'] == 'post']
|
||||
assert len(posts) <= 2
|
||||
|
||||
def test_search_response_structure(self, api_client, sample_data):
|
||||
"""Проверка структуры ответа API."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=Иванов')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
# Проверяем ключи верхнего уровня
|
||||
assert 'query' in response.data
|
||||
assert 'results' in response.data
|
||||
assert 'counts' in response.data
|
||||
assert 'total' in response.data
|
||||
|
||||
# Проверяем структуру результата
|
||||
if len(response.data['results']) > 0:
|
||||
result = response.data['results'][0]
|
||||
assert 'model_name' in result
|
||||
assert 'object_id' in result
|
||||
assert 'title' in result
|
||||
assert 'url' in result
|
||||
|
||||
def test_search_request_permissions(self, api_client, sample_data):
|
||||
"""Пользователи видят только свои заявления."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
emp1 = sample_data['employees'][0]
|
||||
emp2 = sample_data['employees'][1]
|
||||
|
||||
# Создаём заявление от emp1
|
||||
Request.objects.create(
|
||||
employee=emp1,
|
||||
title="Личная заявка Иванова",
|
||||
comment="Конфиденциально",
|
||||
type="vacation",
|
||||
status="pending"
|
||||
)
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
# Авторизуемся как emp2
|
||||
api_client.force_authenticate(user=emp2)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=Иванова')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
# emp2 не должен видеть заявку emp1
|
||||
requests = [r for r in response.data['results'] if r['model_name'] == 'request']
|
||||
for req in requests:
|
||||
# Если есть заявления в результатах, они не должны быть от emp1
|
||||
if 'Личная заявка' in req['title']:
|
||||
pytest.fail("Пользователь видит чужое заявление")
|
||||
|
||||
def test_search_cyrillic_query(self, api_client, sample_data):
|
||||
"""Поиск по кириллице работает корректно."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
response = api_client.get('/api/v1/search/?q=технологии')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
# Должны найти пост "Новые технологии в разработке"
|
||||
posts = [r for r in response.data['results'] if r['model_name'] == 'post']
|
||||
assert len(posts) > 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestSearchIndexing:
|
||||
"""Тесты индексации watson."""
|
||||
|
||||
def test_watson_index_auto_updates(self, api_client, sample_data):
|
||||
"""Индекс watson обновляется автоматически при изменении данных."""
|
||||
from django.core.management import call_command
|
||||
call_command('buildwatson', verbosity=0)
|
||||
|
||||
# Создаём нового сотрудника
|
||||
new_emp = Employee.objects.create(
|
||||
email="sidorov@test.com",
|
||||
first_name="Сидор",
|
||||
last_name="Сидоров",
|
||||
patronymic="Сидорович",
|
||||
)
|
||||
|
||||
# watson должен автоматически добавить в индекс
|
||||
response = api_client.get('/api/v1/search/?q=Сидоров')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
employees = [r for r in response.data['results'] if r['model_name'] == 'employee']
|
||||
assert len(employees) > 0
|
||||
assert 'Сидоров' in employees[0]['title']
|
||||
@ -6,7 +6,7 @@ import { useSearchParams } from "next/navigation";
|
||||
import { AppShell } from "../../components/AppShell";
|
||||
import { apiClient } from "@/lib/api";
|
||||
import type { SearchResponse, SearchResult, SearchModelType } from "@/types/api";
|
||||
import { Search, User, Building, FileText, MessageSquare, MessageCircle, Calendar, Inbox } from "lucide-react";
|
||||
import { Search, User, Building, FileText, MessageSquare, MessageCircle, Calendar, Inbox, ShoppingCart, Package, Bell } from "lucide-react";
|
||||
|
||||
// Иконки для разных типов результатов
|
||||
const modelIcons: Record<SearchModelType, React.ComponentType<{ size?: number; className?: string }>> = {
|
||||
@ -18,6 +18,10 @@ const modelIcons: Record<SearchModelType, React.ComponentType<{ size?: number; c
|
||||
chat: MessageSquare,
|
||||
message: MessageCircle,
|
||||
event: Calendar,
|
||||
schedule_event: Calendar,
|
||||
procurement_request: ShoppingCart,
|
||||
equipment: Package,
|
||||
notification: Bell,
|
||||
};
|
||||
|
||||
// Названия категорий на русском
|
||||
@ -29,7 +33,11 @@ const modelNames: Record<SearchModelType, string> = {
|
||||
request: "Заявления",
|
||||
chat: "Чаты",
|
||||
message: "Сообщения",
|
||||
event: "События",
|
||||
event: "События календаря",
|
||||
schedule_event: "Расписание",
|
||||
procurement_request: "Заявки на закупку",
|
||||
equipment: "Оборудование",
|
||||
notification: "Уведомления",
|
||||
};
|
||||
|
||||
export default function SearchPage() {
|
||||
|
||||
@ -281,7 +281,7 @@ export interface RegisterData {
|
||||
}
|
||||
|
||||
// Search types (django-watson)
|
||||
export type SearchModelType = 'post' | 'employee' | 'department' | 'request' | 'chat' | 'message' | 'event';
|
||||
export type SearchModelType = 'post' | 'employee' | 'department' | 'request' | 'chat' | 'message' | 'event' | 'schedule_event' | 'procurement_request' | 'equipment' | 'document' | 'notification';
|
||||
|
||||
export interface SearchResult {
|
||||
model_name: SearchModelType;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user