Добавлена загрузка/замена/удаление фото сотрудника с автоматическим удалением старых файлов
This commit is contained in:
parent
750a2c5da8
commit
b636e13382
@ -0,0 +1,98 @@
|
||||
# Generated by Django 5.2.4 on 2025-11-30 07:40
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('communications', '0013_messengerintegration_messengeraccount_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Сначала удаляем индексы, которые зависят от полей
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengermessage',
|
||||
name='communicati_integra_af291e_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengermessage',
|
||||
name='communicati_sender__496a25_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengermessage',
|
||||
name='communicati_status_9d880b_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengermessage',
|
||||
name='communicati_synced__4496a8_idx',
|
||||
),
|
||||
# Удаляем unique_together
|
||||
migrations.AlterUniqueTogether(
|
||||
name='messengermessage',
|
||||
unique_together=None,
|
||||
),
|
||||
# Теперь удаляем поля
|
||||
migrations.RemoveField(
|
||||
model_name='messengermessage',
|
||||
name='sender_account',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='messengermessage',
|
||||
name='recipient_account',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='messengermessage',
|
||||
name='integration',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='messengermessage',
|
||||
name='internal_message',
|
||||
),
|
||||
# Удаляем индексы MessengerAccount
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengeraccount',
|
||||
name='communicati_user_id_acfa5e_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengeraccount',
|
||||
name='communicati_externa_2dfbcf_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengeraccount',
|
||||
name='communicati_phone_n_8df544_idx',
|
||||
),
|
||||
# Удаляем unique_together MessengerAccount
|
||||
migrations.AlterUniqueTogether(
|
||||
name='messengeraccount',
|
||||
unique_together=None,
|
||||
),
|
||||
# Удаляем поля MessengerAccount
|
||||
migrations.RemoveField(
|
||||
model_name='messengeraccount',
|
||||
name='user',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='messengeraccount',
|
||||
name='integration',
|
||||
),
|
||||
# Удаляем индексы MessengerIntegration
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengerintegration',
|
||||
name='communicati_messeng_c8457c_idx',
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='messengerintegration',
|
||||
name='communicati_status_7fb887_idx',
|
||||
),
|
||||
# Теперь можно удалить модели
|
||||
migrations.DeleteModel(
|
||||
name='MessengerMessage',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='MessengerAccount',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='MessengerIntegration',
|
||||
),
|
||||
]
|
||||
@ -22,11 +22,6 @@ urlpatterns = [
|
||||
name="start_private_chat"
|
||||
),
|
||||
|
||||
# Messenger Views
|
||||
path("messengers/whatsapp/", views.whatsapp_view, name="whatsapp"),
|
||||
path("messengers/telegram/", views.telegram_view, name="telegram"),
|
||||
path("messengers/wechat/", views.wechat_view, name="wechat"),
|
||||
|
||||
# API Endpoints
|
||||
# Chat management
|
||||
path("api/chat/create/", api_views.create_chat, name="api_create_chat"),
|
||||
|
||||
@ -452,38 +452,3 @@ def chat_mark_read(request, pk: int):
|
||||
return JsonResponse(
|
||||
{"ok": True, "last_read_at": int(ts.timestamp() * 1000)}
|
||||
)
|
||||
|
||||
|
||||
# ===== Messenger Views =====
|
||||
|
||||
@login_required
|
||||
def whatsapp_view(request):
|
||||
"""WhatsApp интеграция"""
|
||||
from django.shortcuts import render
|
||||
return render(request, 'communications/messengers/whatsapp.html', {
|
||||
'messenger_name': 'WhatsApp',
|
||||
'messenger_icon': 'bi-whatsapp',
|
||||
'messenger_color': '#25D366'
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def telegram_view(request):
|
||||
"""Telegram интеграция"""
|
||||
from django.shortcuts import render
|
||||
return render(request, 'communications/messengers/telegram.html', {
|
||||
'messenger_name': 'Telegram',
|
||||
'messenger_icon': 'bi-telegram',
|
||||
'messenger_color': '#0088cc'
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def wechat_view(request):
|
||||
"""WeChat интеграция"""
|
||||
from django.shortcuts import render
|
||||
return render(request, 'communications/messengers/wechat.html', {
|
||||
'messenger_name': 'WeChat',
|
||||
'messenger_icon': 'bi-wechat',
|
||||
'messenger_color': '#09B83E'
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
# users/signals.py
|
||||
import os
|
||||
from django.db.models.signals import post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
@ -9,7 +10,32 @@ from bots.models import BotSubscriber
|
||||
|
||||
|
||||
@receiver(pre_save, sender=Employee)
|
||||
def unbind_bot_subscriber_on_contact_change(sender, instance: Employee, **kwargs):
|
||||
def delete_old_avatar_on_change(sender, instance: Employee, **kwargs):
|
||||
"""Удаляем старый файл аватара при замене на новый или при удалении."""
|
||||
if instance.pk is None:
|
||||
# Новый пользователь - нечего удалять
|
||||
return
|
||||
|
||||
try:
|
||||
old = sender.objects.get(pk=instance.pk)
|
||||
except sender.DoesNotExist:
|
||||
return
|
||||
|
||||
# Если аватар изменился или удален
|
||||
if old.avatar and old.avatar != instance.avatar:
|
||||
# Удаляем старый файл с диска
|
||||
if os.path.isfile(old.avatar.path):
|
||||
try:
|
||||
os.remove(old.avatar.path)
|
||||
except Exception as e:
|
||||
# Логируем ошибку, но не прерываем сохранение
|
||||
print(f"Error deleting old avatar: {e}")
|
||||
|
||||
|
||||
@receiver(pre_save, sender=Employee)
|
||||
def unbind_bot_subscriber_on_contact_change(
|
||||
sender, instance: Employee, **kwargs
|
||||
):
|
||||
# Игнорируем новых пользователей
|
||||
if instance.pk is None:
|
||||
return
|
||||
|
||||
@ -187,39 +187,7 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Мессенджеры - ссылки */
|
||||
.chat-type-btn[data-messenger] {
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.chat-type-btn[data-messenger]:hover {
|
||||
background-color: var(--bs-light) !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.chat-type-btn[data-messenger] i {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.chat-type-btn[data-messenger]:hover i {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Цвета иконок мессенджеров */
|
||||
.chat-type-btn[data-messenger="whatsapp"] i {
|
||||
color: #25D366 !important;
|
||||
}
|
||||
|
||||
.chat-type-btn[data-messenger="telegram"] i {
|
||||
color: #0088cc !important;
|
||||
}
|
||||
|
||||
.chat-type-btn[data-messenger="wechat"] i {
|
||||
color: #09B83E !important;
|
||||
}
|
||||
|
||||
.chat-type-badge {
|
||||
.chat-type-btn .badge {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
|
||||
@ -177,20 +177,43 @@ export function initEmployeeForm(options = {}) {
|
||||
const skills = collectSkillsPayload();
|
||||
if (skills) payload.skills_ids = skills;
|
||||
|
||||
// avatar (Base64) / clear
|
||||
const avatarFile = document.getElementById('f_avatar')?.files?.[0] || null;
|
||||
const clearAvatar = form.querySelector('input[name="clear_avatar"]')?.checked;
|
||||
// avatar (файл через FormData)
|
||||
const avatarInput = document.getElementById('avatarInput');
|
||||
const avatarFile = avatarInput?.files?.[0] || null;
|
||||
|
||||
let body;
|
||||
let headers = {
|
||||
'X-CSRFToken': form.querySelector('[name="csrfmiddlewaretoken"]')?.value || ''
|
||||
};
|
||||
|
||||
if (avatarFile) {
|
||||
try {
|
||||
const dataUrl = await readFileAsDataURL(avatarFile);
|
||||
payload.avatar = dataUrl; // Base64ImageField ожидает data URL
|
||||
} catch (_) {}
|
||||
} else if (clearAvatar) {
|
||||
payload.avatar = ''; // Очистка
|
||||
// Если есть файл - используем FormData
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', avatarFile);
|
||||
|
||||
// Добавляем остальные поля
|
||||
Object.entries(payload).forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => formData.append(key, v));
|
||||
} else {
|
||||
formData.append(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (skills) {
|
||||
skills.forEach(id => formData.append('skills_ids', id));
|
||||
}
|
||||
|
||||
body = formData;
|
||||
// Не устанавливаем Content-Type для FormData - браузер сам добавит boundary
|
||||
} else {
|
||||
// Если файла нет - используем JSON
|
||||
headers['Content-Type'] = 'application/json';
|
||||
body = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
// Если нечего отправлять — просто обновим UI
|
||||
if (Object.keys(payload).length === 0) {
|
||||
if (Object.keys(payload).length === 0 && !avatarFile) {
|
||||
setSubmitting(false);
|
||||
if (window.bootstrap) {
|
||||
try {
|
||||
@ -201,16 +224,10 @@ export function initEmployeeForm(options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
const csrftoken = form.querySelector('input[name=csrfmiddlewaretoken]')?.value || '';
|
||||
const resp = await fetch(endpoint, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': csrftoken
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
headers: headers,
|
||||
body: body,
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
|
||||
@ -61,23 +61,6 @@
|
||||
<i class="bi-megaphone fs-4 d-block"></i>
|
||||
<span class="badge rounded-pill bg-secondary" data-count="announcement">0</span>
|
||||
</button>
|
||||
|
||||
{# Разделитель #}
|
||||
<div class="list-group-item p-0" style="height: 1px; background-color: var(--bs-border-color);"></div>
|
||||
|
||||
{# Мессенджеры #}
|
||||
<a href="{% url 'communications:whatsapp' %}" class="list-group-item list-group-item-action text-center py-3 chat-type-btn"
|
||||
title="WhatsApp" data-messenger="whatsapp">
|
||||
<i class="bi-whatsapp fs-4 d-block text-success"></i>
|
||||
</a>
|
||||
<a href="{% url 'communications:telegram' %}" class="list-group-item list-group-item-action text-center py-3 chat-type-btn"
|
||||
title="Telegram" data-messenger="telegram">
|
||||
<i class="bi-telegram fs-4 d-block text-primary"></i>
|
||||
</a>
|
||||
<a href="{% url 'communications:wechat' %}" class="list-group-item list-group-item-action text-center py-3 chat-type-btn"
|
||||
title="WeChat" data-messenger="wechat">
|
||||
<i class="bi-wechat fs-4 d-block text-success"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
{# backend/templates/communications/messengers/telegram.html #}
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ messenger_name }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
|
||||
<style>
|
||||
.messenger-iframe-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100vh - 180px);
|
||||
min-height: 600px;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.messenger-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.messenger-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.messenger-loading i {
|
||||
font-size: 3rem;
|
||||
color: {{ messenger_color }};
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-3">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% include "includes/components/page_header.html" with icon=messenger_icon title=messenger_name %}
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="messenger-iframe-container">
|
||||
<div class="messenger-loading">
|
||||
<i class="bi-arrow-repeat"></i>
|
||||
<p class="mt-2 text-muted">Загрузка {{ messenger_name }}...</p>
|
||||
</div>
|
||||
<iframe
|
||||
class="messenger-iframe"
|
||||
src="https://web.telegram.org/"
|
||||
allow="microphone; camera"
|
||||
onload="document.querySelector('.messenger-loading').style.display='none'">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{% url 'communications:chat_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="bi-arrow-left"></i> Вернуться к чатам
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,94 +0,0 @@
|
||||
{# backend/templates/communications/messengers/wechat.html #}
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ messenger_name }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
|
||||
<style>
|
||||
.messenger-iframe-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100vh - 180px);
|
||||
min-height: 600px;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.messenger-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.messenger-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.messenger-loading i {
|
||||
font-size: 3rem;
|
||||
color: {{ messenger_color }};
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.wechat-notice {
|
||||
padding: 1rem;
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-3">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% include "includes/components/page_header.html" with icon=messenger_icon title=messenger_name %}
|
||||
|
||||
<div class="wechat-notice">
|
||||
<i class="bi-info-circle text-warning"></i>
|
||||
<strong>Внимание:</strong> WeChat не имеет полноценной веб-версии.
|
||||
Используйте <a href="https://www.wechat.com/en/" target="_blank">официальное приложение</a>.
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="messenger-iframe-container">
|
||||
<div class="messenger-loading">
|
||||
<i class="bi-arrow-repeat"></i>
|
||||
<p class="mt-2 text-muted">Загрузка {{ messenger_name }}...</p>
|
||||
</div>
|
||||
<iframe
|
||||
class="messenger-iframe"
|
||||
src="https://www.wechat.com/"
|
||||
allow="microphone; camera"
|
||||
onload="document.querySelector('.messenger-loading').style.display='none'">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{% url 'communications:chat_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="bi-arrow-left"></i> Вернуться к чатам
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,80 +0,0 @@
|
||||
{# backend/templates/communications/messengers/whatsapp.html #}
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ messenger_name }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
|
||||
<style>
|
||||
.messenger-iframe-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100vh - 180px);
|
||||
min-height: 600px;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.messenger-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.messenger-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.messenger-loading i {
|
||||
font-size: 3rem;
|
||||
color: {{ messenger_color }};
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-3">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% include "includes/components/page_header.html" with icon=messenger_icon title=messenger_name %}
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="messenger-iframe-container">
|
||||
<div class="messenger-loading">
|
||||
<i class="bi-arrow-repeat"></i>
|
||||
<p class="mt-2 text-muted">Загрузка {{ messenger_name }}...</p>
|
||||
</div>
|
||||
<iframe
|
||||
class="messenger-iframe"
|
||||
src="https://web.whatsapp.com/"
|
||||
allow="microphone; camera"
|
||||
onload="document.querySelector('.messenger-loading').style.display='none'">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{% url 'communications:chat_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="bi-arrow-left"></i> Вернуться к чатам
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -9,6 +9,33 @@
|
||||
</div>
|
||||
<div class="ios-acc-body">
|
||||
<div class="row g-3 px-2">
|
||||
{# Фотография #}
|
||||
<div class="col-12">
|
||||
<label class="form-label">Фотография</label>
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<div class="position-relative">
|
||||
{% if emp.avatar %}
|
||||
<img id="avatarPreview" src="{{ emp.avatar.url }}" alt="Фото"
|
||||
class="rounded" style="width: 120px; height: 120px; object-fit: cover; border: 2px solid #dee2e6;">
|
||||
{% else %}
|
||||
<div id="avatarPreview" class="rounded d-flex align-items-center justify-content-center bg-light text-muted"
|
||||
style="width: 120px; height: 120px; border: 2px solid #dee2e6;">
|
||||
<i class="bi-person fs-1"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<input type="file" class="form-control mb-2" id="avatarInput" name="avatar" accept="image/*">
|
||||
<small class="text-muted d-block">Форматы: JPG, PNG, GIF. Максимум 5 МБ.</small>
|
||||
{% if emp.avatar %}
|
||||
<button type="button" class="btn btn-sm btn-outline-danger mt-2" id="removeAvatarBtn">
|
||||
<i class="bi-trash"></i> Удалить фото
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="f_first_name">Имя</label>
|
||||
<input id="f_first_name" class="form-control" name="first_name"
|
||||
|
||||
@ -27,5 +27,101 @@
|
||||
if (groupPicker) window.groupPicker = groupPicker;
|
||||
if (editGroupPicker) window.editGroupPicker = editGroupPicker;
|
||||
if (groupsMgr) window.employeeGroupsManager = groupsMgr;
|
||||
|
||||
// Инициализация загрузчика аватара
|
||||
initAvatarUploader();
|
||||
}
|
||||
|
||||
// Обработка загрузки и предпросмотра аватара
|
||||
function initAvatarUploader() {
|
||||
const avatarInput = document.getElementById('avatarInput');
|
||||
const avatarPreview = document.getElementById('avatarPreview');
|
||||
const removeBtn = document.getElementById('removeAvatarBtn');
|
||||
|
||||
if (!avatarInput || !avatarPreview) return;
|
||||
|
||||
// Предпросмотр при выборе файла
|
||||
avatarInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Проверка размера (5 МБ)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
alert('Файл слишком большой. Максимум 5 МБ.');
|
||||
avatarInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка типа
|
||||
if (!file.type.match(/^image\/(jpeg|jpg|png|gif)$/i)) {
|
||||
alert('Неподдерживаемый формат. Используйте JPG, PNG или GIF.');
|
||||
avatarInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем предпросмотр
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
if (avatarPreview.tagName === 'IMG') {
|
||||
avatarPreview.src = event.target.result;
|
||||
} else {
|
||||
// Заменяем placeholder на img
|
||||
const img = document.createElement('img');
|
||||
img.id = 'avatarPreview';
|
||||
img.src = event.target.result;
|
||||
img.alt = 'Фото';
|
||||
img.className = 'rounded';
|
||||
img.style.cssText = 'width: 120px; height: 120px; object-fit: cover; border: 2px solid #dee2e6;';
|
||||
avatarPreview.replaceWith(img);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Удаление фото
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', async () => {
|
||||
if (!confirm('Удалить фотографию?')) return;
|
||||
|
||||
try {
|
||||
const form = document.getElementById('apiEditForm');
|
||||
const endpoint = form?.dataset?.endpoint;
|
||||
if (!endpoint) {
|
||||
alert('Ошибка: не указан endpoint');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
|
||||
},
|
||||
body: JSON.stringify({ avatar: null })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Заменяем изображение на placeholder
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.id = 'avatarPreview';
|
||||
placeholder.className = 'rounded d-flex align-items-center justify-content-center bg-light text-muted';
|
||||
placeholder.style.cssText = 'width: 120px; height: 120px; border: 2px solid #dee2e6;';
|
||||
placeholder.innerHTML = '<i class="bi-person fs-1"></i>';
|
||||
|
||||
document.getElementById('avatarPreview').replaceWith(placeholder);
|
||||
removeBtn.remove();
|
||||
avatarInput.value = '';
|
||||
|
||||
alert('Фотография удалена');
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert('Ошибка: ' + (data.error || 'Не удалось удалить фото'));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Avatar remove error:', err);
|
||||
alert('Ошибка при удалении фото');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user