feat: update admin page

This commit is contained in:
2025-11-03 09:20:28 +03:00
parent 30b0108fe7
commit cd818d3356
94 changed files with 4729 additions and 1227 deletions

View File

@@ -2,11 +2,6 @@
<div id="content">
<div class="page-header">
<div class="container-fluid">
<div class="pull-right">
<button type="submit" form="form-module" data-toggle="tooltip" title="{{ button_save }}"
class="btn btn-primary"><i class="fa fa-save"></i></button>
<a href="{{ cancel }}" data-toggle="tooltip" title="{{ button_cancel }}" class="btn btn-default"><i
class="fa fa-reply"></i></a></div>
<h1>{{ heading_title }}</h1>
<ul class="breadcrumb">
{% for breadcrumb in breadcrumbs %}
@@ -27,438 +22,18 @@
<button type="button" class="close" data-dismiss="alert">&times;</button>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-pencil"></i> {{ text_edit }}</h3>
</div>
<div class="panel-body">
<form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-module"
class="form-horizontal">
<ul class="nav nav-tabs">
{% for tabKey, tabItems in settings %}
<li{% if tabKey == 'general' %} class="active" {% endif %}>
<a href="#{{ tabKey }}" data-toggle="tab">
{% if attribute(_context, 'tab_' ~ tabKey) %}
{{ attribute(_context, 'tab_' ~ tabKey) }}
{% else %}
{{ 'tab_' ~ tabKey }}
{% endif %}
</a>
</li>
{% endfor %}
<li>
<a href="#banners" data-toggle="tab">
Баннеры
</a>
</li>
</ul>
<div class="tab-content">
{% for tabKey, tabItems in settings %}
<div class="tab-pane{%if tabKey == 'general' %} active{% endif %}" id="{{ tabKey }}">
{% for settingKey, item in tabItems %}
<div class="form-group{%if item['required'] %} required{% endif %}{% if item['hidden'] %} hidden{% endif %}">
<label class="col-sm-2 control-label" for="{{ settingKey }}">
{% if attribute(_context, 'lbl_' ~ settingKey) %}
{{ attribute(_context, 'lbl_' ~ settingKey) }}
{% else %}
{{ 'lbl_' ~ settingKey }}
{% endif %}
</label>
<div class="col-sm-10">
{# Select #}
{% if item['type'] == 'select' %}
<select name="{{ settingKey }}" id="{{ settingKey }}" class="form-control">
{% for key, value in item['options'] %}
<option value="{{ key }}" {% if key == attribute(_context, settingKey) %}selected="selected"{% endif %}>
{{ value }}
</option>
{% endfor %}
</select>
{# Text Input #}
{% elseif item['type'] == 'text' %}
<input type="text"
name="{{ settingKey }}"
value="{{ attribute(_context, settingKey) }}"
placeholder="{{ item['placeholder'] }}"
id="{{ settingKey }}"
class="form-control"
/>
{# Image #}
{% elseif item['type'] == 'image' %}
<a href="" id="thumb-image-{{ settingKey }}" data-toggle="image" class="img-thumbnail">
<img src="{{ attribute(_context, settingKey) }}"
data-placeholder="https://placehold.co/100x100?text=Удалено"
/>
</a>
<input type="hidden"
name="{{ settingKey }}"
value="{{ attribute(_context, settingKey) }}"
id="{{ settingKey }}"
/>
{# Textarea #}
{% elseif item['type'] == 'textarea' %}
<textarea name="{{ settingKey }}"
rows="{{ item['rows'] }}"
placeholder="{{ item['placeholder'] }}"
id="{{ settingKey }}"
class="form-control"
>{{ attribute(_context, settingKey) }}</textarea>
{# Products #}
{% elseif item['type'] == 'products' %}
<input type="text" value="" placeholder="Начните вводить название товара..." id="{{ settingKey }}-input" class="form-control"/>
<div id="{{ settingKey }}-list" class="well well-sm" style="height: 150px; overflow: auto;">
{% for product in attribute(_context, settingKey) %}
<div id="{{ settingKey }}-{{ product.product_id }}">
<i class="fa fa-minus-circle"></i> {{ product.name }}
<input type="hidden" name="{{ settingKey }}[]" value="{{ product.product_id }}"/>
</div>
{% endfor %}
</div>
<script>
$('#{{ settingKey }}-input').autocomplete({
'source': function(request, response) {
$.ajax({
url: 'index.php?route=catalog/product/autocomplete&user_token={{ user_token }}&filter_name=' + encodeURIComponent(request),
dataType: 'json',
success: function(json) {
response($.map(json, function(item) {
return {
label: item['name'],
value: item['product_id']
}
}));
}
});
},
'select': function(item) {
$('#{{ settingKey }}').val('');
$('#{{ settingKey }}-' + item['value']).remove();
$('#{{ settingKey }}-list').append('<div id="{{ settingKey }}-' + item['value'] + '"><i class="fa fa-minus-circle"></i> ' + item['label'] + '<input type="hidden" name="{{ settingKey }}[]" value="' + item['value'] + '" /></div>');
}
});
$('#{{ settingKey }}-list').delegate('.fa-minus-circle', 'click', function() {
$(this).parent().remove();
});
</script>
{% elseif item['type'] == 'categories' %}
<input type="text" value="" placeholder="Начните вводить название категории..." id="{{ settingKey }}-input" class="form-control"/>
<div id="{{ settingKey }}-list" class="well well-sm" style="height: 150px; overflow: auto;">
{% for category in attribute(_context, settingKey) %}
<div id="{{ settingKey }}-{{ category.category_id }}">
<i class="fa fa-minus-circle"></i> {{ category.name }}
<input type="hidden" name="{{ settingKey }}[]" value="{{ category.category_id }}"/>
</div>
{% endfor %}
</div>
<script>
$('#{{ settingKey }}-input').autocomplete({
'source': function(request, response) {
$.ajax({
url: 'index.php?route=catalog/category/autocomplete&user_token={{ user_token }}&filter_name=' + encodeURIComponent(request),
dataType: 'json',
success: function(json) {
response($.map(json, function(item) {
return {
label: item['name'],
value: item['category_id']
}
}));
}
});
},
'select': function(item) {
$('#{{ settingKey }}').val('');
$('#{{ settingKey }}-' + item['value']).remove();
$('#{{ settingKey }}-list').append('<div id="{{ settingKey }}-' + item['value'] + '"><i class="fa fa-minus-circle"></i> ' + item['label'] + '<input type="hidden" name="{{ settingKey }}[]" value="' + item['value'] + '" /></div>');
}
});
$('#{{ settingKey }}-list').delegate('.fa-minus-circle', 'click', function() {
$(this).parent().remove();
});
</script>
{# ChatID #}
{% elseif item['type'] == 'chatid' %}
{% if module_tgshop_bot_token %}
<div class="input-group">
<span class="input-group-btn">
<button id="{{ settingKey }}-btn" class="btn btn-primary" type="button">
<i class="fa fa-refresh"></i> Получить Chat ID
</button>
</span>
<input type="text"
name="{{ settingKey }}"
value="{{ attribute(_context, settingKey) }}"
placeholder="{{ item['placeholder'] }}"
id="{{ settingKey }}"
class="form-control"
/>
<script>
$('#{{ settingKey }}-btn').click(function () {
const $resultLabel = $('#{{ settingKey }}-result-label');
const telegramToken = $('#module_tgshop_bot_token').val().trim(); // fetch from input
if (! telegramToken) {
alert('Сначала введите Telegram Bot Token!');
return;
}
fetch('/admin/index.php?route=extension/module/tgshop/handle&api_action=getChatId&user_token={{ user_token }}')
.then(async (res) => {
const data = await res.json().catch(() => null);
if (!res.ok) {
throw new Error(`Ошибка ${res.status}: ${data.message || res.statusText}`);
}
$('#{{ settingKey }}').val(data.data.chat_id);
$resultLabel
.text('✅ ChatID успешно получен и подставлен в поле. Не забудьте сохранить настройки!')
.css('color', 'green');
})
.catch(err => {
console.error(err);
alert(err);
});
});
</script>
</div>
<div id="{{ settingKey }}-result-label"></div>
<button class="btn btn-link btn-xs" type="button" data-toggle="collapse" data-target="#{{ settingKey }}-collapse" aria-expanded="false" aria-controls="collapseExample">
Инструкция как получить ChatID.
</button>
<div class="collapse" id="{{ settingKey }}-collapse">
<div class="well">
<p class="text-primary">Как получить Chat ID</p>
<ol>
<li>Убедитесь, что Telegram Bot Token введён выше.</li>
<li>Откройте вашего бота в Telegram и отправьте ему кодовое слово: `opencart_get_chatid`. Важно отправить именно такое сообщение, иначе не сработает.</li>
<li>Вернитесь сюда и нажмите кнопку «Получить Chat ID» — скрипт автоматически подставит его в поле ниже.</li>
</ol>
</div>
</div>
{% else %}
<div class="alert alert-warning">
<strong>BotToken</strong> не указан. Пожалуйста, введите корректный BotToken и сохраните настройки. После этого здесь станет доступна настройка ChatID.
</div>
{% endif %}
{% elseif item['type'] == 'tg_message_template' %}
<div style="margin-bottom: 10px;">
<textarea name="{{ settingKey }}"
rows="{{ item['rows'] }}"
placeholder="{{ item['placeholder'] }}"
id="{{ settingKey }}"
class="form-control"
>{{ attribute(_context, settingKey) }}</textarea>
</div>
<div>
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#{{ settingKey }}-collapse">
Документация
</button>
<button id="{{ settingKey }}-btn-test" type="button" class="btn btn-primary btn-sm">
<i class="fa fa-envelope"></i>
Отправить тестовое уведомление
</button>
</div>
<div class="collapse" id="{{ settingKey }}-collapse" style="margin-top: 15px">
<div class="well">
<p>Вы можете использовать переменные:</p>
<ul>
<li><code>{store_name}</code> — название магазина</li>
<li><code>{order_id}</code> — номер заказа</li>
<li><code>{customer}</code> — имя и фамилия покупателя</li>
<li><code>{email}</code> — email покупателя</li>
<li><code>{phone}</code> — телефон</li>
<li><code>{comment}</code> — комментарий к заказу</li>
<li><code>{address}</code> — адрес доставки</li>
<li><code>{total}</code> — сумма заказа</li>
<li><code>{ip}</code> — IP покупателя</li>
<li><code>{created_at}</code> — дата и время создания заказа</li>
</ul>
<p>Форматирование: поддерживается <a href="https://core.telegram.org/bots/api#markdownv2-style" target="_blank">*MarkdownV2* <i class="fa fa-external-link"></i></a>.</p>
<p>Символы, которые нужно экранировать в тексте:</p>
<pre>_ * [ ] ( ) ~ ` > # + - = | { } . !</pre>
<p>Каждый из них нужно экранировать обратным слэшем \, если он не используется для форматирования. Например вместо <code>Заказ #123</code> нужно писать <code>Заказ \#123</code>.</p>
</div>
</div>
<script>
$('#{{ settingKey }}-btn-test').click(function () {
const telegramToken = $('#module_tgshop_bot_token').val().trim();
if (! telegramToken) {
alert('Сначала введите Telegram Bot Token!');
return;
}
const chatId = $('#module_tgshop_chat_id').val().trim();
if (! chatId) {
alert('Сначала введите Chat ID!');
return;
}
const template = $('#{{ settingKey }}').val().trim();
if (! template) {
alert('Сначала задайте шаблон!');
return;
}
fetch('/index.php?route=extension/tgshop/handle&api_action=testTgMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: telegramToken,
chat_id: chatId,
template: template
})
})
.then(res => res.json())
.then(response => {
alert(response.message || 'Уведомление успешно отправлено');
})
.catch(error => {
console.error(error);
alert('Ошибка при отправке тестового сообщения');
});
});
</script>
{% elseif item['type'] == 'text_readonly' %}
<input type="text"
readonly
name="{{ settingKey }}"
value="{{ attribute(_context, settingKey) }}"
id="{{ settingKey }}"
class="form-control"
onfocus="this.select()"
/>
{# BOT TOKEN #}
{% elseif item['type'] == 'bot_token' %}
<div class="input-group">
<span class="input-group-btn">
<button id="{{ settingKey }}-btn" class="btn btn-primary" type="button" onclick="validateBotToken()">
<i class="fa fa-refresh"></i> Проверить Bot Token
</button>
</span>
<input type="text"
name="{{ settingKey }}"
value="{{ attribute(_context, settingKey) }}"
placeholder="{{ item['placeholder'] }}"
id="{{ settingKey }}"
class="form-control"
onfocusout="validateBotToken()"
/>
</div>
<div id="{{ settingKey }}-result-label"></div>
<button class="btn btn-link btn-xs" type="button" data-toggle="collapse" data-target="#{{ settingKey }}-collapse" aria-expanded="false" aria-controls="collapseExample">
Инструкция как создать Bot Token
</button>
<div class="collapse" id="{{ settingKey }}-collapse">
<div class="well">
<p>Подробная инструкция доступна в <a href="https://nikitakiselev.github.io/telecart-docs/#telegram" target="_blank">документации <i class="fa fa-external-link"></i></a>.</p>
</div>
</div>
<script>
function validateBotToken() {
const $input = $('#{{ settingKey }}');
const $resultLabel = $('#{{ settingKey }}-result-label');
const botToken = $input.val();
const url = '/admin/index.php?route=extension/module/tgshop/handle&api_action=configureBotToken&user_token={{ user_token }}';
if (botToken.trim().length === 0) {
$resultLabel
.text(`❌ Введите Bot Token!`)
.css('color', 'red');
return;
}
$input.attr('readonly', true);
$resultLabel.text('Проверяю...');
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ botToken }),
})
.then(async (res) => {
const response = await res.json().catch(() => null);
if (res.status === 422) {
console.error(res, response);
$resultLabel
.text(`❌ Ошибка: ${response.error}`)
.css('color', 'red');
return;
}
if (!res.ok) {
throw new Error(`Ошибка ${response.error || res.statusText}`);
}
if (! response.id) {
throw new Error(`bot token is not found in server response.`);
}
$resultLabel
.text(`✅ Бот: @${response.username} (id: ${response.id}) webhook: ${response.webhook_url}`)
.css('color', 'green');
})
.catch(err => {
console.error(err);
$resultLabel
.text(`❌ Ошибка проверки BotToken.`)
.css('color', 'red');
})
.finally(() => $input.attr('readonly', false))
}
</script>
{% else %}
Unsupported {{ item|json_encode }}
{% endif %}
{% if attribute(_context, 'error_' ~ settingKey) %}
<div class="text-danger">{{ attribute(_context, 'error_' ~ settingKey) }}</div>
{% endif %}
{% if item['help'] %}
<p class="help-block">{{ item['help'] }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
<div id="banners" class="tab-pane">
<script>
window.TeleCart = {
user_token: '{{ user_token }}',
mainpage_slider: '{{ mainpage_slider }}',
};
</script>
<div id="app">App Loading...</div>
</div>
</div>
</form>
<script>
window.TeleCart = {
user_token: '{{ user_token }}',
mainpage_slider: '{{ mainpage_slider }}',
themes: '{{ themes | json_encode }}',
order_statuses: '{{ order_statuses | json_encode }}',
};
</script>
<div id="app">App Loading...</div>
</div>
</div>
</div>
@@ -467,7 +42,7 @@
<script>
const $element = $('#thumb-image-module_tgshop_app_icon');
$('#button-clear').on('click', function() {
$('#button-clear').on('click', function () {
$element.find('img').attr('src', $element.find('img').attr('data-placeholder'));
$element.parent().find('input').val('');
$element.popover('destroy');

View File

@@ -1,60 +0,0 @@
{{ header }}{{ column_left }}
<div id="content">
<div class="page-header">
<div class="container-fluid">
<div class="pull-right">
<a href="{{ cancel }}" data-toggle="tooltip" title="{{ button_cancel }}" class="btn btn-default"><i
class="fa fa-reply"></i></a></div>
<h1>{{ heading_title }}</h1>
<ul class="breadcrumb">
{% for breadcrumb in breadcrumbs %}
<li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li>
{% endfor %}
</ul>
</div>
</div>
<div class="container-fluid">
{% if error_warning %}
<div class="alert alert-danger alert-dismissible"><i
class="fa fa-exclamation-circle"></i> {{ error_warning }}
<button type="button" class="close" data-dismiss="alert">&times;</button>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-pencil"></i> Инициализация модуля</h3>
</div>
<div class="panel-body">
<div class="col-md-push-3 col-md-6 text-center">
<div style="font-size: 16px;">
<h2 style="margin-top: 50px; margin-bottom: 30px;">🛠 Добро пожаловать в модуль Telegram-магазина</h2>
<p>Этот модуль разработан с вниманием к деталям и заботой о стабильной работе вашего магазина в Telegram. Я старался сделать его максимально простым, понятным и гибким.</p>
<p>Если у вас возникнут вопросы, пожелания или нужны доработки — вы всегда можете обратиться:</p>
<ul style="list-style: none">
<li>📬 Email: <a href="mailto:kiselev2008@gmail.com">kiselev2008@gmail.com</a></li>
<li>💬 Telegram-группа: <a href="https://t.me/ocstore3" target="_blank">https://t.me/ocstore3 <i class="fa fa-external-link"></i></a></li>
</ul>
<p>Заходите в Telegram-группу, там я анонсирую свежие версии своих модулей.</p>
<div class="alert alert-info">
<p>⚠️ Перед началом работы требуется инициализация модуля.</p>
<p>Она создаст дефолтные настройки и подготовит систему к использованию.</p>
<p>Нажмите кнопку ниже, чтобы выполнить первичную настройку. Всё выполнится автоматически.</p>
</div>
<form action="{{ action }}" method="post" enctype="multipart/form-data" class="form-horizontal">
<button type="submit"
data-toggle="tooltip"
title="Нажмите чтобы выполнить начальную инициализацию модуля"
class="btn btn-primary"
>
Инициализация
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{{ footer }}