396 lines
17 KiB
HTML
396 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Редактирование клиента{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mt-4">
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header" style="background: linear-gradient(135deg, var(--steel-blue) 0%, var(--light-steel-blue) 100%); color: white;">
|
|
<h2 class="card-title mb-0">
|
|
<i class="fas fa-edit"></i> Редактирование клиента
|
|
</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Flash сообщения -->
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible fade show">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
<form method="POST" id="clientForm">
|
|
<div class="row">
|
|
<!-- Основная информация -->
|
|
<div class="col-md-6">
|
|
<h5 class="border-bottom pb-2 mb-3" style="color: var(--steel-blue);">Основная информация</h5>
|
|
|
|
<div class="mb-3">
|
|
<label for="name" class="form-label">Название компании</label>
|
|
<input type="text" class="form-control" id="name" name="name"
|
|
value="{{ client.name }}" required maxlength="255"
|
|
onkeypress="return blockNumbers(event)" onpaste="return blockNumbersPaste(event)">
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="director" class="form-label">Директор (ФИО)</label>
|
|
<input type="text" class="form-control" id="director" name="director"
|
|
value="{{ client.director or '' }}" maxlength="255"
|
|
onkeypress="return blockNumbers(event)" onpaste="return blockNumbersPaste(event)">
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="contractor_type_id" class="form-label">Тип клиента</label>
|
|
<select class="form-select" id="contractor_type_id" name="contractor_type_id" required>
|
|
<option value="">Выберите тип клиента</option>
|
|
{% for type in contractor_types %}
|
|
<option value="{{ type.id }}"
|
|
{% if type.id == client.contractor_type_id %}selected{% endif %}>
|
|
{{ type.type_name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="rating" class="form-label">Рейтинг</label>
|
|
<input type="number" class="form-control" id="rating" name="rating"
|
|
min="0" max="5" step="0.1" value="{{ client.rating or 0.0 }}">
|
|
<div class="form-text">От 0.0 до 5.0</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Контактная информация -->
|
|
<div class="col-md-6">
|
|
<h5 class="border-bottom pb-2 mb-3" style="color: var(--steel-blue);">Контактная информация</h5>
|
|
|
|
<div class="mb-3">
|
|
<label for="email" class="form-label">Email</label>
|
|
<input type="email" class="form-control" id="email" name="email"
|
|
value="{{ client.email }}" required maxlength="255">
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="phone" class="form-label">Телефон</label>
|
|
<input type="tel" class="form-control" id="phone" name="phone"
|
|
value="{{ client.phone or '' }}" maxlength="50"
|
|
onkeypress="return allowPhoneNumbers(event)">
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="inn" class="form-label">ИНН</label>
|
|
<input type="text" class="form-control" id="inn" name="inn"
|
|
value="{{ client.inn or '' }}" maxlength="20" pattern="\d{10,12}"
|
|
onkeypress="return allowOnlyNumbers(event)" onpaste="return allowNumbersPaste(event)">
|
|
<div class="form-text">10 или 12 цифр</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="address" class="form-label">Юридический адрес</label>
|
|
<textarea class="form-control" id="address" name="address"
|
|
rows="3" maxlength="500">{{ client.address or '' }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between">
|
|
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i> Назад к списку
|
|
</a>
|
|
<div>
|
|
<a href="{{ url_for('order_history', client_id=client.id) }}"
|
|
class="btn btn-info me-2">
|
|
<i class="fas fa-history"></i> История заказов
|
|
</a>
|
|
<button type="submit" class="btn btn-success">
|
|
<i class="fas fa-save"></i> Сохранить изменения
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.card {
|
|
border: 1px solid var(--light-steel-blue);
|
|
border-radius: 15px;
|
|
box-shadow: 0 4px 10px rgba(70, 130, 180, 0.1);
|
|
}
|
|
|
|
.form-label {
|
|
color: var(--steel-blue);
|
|
font-weight: 500;
|
|
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
|
}
|
|
|
|
.form-control, .form-select {
|
|
border: 2px solid var(--light-steel-blue);
|
|
border-radius: 8px;
|
|
padding: 10px 15px;
|
|
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.form-control:focus, .form-select:focus {
|
|
border-color: var(--steel-blue);
|
|
box-shadow: 0 0 0 0.2rem rgba(70, 130, 180, 0.25);
|
|
outline: none;
|
|
}
|
|
|
|
.form-control:hover, .form-select:hover {
|
|
border-color: var(--steel-blue);
|
|
}
|
|
|
|
.btn-success {
|
|
background: linear-gradient(135deg, #28a745 0%, #218838 100%);
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 10px 20px;
|
|
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: linear-gradient(135deg, #218838 0%, #28a745 100%);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 10px rgba(40, 167, 69, 0.3);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 10px 20px;
|
|
color: #2c3e50;
|
|
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: linear-gradient(135deg, #9fb6d4 0%, var(--light-steel-blue) 100%);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 10px rgba(176, 196, 222, 0.3);
|
|
}
|
|
|
|
.btn-info {
|
|
background: linear-gradient(135deg, var(--steel-blue) 0%, #3a6d99 100%);
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 10px 20px;
|
|
color: white;
|
|
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-info:hover {
|
|
background: linear-gradient(135deg, #3a6d99 0%, var(--steel-blue) 100%);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 10px rgba(70, 130, 180, 0.3);
|
|
}
|
|
|
|
.alert {
|
|
border-radius: 10px;
|
|
border: 1px solid var(--light-steel-blue);
|
|
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
|
}
|
|
|
|
.alert-success {
|
|
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
|
color: #155724;
|
|
border-left: 6px solid #28a745;
|
|
}
|
|
|
|
.alert-danger {
|
|
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
|
color: #721c24;
|
|
border-left: 6px solid #dc3545;
|
|
}
|
|
|
|
.border-bottom {
|
|
border-color: var(--light-steel-blue) !important;
|
|
}
|
|
|
|
textarea.form-control {
|
|
resize: vertical;
|
|
}
|
|
</style>
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Запрет на ввод цифр в текстовые поля (название компании, ФИО)
|
|
function blockNumbers(event) {
|
|
const key = event.key;
|
|
// Разрешаем: буквы, пробел, backspace, delete, tab, enter, стрелки
|
|
if (key === ' ' || key === 'Backspace' || key === 'Delete' || key === 'Tab' ||
|
|
key === 'Enter' || key === 'ArrowLeft' || key === 'ArrowRight' ||
|
|
key === 'ArrowUp' || key === 'ArrowDown' || key === 'Home' || key === 'End') {
|
|
return true;
|
|
}
|
|
|
|
// Запрещаем цифры
|
|
if (key >= '0' && key <= '9') {
|
|
event.preventDefault();
|
|
showNotification('Цифры запрещены в этом поле');
|
|
return false;
|
|
}
|
|
|
|
// Разрешаем буквы (любые)
|
|
return true;
|
|
}
|
|
|
|
// Запрет на вставку цифр через Ctrl+V
|
|
function blockNumbersPaste(event) {
|
|
event.preventDefault();
|
|
const pastedText = (event.clipboardData || window.clipboardData).getData('text');
|
|
// Удаляем все цифры из вставляемого текста
|
|
const cleanedText = pastedText.replace(/[0-9]/g, '');
|
|
|
|
// Вставляем очищенный текст
|
|
const input = event.target;
|
|
const start = input.selectionStart;
|
|
const end = input.selectionEnd;
|
|
const currentValue = input.value;
|
|
const newValue = currentValue.substring(0, start) + cleanedText + currentValue.substring(end);
|
|
input.value = newValue;
|
|
|
|
// Возвращаем курсор в конец вставленного текста
|
|
input.selectionStart = input.selectionEnd = start + cleanedText.length;
|
|
|
|
if (pastedText.match(/[0-9]/)) {
|
|
showNotification('Цифры были удалены из вставленного текста');
|
|
}
|
|
}
|
|
|
|
// Разрешаем только цифры (для ИНН)
|
|
function allowOnlyNumbers(event) {
|
|
const key = event.key;
|
|
// Разрешаем: цифры, backspace, delete, tab, enter, стрелки
|
|
if (key === 'Backspace' || key === 'Delete' || key === 'Tab' ||
|
|
key === 'Enter' || key === 'ArrowLeft' || key === 'ArrowRight' ||
|
|
key === 'ArrowUp' || key === 'ArrowDown' || key === 'Home' || key === 'End') {
|
|
return true;
|
|
}
|
|
|
|
// Разрешаем только цифры
|
|
if (key >= '0' && key <= '9') {
|
|
return true;
|
|
}
|
|
|
|
event.preventDefault();
|
|
showNotification('Только цифры разрешены в этом поле');
|
|
return false;
|
|
}
|
|
|
|
// Разрешаем вставку только цифр (для ИНН)
|
|
function allowNumbersPaste(event) {
|
|
event.preventDefault();
|
|
const pastedText = (event.clipboardData || window.clipboardData).getData('text');
|
|
// Оставляем только цифры
|
|
const cleanedText = pastedText.replace(/[^0-9]/g, '');
|
|
|
|
// Вставляем очищенный текст
|
|
const input = event.target;
|
|
const start = input.selectionStart;
|
|
const end = input.selectionEnd;
|
|
const currentValue = input.value;
|
|
const newValue = currentValue.substring(0, start) + cleanedText + currentValue.substring(end);
|
|
input.value = newValue;
|
|
|
|
// Возвращаем курсор в конец вставленного текста
|
|
input.selectionStart = input.selectionEnd = start + cleanedText.length;
|
|
|
|
if (pastedText.match(/[^0-9]/)) {
|
|
showNotification('Были вставлены только цифры, остальные символы удалены');
|
|
}
|
|
}
|
|
|
|
// Для поля телефона разрешаем цифры и некоторые символы
|
|
function allowPhoneNumbers(event) {
|
|
const key = event.key;
|
|
// Разрешаем: цифры, +, -, (, ), пробел, backspace и т.д.
|
|
const allowedKeys = ['+', '-', '(', ')', ' '];
|
|
|
|
if (key === 'Backspace' || key === 'Delete' || key === 'Tab' ||
|
|
key === 'Enter' || key === 'ArrowLeft' || key === 'ArrowRight' ||
|
|
key === 'ArrowUp' || key === 'ArrowDown' || key === 'Home' || key === 'End') {
|
|
return true;
|
|
}
|
|
|
|
if (allowedKeys.includes(key)) {
|
|
return true;
|
|
}
|
|
|
|
if (key >= '0' && key <= '9') {
|
|
return true;
|
|
}
|
|
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
// Показать уведомление
|
|
function showNotification(message) {
|
|
// Можно использовать alert или создать более красивое уведомление
|
|
console.log(message);
|
|
// Для простоты используем alert, но можно заменить на что-то более элегантное
|
|
// alert(message);
|
|
}
|
|
|
|
// Валидация формы
|
|
document.getElementById('clientForm').addEventListener('submit', function(e) {
|
|
const inn = document.getElementById('inn').value;
|
|
const name = document.getElementById('name').value;
|
|
const director = document.getElementById('director').value;
|
|
|
|
// Проверка на наличие цифр в названии компании (дополнительная защита)
|
|
if (name.match(/[0-9]/)) {
|
|
e.preventDefault();
|
|
alert('Название компании не должно содержать цифры');
|
|
return false;
|
|
}
|
|
|
|
// Проверка на наличие цифр в ФИО директора
|
|
if (director && director.match(/[0-9]/)) {
|
|
e.preventDefault();
|
|
alert('ФИО директора не должно содержать цифры');
|
|
return false;
|
|
}
|
|
|
|
if (inn && !/^\d{10,12}$/.test(inn)) {
|
|
e.preventDefault();
|
|
alert('ИНН должен содержать 10 или 12 цифр');
|
|
return false;
|
|
}
|
|
|
|
const rating = parseFloat(document.getElementById('rating').value);
|
|
if (rating < 0 || rating > 5) {
|
|
e.preventDefault();
|
|
alert('Рейтинг должен быть от 0.0 до 5.0');
|
|
return false;
|
|
}
|
|
|
|
const email = document.getElementById('email').value;
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email)) {
|
|
e.preventDefault();
|
|
alert('Введите корректный email адрес');
|
|
return false;
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
{% endblock %} |