fix(e-taxes): make document-load dialogs fit the screen

The "select records to import" dialogs (VAT operations, sales/purchase invoices,
AMAS employees) wasted vertical space: a fixed 500px table plus a context alert
pinned to the bottom forced scrolling to reach the content and the buttons.

Rework them to the journal_entry pattern: the records table is now the single
scroll area sized to the viewport (max-height: calc(100vh - 360px)) with a
sticky header, and the top count alert + bottom context alert are merged into
one compact header row. Affects journal_entry, sales_order, purchase_order,
sales_invoice and employee (AMAS) selection dialogs. No logic/handler changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-06-15 11:20:11 +00:00
parent 610b98ca69
commit c6e2101c26
5 changed files with 121 additions and 111 deletions

View File

@ -529,17 +529,19 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
const existing_count = employees.length - new_count; const existing_count = employees.length - new_count;
// Build table (e-taxes style) // Build table (e-taxes style)
let invoiceTable = '<div style="max-height: 500px; overflow-y: auto;">' + // Таблица сотрудников — единственная прокручиваемая область. Высота привязана
'<table class="table table-bordered amas-employees-table" style="width: 100%; table-layout: fixed;">' + // к вьюпорту (calc), чтобы модал целиком помещался в экран. Шапка «липкая».
'<thead><tr>' + let invoiceTable = '<div class="amas-employees-scroll" style="max-height: calc(100vh - 360px); min-height: 220px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 6px;">' +
'<th style="width: 4%;"><input type="checkbox" class="select-all-employees"></th>' + '<table class="table table-bordered amas-employees-table" style="width: 100%; table-layout: fixed; margin-bottom: 0;">' +
'<th style="width: 20%;">' + __('Full Name') + '</th>' + '<thead style="position: sticky; top: 0; z-index: 1;"><tr>' +
'<th style="width: 10%;">' + __('FIN') + '</th>' + '<th style="width: 4%; background: var(--control-bg, var(--bg-color));"><input type="checkbox" class="select-all-employees"></th>' +
'<th style="width: 20%;">' + __('Position') + '</th>' + '<th style="width: 20%; background: var(--control-bg, var(--bg-color));">' + __('Full Name') + '</th>' +
'<th style="width: 8%;">' + __('Salary') + '</th>' + '<th style="width: 10%; background: var(--control-bg, var(--bg-color));">' + __('FIN') + '</th>' +
'<th style="width: 12%;">' + __('Status') + '</th>' + '<th style="width: 20%; background: var(--control-bg, var(--bg-color));">' + __('Position') + '</th>' +
'<th style="width: 10%;">' + __('Begin Date') + '</th>' + '<th style="width: 8%; background: var(--control-bg, var(--bg-color));">' + __('Salary') + '</th>' +
'<th style="width: 16%;">' + __('In System') + '</th>' + '<th style="width: 12%; background: var(--control-bg, var(--bg-color));">' + __('Status') + '</th>' +
'<th style="width: 10%; background: var(--control-bg, var(--bg-color));">' + __('Begin Date') + '</th>' +
'<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('In System') + '</th>' +
'</tr></thead><tbody>'; '</tr></thead><tbody>';
employees.forEach(function(emp, idx) { employees.forEach(function(emp, idx) {
@ -563,9 +565,19 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
invoiceTable += '</tbody></table></div>'; invoiceTable += '</tbody></table></div>';
const infoMessage = '<div class="alert alert-info">' + // Компактная шапка: счётчик сотрудников слева + организация справа в одну
__('Employees found: ') + employees.length + // строку. Заменяет alert со счётчиком и убирает пустые Section Break-лейблы.
const headerHtml =
'<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:8px;">' +
'<span class="indicator-pill blue" style="font-size:13px;">' +
__('Employees found: ') + '<strong>' + employees.length + '</strong>' +
' (' + __('New') + ': ' + new_count + ', ' + __('Already in system') + ': ' + existing_count + ')' + ' (' + __('New') + ': ' + new_count + ', ' + __('Already in system') + ': ' + existing_count + ')' +
'</span>' +
(organization_name
? '<span style="color: var(--text-muted);">' +
__('Organization: ') + '<strong>' + frappe.utils.escape_html(organization_name) + '</strong>' +
'</span>'
: '') +
'</div>'; '</div>';
const default_company = frappe.defaults.get_user_default('Company') || frappe.defaults.get_global_default('company'); const default_company = frappe.defaults.get_user_default('Company') || frappe.defaults.get_global_default('company');
@ -575,9 +587,9 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
size: 'large', size: 'large',
fields: [ fields: [
{ {
fieldname: 'settings_section', fieldname: 'header_html',
fieldtype: 'Section Break', fieldtype: 'HTML',
label: __('Settings') options: headerHtml
}, },
{ {
fieldname: 'create_designation', fieldname: 'create_designation',
@ -585,16 +597,6 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
label: __('Create Designation if not found'), label: __('Create Designation if not found'),
default: 1 default: 1
}, },
{
fieldname: 'employees_section',
fieldtype: 'Section Break',
label: __('Employees')
},
{
fieldname: 'info_html',
fieldtype: 'HTML',
options: infoMessage
},
{ {
fieldname: 'employees_html', fieldname: 'employees_html',
fieldtype: 'HTML', fieldtype: 'HTML',

View File

@ -1142,17 +1142,21 @@ VATETaxes.import = {
// Показать диалог выбора операций // Показать диалог выбора операций
showOperationSelection: function(operations, token, company, createAsDraft = 0) { showOperationSelection: function(operations, token, company, createAsDraft = 0) {
VATETaxes.dialogs.hide(); VATETaxes.dialogs.hide();
let operationTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered vat-operations-table" style="width: 100%; table-layout: fixed;">'; // Таблица операций — единственная прокручиваемая область. Высота привязана к
operationTable += '<thead><tr>' + // вьюпорту (calc), чтобы модал целиком помещался в экран без внешнего скролла.
'<th style="width: 3%;"><input type="checkbox" class="select-all-operations"></th>' + // Шапка таблицы «липкая» (sticky), чтобы заголовки колонок оставались видны
'<th style="width: 8%;">' + __('Date') + '</th>' + // при прокрутке списка.
'<th style="width: 9%;">' + __('TIN') + '</th>' + let operationTable = '<div class="vat-operations-scroll" style="max-height: calc(100vh - 360px); min-height: 220px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 6px;"><table class="table table-bordered vat-operations-table" style="width: 100%; table-layout: fixed; margin-bottom: 0;">';
'<th style="width: 15%;">' + __('Name') + '</th>' + operationTable += '<thead style="position: sticky; top: 0; z-index: 1;"><tr style="background: var(--control-bg, var(--bg-color));">' +
'<th style="width: 15%;">' + __('Operation Type') + '</th>' + '<th style="width: 3%; background: var(--control-bg, var(--bg-color));"><input type="checkbox" class="select-all-operations"></th>' +
'<th style="width: 9%;">' + __('Classification Code') + '</th>' + '<th style="width: 8%; background: var(--control-bg, var(--bg-color));">' + __('Date') + '</th>' +
'<th style="width: 17%;">' + __('Explanation') + '</th>' + '<th style="width: 9%; background: var(--control-bg, var(--bg-color));">' + __('TIN') + '</th>' +
'<th style="width: 12%;">' + __('Income') + '</th>' + '<th style="width: 15%; background: var(--control-bg, var(--bg-color));">' + __('Name') + '</th>' +
'<th style="width: 12%;">' + __('Expense') + '</th>' + '<th style="width: 15%; background: var(--control-bg, var(--bg-color));">' + __('Operation Type') + '</th>' +
'<th style="width: 9%; background: var(--control-bg, var(--bg-color));">' + __('Classification Code') + '</th>' +
'<th style="width: 17%; background: var(--control-bg, var(--bg-color));">' + __('Explanation') + '</th>' +
'<th style="width: 12%; background: var(--control-bg, var(--bg-color));">' + __('Income') + '</th>' +
'<th style="width: 12%; background: var(--control-bg, var(--bg-color));">' + __('Expense') + '</th>' +
'</tr></thead><tbody>'; '</tr></thead><tbody>';
operations.forEach(function(operation) { operations.forEach(function(operation) {
@ -1188,8 +1192,17 @@ VATETaxes.import = {
operationTable += '</tbody></table></div>'; operationTable += '</tbody></table></div>';
const infoMessage = '<div class="alert alert-info">' + // Компактная шапка: количество операций + компания в одной строке.
__('New VAT operations found: ') + operations.length + // Заменяет два отдельных alert'а (верхний со счётчиком и нижний с компанией,
// который раньше уезжал под таблицу и требовал скролла).
const headerHtml =
'<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:8px;">' +
'<span class="indicator-pill blue" style="font-size:13px;">' +
__('New VAT operations found: ') + '<strong>' + operations.length + '</strong>' +
'</span>' +
'<span style="color: var(--text-muted);">' +
__('Company: ') + '<strong>' + frappe.utils.escape_html(company) + '</strong>' +
'</span>' +
'</div>'; '</div>';
const d = new frappe.ui.Dialog({ const d = new frappe.ui.Dialog({
@ -1197,14 +1210,9 @@ VATETaxes.import = {
size: 'large', size: 'large',
fields: [ fields: [
{ {
fieldname: 'info_html', fieldname: 'header_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: infoMessage options: headerHtml
},
{
fieldname: 'settings_section',
fieldtype: 'Section Break',
label: __('Import Settings')
}, },
{ {
fieldname: 'create_as_draft', fieldname: 'create_as_draft',
@ -1213,6 +1221,10 @@ VATETaxes.import = {
default: createAsDraft, default: createAsDraft,
description: __('If checked, Journal Entries will be created in Draft status instead of being submitted automatically') description: __('If checked, Journal Entries will be created in Draft status instead of being submitted automatically')
}, },
{
fieldname: 'settings_col_break',
fieldtype: 'Column Break'
},
{ {
fieldname: 'auto_create_customers', fieldname: 'auto_create_customers',
fieldtype: 'Check', fieldtype: 'Check',
@ -1222,19 +1234,12 @@ VATETaxes.import = {
}, },
{ {
fieldname: 'operations_section', fieldname: 'operations_section',
fieldtype: 'Section Break', fieldtype: 'Section Break'
label: __('Operations')
}, },
{ {
fieldname: 'operations_html', fieldname: 'operations_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: operationTable options: operationTable
},
{
fieldname: 'company_display',
fieldtype: 'HTML',
options: '<div class="row"><div class="col-xs-12"><div class="alert alert-info">' +
__('Selected company: ') + '<strong>' + company + '</strong></div></div></div>'
} }
], ],
primary_action_label: __('Load Selected'), primary_action_label: __('Load Selected'),

View File

@ -1094,13 +1094,13 @@ ETaxes.import = {
// Показать диалог выбора инвойсов // Показать диалог выбора инвойсов
showInvoiceSelection: function(invoices, token, warehouse) { showInvoiceSelection: function(invoices, token, warehouse) {
let invoiceTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered etaxes-invoices-table" style="width: 100%; table-layout: fixed;">'; let invoiceTable = '<div class="etaxes-purchase-scroll" style="max-height: calc(100vh - 360px); min-height: 220px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 6px;"><table class="table table-bordered etaxes-invoices-table" style="width: 100%; table-layout: fixed; margin-bottom: 0;">';
invoiceTable += '<thead><tr>' + invoiceTable += '<thead style="position: sticky; top: 0; z-index: 1;"><tr>' +
'<th style="width: 6%;"><input type="checkbox" class="select-all-invoices"></th>' + '<th style="width: 6%; background: var(--control-bg, var(--bg-color));"><input type="checkbox" class="select-all-invoices"></th>' +
'<th style="width: 24%;">' + __('Number') + '</th>' + '<th style="width: 24%; background: var(--control-bg, var(--bg-color));">' + __('Number') + '</th>' +
'<th style="width: 16%;">' + __('Date') + '</th>' + '<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Date') + '</th>' +
'<th style="width: 38%;">' + __('Supplier') + '</th>' + '<th style="width: 38%; background: var(--control-bg, var(--bg-color));">' + __('Supplier') + '</th>' +
'<th style="width: 16%;">' + __('Amount') + '</th>' + '<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Amount') + '</th>' +
'</tr></thead><tbody>'; '</tr></thead><tbody>';
invoices.forEach(function(invoice) { invoices.forEach(function(invoice) {
@ -1127,8 +1127,14 @@ ETaxes.import = {
invoiceTable += '</tbody></table></div>'; invoiceTable += '</tbody></table></div>';
const infoMessage = '<div class="alert alert-info">' + const infoMessage =
__('New invoices found: ') + invoices.length + '<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:8px;">' +
'<span class="indicator-pill blue" style="font-size:13px;">' +
__('New invoices found: ') + '<strong>' + invoices.length + '</strong>' +
'</span>' +
'<span style="color: var(--text-muted);">' +
__('Selected warehouse: ') + '<strong>' + frappe.utils.escape_html(warehouse) + '</strong>' +
'</span>' +
'</div>'; '</div>';
const d = new frappe.ui.Dialog({ const d = new frappe.ui.Dialog({
@ -1144,12 +1150,6 @@ ETaxes.import = {
fieldname: 'invoices_html', fieldname: 'invoices_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: invoiceTable options: invoiceTable
},
{
fieldname: 'warehouse_display',
fieldtype: 'HTML',
options: '<div class="row"><div class="col-xs-12"><div class="alert alert-info">' +
__('Selected warehouse: ') + '<strong>' + warehouse + '</strong></div></div></div>'
} }
], ],
primary_action_label: __('Load selected'), primary_action_label: __('Load selected'),

View File

@ -1113,13 +1113,13 @@ ETaxes.import = {
// Показать диалог выбора инвойсов // Показать диалог выбора инвойсов
showInvoiceSelection: function(invoices, token, warehouse) { showInvoiceSelection: function(invoices, token, warehouse) {
let invoiceTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered etaxes-invoices-table" style="width: 100%; table-layout: fixed;">'; let invoiceTable = '<div class="etaxes-sales-inv-scroll" style="max-height: calc(100vh - 360px); min-height: 220px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 6px;"><table class="table table-bordered etaxes-invoices-table" style="width: 100%; table-layout: fixed; margin-bottom: 0;">';
invoiceTable += '<thead><tr>' + invoiceTable += '<thead style="position: sticky; top: 0; z-index: 1;"><tr>' +
'<th style="width: 6%;"><input type="checkbox" class="select-all-invoices"></th>' + '<th style="width: 6%; background: var(--control-bg, var(--bg-color));"><input type="checkbox" class="select-all-invoices"></th>' +
'<th style="width: 24%;">' + __('Number') + '</th>' + '<th style="width: 24%; background: var(--control-bg, var(--bg-color));">' + __('Number') + '</th>' +
'<th style="width: 16%;">' + __('Date') + '</th>' + '<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Date') + '</th>' +
'<th style="width: 38%;">' + __('Supplier') + '</th>' + '<th style="width: 38%; background: var(--control-bg, var(--bg-color));">' + __('Supplier') + '</th>' +
'<th style="width: 16%;">' + __('Amount') + '</th>' + '<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Amount') + '</th>' +
'</tr></thead><tbody>'; '</tr></thead><tbody>';
invoices.forEach(function(invoice) { invoices.forEach(function(invoice) {
@ -1146,8 +1146,14 @@ ETaxes.import = {
invoiceTable += '</tbody></table></div>'; invoiceTable += '</tbody></table></div>';
const infoMessage = '<div class="alert alert-info">' + const headerHtml =
__('New invoices found: ') + invoices.length + '<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:8px;">' +
'<span class="indicator-pill blue" style="font-size:13px;">' +
__('New invoices found: ') + '<strong>' + invoices.length + '</strong>' +
'</span>' +
'<span style="color: var(--text-muted);">' +
__('Selected warehouse: ') + '<strong>' + frappe.utils.escape_html(warehouse) + '</strong>' +
'</span>' +
'</div>'; '</div>';
const d = new frappe.ui.Dialog({ const d = new frappe.ui.Dialog({
@ -1155,20 +1161,14 @@ ETaxes.import = {
size: 'large', size: 'large',
fields: [ fields: [
{ {
fieldname: 'info_html', fieldname: 'header_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: infoMessage options: headerHtml
}, },
{ {
fieldname: 'invoices_html', fieldname: 'invoices_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: invoiceTable options: invoiceTable
},
{
fieldname: 'warehouse_display',
fieldtype: 'HTML',
options: '<div class="row"><div class="col-xs-12"><div class="alert alert-info">' +
__('Selected warehouse: ') + '<strong>' + warehouse + '</strong></div></div></div>'
} }
], ],
primary_action_label: __('Load selected'), primary_action_label: __('Load selected'),

View File

@ -1111,13 +1111,13 @@ SalesETaxes.import = {
// Показать диалог выбора инвойсов // Показать диалог выбора инвойсов
showInvoiceSelection: function(invoices, token, warehouse) { showInvoiceSelection: function(invoices, token, warehouse) {
SalesETaxes.dialogs.hide(); SalesETaxes.dialogs.hide();
let invoiceTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered etaxes-sales-invoices-table" style="width: 100%; table-layout: fixed;">'; let invoiceTable = '<div class="etaxes-sales-scroll" style="max-height: calc(100vh - 360px); min-height: 220px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 6px;"><table class="table table-bordered etaxes-sales-invoices-table" style="width: 100%; table-layout: fixed; margin-bottom: 0;">';
invoiceTable += '<thead><tr>' + invoiceTable += '<thead style="position: sticky; top: 0; z-index: 1;"><tr>' +
'<th style="width: 6%;"><input type="checkbox" class="select-all-sales-invoices"></th>' + '<th style="width: 6%; background: var(--control-bg, var(--bg-color));"><input type="checkbox" class="select-all-sales-invoices"></th>' +
'<th style="width: 24%;">' + __('Number') + '</th>' + '<th style="width: 24%; background: var(--control-bg, var(--bg-color));">' + __('Number') + '</th>' +
'<th style="width: 16%;">' + __('Date') + '</th>' + '<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Date') + '</th>' +
'<th style="width: 38%;">' + __('Customer') + '</th>' + '<th style="width: 38%; background: var(--control-bg, var(--bg-color));">' + __('Customer') + '</th>' +
'<th style="width: 16%;">' + __('Amount') + '</th>' + '<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Amount') + '</th>' +
'</tr></thead><tbody>'; '</tr></thead><tbody>';
invoices.forEach(function(invoice) { invoices.forEach(function(invoice) {
@ -1144,8 +1144,17 @@ SalesETaxes.import = {
invoiceTable += '</tbody></table></div>'; invoiceTable += '</tbody></table></div>';
const infoMessage = '<div class="alert alert-info">' + // Компактная шапка: количество инвойсов + склад в одной строке.
__('Sales invoices found: ') + invoices.length + // Заменяет два отдельных alert'а (верхний со счётчиком и нижний со складом,
// который раньше уезжал под таблицу и требовал скролла).
const headerHtml =
'<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:8px;">' +
'<span class="indicator-pill blue" style="font-size:13px;">' +
__('Sales invoices found: ') + '<strong>' + invoices.length + '</strong>' +
'</span>' +
'<span style="color: var(--text-muted);">' +
__('Selected delivery warehouse: ') + '<strong>' + frappe.utils.escape_html(warehouse) + '</strong>' +
'</span>' +
'</div>'; '</div>';
const d = new frappe.ui.Dialog({ const d = new frappe.ui.Dialog({
@ -1153,20 +1162,14 @@ SalesETaxes.import = {
size: 'large', size: 'large',
fields: [ fields: [
{ {
fieldname: 'info_html', fieldname: 'header_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: infoMessage options: headerHtml
}, },
{ {
fieldname: 'invoices_html', fieldname: 'invoices_html',
fieldtype: 'HTML', fieldtype: 'HTML',
options: invoiceTable options: invoiceTable
},
{
fieldname: 'warehouse_display',
fieldtype: 'HTML',
options: '<div class="row"><div class="col-xs-12"><div class="alert alert-info">' +
__('Selected delivery warehouse: ') + '<strong>' + warehouse + '</strong></div></div></div>'
} }
], ],
primary_action_label: __('Load selected'), primary_action_label: __('Load selected'),