mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-12-09 13:46:21 +08:00
Initial label templates refactor [SCI-9303]
This commit is contained in:
parent
da6d294573
commit
bb4349f346
36 changed files with 977 additions and 446 deletions
4
app/assets/images/checkbox/checked.svg
Normal file
4
app/assets/images/checkbox/checked.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 1C0 0.447715 0.447715 0 1 0H15C15.5523 0 16 0.447715 16 1V15C16 15.5523 15.5523 16 15 16H1C0.447715 16 0 15.5523 0 15V1Z" fill="#3B99FD"/>
|
||||||
|
<path d="M6.56358 11.8292L3 8.32244L3.70142 7.60969L6.55139 10.4143L12.8641 4L13.5775 4.70204L6.56358 11.8292Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 381 B |
4
app/assets/images/checkbox/default.svg
Normal file
4
app/assets/images/checkbox/default.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H1V15H15V1ZM1 0C0.447715 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V1C16 0.447715 15.5523 0 15 0H1Z" fill="#1D2939"/>
|
||||||
|
<path d="M1 1H15V15H1V1Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 351 B |
4
app/assets/images/checkbox/disabled.svg
Normal file
4
app/assets/images/checkbox/disabled.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H1V15H15V1ZM1 0C0.447715 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V1C16 0.447715 15.5523 0 15 0H1Z" fill="#EAECF0"/>
|
||||||
|
<path d="M1 1H15V15H1V1Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 351 B |
4
app/assets/images/checkbox/indeterminate.svg
Normal file
4
app/assets/images/checkbox/indeterminate.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 1C0 0.447715 0.447716 0 1 0H15C15.5523 0 16 0.447715 16 1V15C16 15.5523 15.5523 16 15 16H1C0.447716 16 0 15.5523 0 15V1Z" fill="#3B99FD"/>
|
||||||
|
<path d="M13 8H3V9H13V8Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 295 B |
|
|
@ -1,306 +0,0 @@
|
||||||
/* global I18n DataTableHelpers HelperModule */
|
|
||||||
/* eslint-disable no-use-before-define no-param-reassign */
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var LABEL_TEMPLATE_TABLE;
|
|
||||||
var rowsSelected = [];
|
|
||||||
|
|
||||||
function rowsSelectedIDs() {
|
|
||||||
return rowsSelected.map(i => i.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderCheckboxHTML(data) {
|
|
||||||
return `<div class="sci-checkbox-container">
|
|
||||||
<input type="checkbox" class="sci-checkbox label-row-checkbox" data-action='toggle'
|
|
||||||
data-label-template-id="${data}">
|
|
||||||
<span class="sci-checkbox-label"></span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDefaultTemplateHTML(data) {
|
|
||||||
return data ? '<i class="fas fa-thumbtack"></i>' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderNameHTML(data, type, row) {
|
|
||||||
return `<div class="flex gap-2">${data.icon_image_tag}<a
|
|
||||||
href='${row.DT_RowAttr['data-edit-url']}'
|
|
||||||
class='label-info-link'
|
|
||||||
>${data.name}</a></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addAttributesToRow(row, data) {
|
|
||||||
$(row).addClass('label-template-row')
|
|
||||||
.attr('data-id', data['0']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initNameClick() {
|
|
||||||
$('.label-info-link', '.dataTables_scrollBody').on('click', function() {
|
|
||||||
window.location.href = this.href;
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initToggleAllCheckboxes() {
|
|
||||||
$('input[name="select_all"]').change(function() {
|
|
||||||
if ($(this).is(':checked')) {
|
|
||||||
$("[data-action='toggle']").prop('checked', true);
|
|
||||||
$('.label-template-row').addClass('selected');
|
|
||||||
$('.label-template-row [data-action="toggle"]').change();
|
|
||||||
} else {
|
|
||||||
$("[data-action='toggle']").prop('checked', false);
|
|
||||||
$('.label-template-row').removeClass('selected');
|
|
||||||
$('.label-template-row [data-action="toggle"]').change();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initCreateButton() {
|
|
||||||
$('#newLabelTemplate').on('click', function() {
|
|
||||||
$.post(this.dataset.url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initSetDefaultButton() {
|
|
||||||
$(document).on('click', '#setZplDefaultLabelTemplate, #setFluicsDefaultLabelTemplate', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (rowsSelected.length === 1) {
|
|
||||||
$.post(rowsSelected[0].setDefaultUrl, function(response) {
|
|
||||||
reloadTable();
|
|
||||||
HelperModule.flashAlertMsg(response.message, 'success');
|
|
||||||
}).fail((response) => {
|
|
||||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initEditButton() {
|
|
||||||
$('#editTemplate').on('click', function() {
|
|
||||||
if (rowsSelected.length === 1) {
|
|
||||||
window.location.href = rowsSelected[0].editUrl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initDuplicateButton() {
|
|
||||||
$(document).on('click', '#duplicateLabelTemplate', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (rowsSelected.length > 0) {
|
|
||||||
$.post(this.dataset.url, { selected_ids: rowsSelectedIDs() }, function(response) {
|
|
||||||
reloadTable();
|
|
||||||
HelperModule.flashAlertMsg(response.message, 'success');
|
|
||||||
}).fail((response) => {
|
|
||||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initDeleteModal() {
|
|
||||||
$(document).on('click', '#deleteLabelTemplate', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
$('#deleteLabelTemplatesModal').modal('show');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initDeleteButton() {
|
|
||||||
$('#confirmLabeleDeletion').on('click', function() {
|
|
||||||
if (rowsSelected.length > 0) {
|
|
||||||
$.post(this.dataset.url, { selected_ids: rowsSelectedIDs() }, function(response) {
|
|
||||||
reloadTable();
|
|
||||||
HelperModule.flashAlertMsg(response.message, 'success');
|
|
||||||
$('#deleteLabelTemplatesModal').modal('hide');
|
|
||||||
}).fail((response) => {
|
|
||||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
|
||||||
$('#deleteLabelTemplatesModal').modal('hide');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initRefreshFluicsButton() {
|
|
||||||
$('#syncFluicsTemplates').on('click', function() {
|
|
||||||
$.post(this.dataset.url, function(response) {
|
|
||||||
reloadTable();
|
|
||||||
HelperModule.flashAlertMsg(response.message, 'success');
|
|
||||||
}).fail((response) => {
|
|
||||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function tableDrawCallback() {
|
|
||||||
initToggleAllCheckboxes();
|
|
||||||
initRowSelection();
|
|
||||||
initNameClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateButtons() {
|
|
||||||
if (window.actionToolbarComponent) {
|
|
||||||
window.actionToolbarComponent.fetchActions({ label_template_ids: rowsSelectedIDs() });
|
|
||||||
$('.dataTables_scrollBody').css('padding-bottom', `${rowsSelectedIDs().length > 0 ? 68 : 0}px`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reloadTable() {
|
|
||||||
LABEL_TEMPLATE_TABLE.ajax.reload(null, false);
|
|
||||||
rowsSelected = [];
|
|
||||||
updateButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDataTableSelectAllCtrl() {
|
|
||||||
var $table = LABEL_TEMPLATE_TABLE.table().node();
|
|
||||||
var $header = LABEL_TEMPLATE_TABLE.table().header();
|
|
||||||
var $chkboxAll = $('.label-row-checkbox', $table);
|
|
||||||
var $chkboxChecked = $('.label-row-checkbox:checked', $table);
|
|
||||||
var chkboxSelectAll = $('input[name="select_all"]', $header).get(0);
|
|
||||||
|
|
||||||
// If none of the checkboxes are checked
|
|
||||||
if ($chkboxChecked.length === 0) {
|
|
||||||
chkboxSelectAll.checked = false;
|
|
||||||
if ('indeterminate' in chkboxSelectAll) {
|
|
||||||
chkboxSelectAll.indeterminate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all of the checkboxes are checked
|
|
||||||
} else if ($chkboxChecked.length === $chkboxAll.length) {
|
|
||||||
chkboxSelectAll.checked = true;
|
|
||||||
if ('indeterminate' in chkboxSelectAll) {
|
|
||||||
chkboxSelectAll.indeterminate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If some of the checkboxes are checked
|
|
||||||
} else {
|
|
||||||
chkboxSelectAll.checked = true;
|
|
||||||
if ('indeterminate' in chkboxSelectAll) {
|
|
||||||
chkboxSelectAll.indeterminate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initRowSelection() {
|
|
||||||
// Handle clicks on checkbox
|
|
||||||
$('#label-templates-table').on('change', '.label-row-checkbox', function(ev) {
|
|
||||||
var rowId;
|
|
||||||
var index;
|
|
||||||
var row;
|
|
||||||
|
|
||||||
rowId = this.dataset.labelTemplateId;
|
|
||||||
row = $(this).closest('tr')[0];
|
|
||||||
|
|
||||||
// Determine whether row ID is in the list of selected row IDs
|
|
||||||
index = rowsSelected.findIndex(v => v.id === rowId);
|
|
||||||
|
|
||||||
// If checkbox is checked and row ID is not in list of selected row IDs
|
|
||||||
if (this.checked && index === -1) {
|
|
||||||
rowsSelected.push({
|
|
||||||
id: rowId,
|
|
||||||
default: row.dataset.default,
|
|
||||||
editUrl: row.dataset.editUrl,
|
|
||||||
setDefaultUrl: row.dataset.setDefaultUrl,
|
|
||||||
format: row.dataset.format
|
|
||||||
});
|
|
||||||
// Otherwise, if checkbox is not checked and row ID is in list of selected row IDs
|
|
||||||
} else if (!this.checked && index !== -1) {
|
|
||||||
rowsSelected.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.checked) {
|
|
||||||
$(this).closest('.label-template-row').addClass('selected');
|
|
||||||
} else {
|
|
||||||
$(this).closest('.label-template-row').removeClass('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataTableSelectAllCtrl();
|
|
||||||
|
|
||||||
ev.stopPropagation();
|
|
||||||
updateButtons();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// INIT
|
|
||||||
|
|
||||||
function initDatatable() {
|
|
||||||
var $table = $('#label-templates-table');
|
|
||||||
LABEL_TEMPLATE_TABLE = $table.DataTable({
|
|
||||||
dom: "R<'label-toolbar'<'label-buttons-container'><'label-search-container'f>>t<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
|
|
||||||
order: [[2, 'desc']],
|
|
||||||
stateSave: true,
|
|
||||||
sScrollX: '100%',
|
|
||||||
sScrollXInner: '100%',
|
|
||||||
processing: true,
|
|
||||||
serverSide: true,
|
|
||||||
ajax: $table.data('source'),
|
|
||||||
pagingType: 'simple_numbers',
|
|
||||||
colReorder: {
|
|
||||||
fixedColumnsLeft: 1000000 // Disable reordering
|
|
||||||
},
|
|
||||||
columnDefs: [{
|
|
||||||
targets: 0,
|
|
||||||
searchable: false,
|
|
||||||
orderable: false,
|
|
||||||
className: 'dt-body-center',
|
|
||||||
sWidth: '1%',
|
|
||||||
render: renderCheckboxHTML
|
|
||||||
}, {
|
|
||||||
targets: 1,
|
|
||||||
searchable: false,
|
|
||||||
orderable: true,
|
|
||||||
width: '1.5rem',
|
|
||||||
render: renderDefaultTemplateHTML
|
|
||||||
}, {
|
|
||||||
targets: 2,
|
|
||||||
className: 'label-template-name',
|
|
||||||
render: renderNameHTML
|
|
||||||
}, {
|
|
||||||
targets: 4,
|
|
||||||
className: 'whitespace-break-spaces',
|
|
||||||
render: data => `<span class='whitespace-break-spaces'>${data}</span>`
|
|
||||||
}],
|
|
||||||
oLanguage: {
|
|
||||||
sSearch: I18n.t('general.filter')
|
|
||||||
},
|
|
||||||
fnDrawCallback: tableDrawCallback,
|
|
||||||
createdRow: addAttributesToRow,
|
|
||||||
fnInitComplete: function() {
|
|
||||||
DataTableHelpers.initLengthAppearance($table.closest('.dataTables_wrapper'));
|
|
||||||
DataTableHelpers.initSearchField(
|
|
||||||
$table.closest('.dataTables_wrapper'),
|
|
||||||
I18n.t('label_templates.index.search_templates')
|
|
||||||
);
|
|
||||||
$('.pagination-row').removeClass('hidden');
|
|
||||||
|
|
||||||
let toolBar = $($('#labelTemplatesToolbar').html());
|
|
||||||
$('.label-buttons-container').html(toolBar);
|
|
||||||
|
|
||||||
initCreateButton();
|
|
||||||
initEditButton();
|
|
||||||
initSetDefaultButton();
|
|
||||||
initDuplicateButton();
|
|
||||||
initDeleteModal();
|
|
||||||
initRefreshFluicsButton();
|
|
||||||
window.initActionToolbar();
|
|
||||||
window.actionToolbarComponent.setBottomOffset(68);
|
|
||||||
},
|
|
||||||
stateLoadParams: function(_, state) {
|
|
||||||
state.search.search = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#wrapper').on('sideBar::shown sideBar::hidden', function() {
|
|
||||||
if (LABEL_TEMPLATE_TABLE) {
|
|
||||||
LABEL_TEMPLATE_TABLE.columns.adjust();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
initDatatable();
|
|
||||||
initDeleteButton();
|
|
||||||
}());
|
|
||||||
|
|
@ -81,6 +81,7 @@
|
||||||
@import "shared/action_toolbar";
|
@import "shared/action_toolbar";
|
||||||
@import "shared/assets";
|
@import "shared/assets";
|
||||||
@import "shared/avatar";
|
@import "shared/avatar";
|
||||||
|
@import "shared/ag_table";
|
||||||
@import "shared/cards";
|
@import "shared/cards";
|
||||||
@import "shared/comments_sidebar";
|
@import "shared/comments_sidebar";
|
||||||
@import "shared/comments";
|
@import "shared/comments";
|
||||||
|
|
@ -111,6 +112,7 @@
|
||||||
@import "themes/repositories";
|
@import "themes/repositories";
|
||||||
@import "themes/scinote";
|
@import "themes/scinote";
|
||||||
|
|
||||||
|
|
||||||
@import "navigation/general";
|
@import "navigation/general";
|
||||||
@import "navigation/breadcrumbs";
|
@import "navigation/breadcrumbs";
|
||||||
@import "navigation/left_menu";
|
@import "navigation/left_menu";
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.projects-show {
|
.projects-show {
|
||||||
|
--content-header-size: 9em;
|
||||||
.content-header {
|
.content-header {
|
||||||
height: var(--content-header-size);
|
height: var(--content-header-size);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -568,6 +568,7 @@ li.module-hover {
|
||||||
// New projects page
|
// New projects page
|
||||||
|
|
||||||
.projects-index {
|
.projects-index {
|
||||||
|
--content-header-size: 9em;
|
||||||
.content-header {
|
.content-header {
|
||||||
height: var(--content-header-size);
|
height: var(--content-header-size);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
33
app/assets/stylesheets/shared/ag_table.scss
Normal file
33
app/assets/stylesheets/shared/ag_table.scss
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
.ag-root-wrapper {
|
||||||
|
--agg-row-border-color: var(--sn-light-grey);
|
||||||
|
--ag-odd-row-background-color: var(--sn-super-light-grey);
|
||||||
|
--ag-header-background-color: var(--sn-light-grey);
|
||||||
|
--ag-selected-row-background-color: var(--sn-super-light-blue);
|
||||||
|
--ag-range-selection-border: var(--sn-science-blue);
|
||||||
|
--ag-grid-size: .5rem;
|
||||||
|
--ag-cell-horizontal-padding: calc(var(--ag-grid-size) * 2);
|
||||||
|
--ag-row-hover-color: var(--sn-super-light-grey);
|
||||||
|
--ag-header-column-resize-handle-height: 100%;
|
||||||
|
--ag-header-column-resize-handle-color: var(--sn-sleepy-grey);
|
||||||
|
--ag-header-column-resize-handle-width: 1px;
|
||||||
|
--ag-row-border-width: 0px;
|
||||||
|
--ag-icon-font-code-checkbox-unchecked: asset-url("checkbox/default.svg");
|
||||||
|
--ag-icon-font-code-checkbox-checked: asset-url("checkbox/checked.svg");
|
||||||
|
--ag-icon-font-code-checkbox-indeterminate: asset-url("checkbox/indeterminate.svg");
|
||||||
|
--ag-input-focus-box-shadow: none;
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
.ag-header {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-input-field-input {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-input-field-input:focus {
|
||||||
|
outline: none !important;
|
||||||
|
outline-offset: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
// scss-lint:disable SelectorFormat
|
// scss-lint:disable SelectorFormat
|
||||||
|
|
||||||
.cards-wrapper {
|
.cards-wrapper {
|
||||||
|
--content-header-size: 9em;
|
||||||
--card-min-width: 200px;
|
--card-min-width: 200px;
|
||||||
--list-columns-number: 5;
|
--list-columns-number: 5;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// scss-lint:disable NestingDepth QualifyingElement
|
// scss-lint:disable NestingDepth QualifyingElement
|
||||||
|
|
||||||
.content-pane {
|
.content-pane {
|
||||||
--content-header-size: 9.5em;
|
--content-header-size: 4em;
|
||||||
background-color: var(--sn-white);
|
background-color: var(--sn-white);
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fixed-content-body {
|
||||||
|
height: calc(100vh - var(--content-header-size) - var(--navbar-height));
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.content-header {
|
.content-header {
|
||||||
&.sticky-header {
|
&.sticky-header {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
top: 2.5rem;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sci-input-container-v2 {
|
.sci-input-container-v2 {
|
||||||
@apply relative h-[2.75rem] flex items-center;
|
@apply relative h-[2.75rem] flex items-center transition-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sci-input-container-v2.input-sm {
|
.sci-input-container-v2.input-sm {
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,11 @@ class LabelTemplatesController < ApplicationController
|
||||||
label_template.last_modified_by = current_user
|
label_template.last_modified_by = current_user
|
||||||
label_template.save!
|
label_template.save!
|
||||||
log_activity(:label_template_created, label_template)
|
log_activity(:label_template_created, label_template)
|
||||||
redirect_to label_template_path(label_template, new_label: true)
|
render json: { redirect_url: label_template_path(label_template, new_label: true) }
|
||||||
end
|
end
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error(e.message)
|
Rails.logger.error(e.message)
|
||||||
Rails.logger.error(e.backtrace.join("\n"))
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
flash[:error] = I18n.t('errors.general')
|
|
||||||
redirect_to label_templates_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
@ -154,7 +152,7 @@ class LabelTemplatesController < ApplicationController
|
||||||
actions:
|
actions:
|
||||||
Toolbars::LabelTemplatesService.new(
|
Toolbars::LabelTemplatesService.new(
|
||||||
current_user,
|
current_user,
|
||||||
label_template_ids: params[:label_template_ids].split(',')
|
label_template_ids: params[:item_ids].split(',')
|
||||||
).actions
|
).actions
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
71
app/datatables/custom_datatable_v2.rb
Normal file
71
app/datatables/custom_datatable_v2.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CustomDatatableV2
|
||||||
|
attr_reader :params, :options
|
||||||
|
|
||||||
|
def initialize(view, raw_data, options = {})
|
||||||
|
@raw_data = raw_data
|
||||||
|
@params = view.params
|
||||||
|
@options = options
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json(_options = {})
|
||||||
|
{
|
||||||
|
data: data,
|
||||||
|
pageTotal: records.total_pages
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def records
|
||||||
|
@records ||= fetch_records
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def order_params
|
||||||
|
@order_params ||=
|
||||||
|
params.require(:order).permit(:column, :dir).to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_records
|
||||||
|
records = get_raw_records
|
||||||
|
records = sort_records(records) if params[:order].present?
|
||||||
|
records = paginate_records(records) if params[:page].present?
|
||||||
|
records = filter_records(records) if params[:search].present?
|
||||||
|
records
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate_records(records)
|
||||||
|
records.page(params[:page]).per(params[:per_page])
|
||||||
|
end
|
||||||
|
|
||||||
|
def sort_direction(order_params)
|
||||||
|
order_params[:dir] == 'asc' ? 'ASC' : 'DESC'
|
||||||
|
end
|
||||||
|
|
||||||
|
def sort_records(records)
|
||||||
|
sort_by = "#{sortable_columns[order_params[:column].to_sym]} #{sort_direction(order_params)}"
|
||||||
|
records.order(sort_by)
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_sortable_displayed_columns
|
||||||
|
@sortable_displayed_columns = []
|
||||||
|
columns_params.each_value do |col|
|
||||||
|
@sortable_displayed_columns << col[:data] if col[:orderable] == 'true'
|
||||||
|
end
|
||||||
|
@sortable_displayed_columns
|
||||||
|
end
|
||||||
|
|
||||||
|
def formated_date
|
||||||
|
f_date = I18n.backend.date_format.dup
|
||||||
|
f_date.gsub!(/%-d/, 'FMDD')
|
||||||
|
f_date.gsub!(/%d/, 'DD')
|
||||||
|
f_date.gsub!(/%-m/, 'FMMM')
|
||||||
|
f_date.gsub!(/%m/, 'MM')
|
||||||
|
f_date.gsub!(/%b/, 'Mon')
|
||||||
|
f_date.gsub!(/%B/, 'Month')
|
||||||
|
f_date.gsub!('%Y', 'YYYY')
|
||||||
|
f_date += ' HH24:MI'
|
||||||
|
f_date
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,24 +1,19 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class LabelTemplateDatatable < CustomDatatable
|
class LabelTemplateDatatable < CustomDatatableV2
|
||||||
include InputSanitizeHelper
|
include InputSanitizeHelper
|
||||||
include Rails.application.routes.url_helpers
|
include Rails.application.routes.url_helpers
|
||||||
|
|
||||||
TABLE_COLUMNS = %w(
|
TABLE_COLUMNS = {
|
||||||
label_templates.default
|
default: 'label_templates.default',
|
||||||
label_templates.name
|
name: 'label_templates.name',
|
||||||
label_templates.type
|
format: 'label_templates.type',
|
||||||
label_templates.description
|
description: 'label_templates.description',
|
||||||
label_templates.modified_by
|
modified_by: 'label_templates.modified_by',
|
||||||
label_templates.updated_at
|
updated_at: 'label_templates.updated_at',
|
||||||
label_templates.created_by_user
|
created_by: 'label_templates.created_by_user',
|
||||||
label_templates.created_at
|
created_at: 'label_templates.created_at'
|
||||||
).freeze
|
}.freeze
|
||||||
|
|
||||||
def initialize(view, label_templates)
|
|
||||||
super(view)
|
|
||||||
@label_templates = label_templates
|
|
||||||
end
|
|
||||||
|
|
||||||
def sortable_columns
|
def sortable_columns
|
||||||
@sortable_columns ||= TABLE_COLUMNS
|
@sortable_columns ||= TABLE_COLUMNS
|
||||||
|
|
@ -30,24 +25,25 @@ class LabelTemplateDatatable < CustomDatatable
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def order_params
|
||||||
|
@order_params ||=
|
||||||
|
params.require(:order).permit(:column, :dir).to_h
|
||||||
|
end
|
||||||
|
|
||||||
def data
|
def data
|
||||||
records.map do |record|
|
records.map do |record|
|
||||||
{
|
{
|
||||||
'0' => record.id,
|
id: record.id,
|
||||||
'1' => record.default,
|
default: record.default,
|
||||||
'2' => append_format_icon(record),
|
name: append_format_icon(record),
|
||||||
'3' => escape_input(record.label_format),
|
format: escape_input(record.label_format),
|
||||||
'4' => escape_input(record.description),
|
description: escape_input(record.description),
|
||||||
'5' => escape_input(record.modified_by),
|
modified_by: escape_input(record.modified_by),
|
||||||
'6' => I18n.l(record.updated_at, format: :full),
|
updated_at: I18n.l(record.updated_at, format: :full),
|
||||||
'7' => escape_input(record.created_by_user),
|
created_by: escape_input(record.created_by_user),
|
||||||
'8' => I18n.l(record.created_at, format: :full),
|
created_at: I18n.l(record.created_at, format: :full),
|
||||||
'recordInfoUrl' => '',
|
attributes: {
|
||||||
'DT_RowAttr': {
|
edit_url: label_template_path(record)
|
||||||
'data-edit-url': label_template_path(record),
|
|
||||||
'data-set-default-url': set_default_label_template_path(record),
|
|
||||||
'data-default': record.default,
|
|
||||||
'data-format': record.label_format
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -65,7 +61,7 @@ class LabelTemplateDatatable < CustomDatatable
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_raw_records
|
def get_raw_records
|
||||||
res = @label_templates.joins(
|
res = @raw_data.joins(
|
||||||
'LEFT OUTER JOIN users AS creators ' \
|
'LEFT OUTER JOIN users AS creators ' \
|
||||||
'ON label_templates.created_by_id = creators.id'
|
'ON label_templates.created_by_id = creators.id'
|
||||||
).joins(
|
).joins(
|
||||||
|
|
@ -85,7 +81,7 @@ class LabelTemplateDatatable < CustomDatatable
|
||||||
records.where_attributes_like(
|
records.where_attributes_like(
|
||||||
['label_templates.name', 'label_templates.label_format', 'label_templates.description',
|
['label_templates.name', 'label_templates.label_format', 'label_templates.description',
|
||||||
'label_templates.modified_by', 'label_templates.created_by_user'],
|
'label_templates.modified_by', 'label_templates.created_by_user'],
|
||||||
dt_params.dig(:search, :value)
|
params[:search]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
@import "bootstrap-select/sass/bootstrap-select"
|
@import "bootstrap-select/sass/bootstrap-select";
|
||||||
|
@import "ag-grid-community/styles/ag-grid.css";
|
||||||
|
@import "ag-grid-community/styles/ag-theme-alpine.css";
|
||||||
|
|
|
||||||
19
app/javascript/packs/vue/label_templates_table.js
Normal file
19
app/javascript/packs/vue/label_templates_table.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import TurbolinksAdapter from 'vue-turbolinks';
|
||||||
|
import Vue from 'vue/dist/vue.esm';
|
||||||
|
import PerfectScrollbar from 'vue2-perfect-scrollbar';
|
||||||
|
import LabelTemplatesTable from '../../vue/label_template/table.vue';
|
||||||
|
|
||||||
|
Vue.use(TurbolinksAdapter);
|
||||||
|
Vue.use(PerfectScrollbar);
|
||||||
|
Vue.prototype.i18n = window.I18n;
|
||||||
|
|
||||||
|
window.initLabelTemplatesTableComponent = () => {
|
||||||
|
new Vue({
|
||||||
|
el: '#labelTemplatesTable',
|
||||||
|
components: {
|
||||||
|
'label-templates-table': LabelTemplatesTable,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initLabelTemplatesTableComponent();
|
||||||
154
app/javascript/vue/label_template/table.vue
Normal file
154
app/javascript/vue/label_template/table.vue
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full">
|
||||||
|
<DataTable :columnDefs="columnDefs"
|
||||||
|
:tableId="'labelTemplates'"
|
||||||
|
:dataUrl="dataSource"
|
||||||
|
:reloadingTable="reloadingTable"
|
||||||
|
:toolbarActions="toolbarActions"
|
||||||
|
:actionsUrl="actionsUrl"
|
||||||
|
@tableReloaded="reloadingTable = false"
|
||||||
|
@set_as_default="setDefault"
|
||||||
|
@duplicate="duplicate"
|
||||||
|
@create="createTemplate"
|
||||||
|
@sync_fluics="syncFluicsLabels"
|
||||||
|
@delete="deleteTemplates"
|
||||||
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
:title="i18n.t('label_templates.index.delete_modal.title')"
|
||||||
|
:description="i18n.t('label_templates.index.delete_modal.description')"
|
||||||
|
:confirmClass="'btn btn-danger'"
|
||||||
|
:confirmText="i18n.t('general.delete')"
|
||||||
|
ref="deleteModal"
|
||||||
|
></DeleteModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from '../../packs/custom_axios.js';
|
||||||
|
|
||||||
|
import DataTable from '../shared/datatable/table.vue'
|
||||||
|
import DeleteModal from '../shared/confirmation_modal.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LabelTemplatesTable',
|
||||||
|
components: {
|
||||||
|
DataTable,
|
||||||
|
DeleteModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
dataSource: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
actionsUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
createUrl: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
syncFluicsUrl: {
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
reloadingTable: false,
|
||||||
|
columnDefs: [ { field: "default", headerName: '', width: 80, minWidth: 80,
|
||||||
|
cellRenderer: this.defaultRenderer, sortable: true, headerComponentParams: { html: '<i class="fas fa-thumbtack"></i>' } },
|
||||||
|
{ field: "name", headerName: this.i18n.t('label_templates.index.thead_name'), cellRenderer: this.labelNameRenderer, sortable: true},
|
||||||
|
{ field: "format", headerName: this.i18n.t('label_templates.index.format'), sortable: true },
|
||||||
|
{ field: "description", headerName: this.i18n.t('label_templates.index.description'), sortable: true },
|
||||||
|
{ field: "modified_by", headerName: this.i18n.t('label_templates.index.updated_by'), sortable: true },
|
||||||
|
{ field: "updated_at", headerName: this.i18n.t('label_templates.index.updated_at'), sortable: true },
|
||||||
|
{ field: "created_by", headerName: this.i18n.t('label_templates.index.created_by'), sortable: true },
|
||||||
|
{ field: "created_at", headerName: this.i18n.t('label_templates.index.created_at'), sortable: true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
toolbarActions() {
|
||||||
|
let left = []
|
||||||
|
if (this.createUrl) {
|
||||||
|
left.push({
|
||||||
|
name: 'create',
|
||||||
|
icon: 'sn-icon sn-icon-new-task',
|
||||||
|
label: this.i18n.t('label_templates.index.toolbar.new'),
|
||||||
|
type: 'emit',
|
||||||
|
path: this.createUrl,
|
||||||
|
buttonStyle: 'btn btn-primary'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.syncFluicsUrl) {
|
||||||
|
left.push({
|
||||||
|
name: 'sync_fluics',
|
||||||
|
icon: 'fas fa-sync',
|
||||||
|
label: this.i18n.t('label_templates.index.toolbar.update_fluics_labels'),
|
||||||
|
type: 'emit',
|
||||||
|
path: this.syncFluicsUrl,
|
||||||
|
buttonStyle: 'btn btn-light'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left: left,
|
||||||
|
right: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
labelNameRenderer(params) {
|
||||||
|
let name = params.data.name;
|
||||||
|
let editUrl = params.data.attributes.edit_url;
|
||||||
|
return `<a href="${editUrl}">
|
||||||
|
${name.icon_image_tag}
|
||||||
|
${name.name}
|
||||||
|
</a>`
|
||||||
|
},
|
||||||
|
defaultRenderer(params) {
|
||||||
|
let defaultSelected = params.data.default;
|
||||||
|
return defaultSelected ? '<i class="fas fa-thumbtack"></i>' : '';
|
||||||
|
},
|
||||||
|
setDefault(action) {
|
||||||
|
axios.post(action.path).then((response) => {
|
||||||
|
this.reloadingTable = true
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
duplicate(action, rows) {
|
||||||
|
axios.post(action.path, { selected_ids: rows.map((row) => row.id) }).then((response) => {
|
||||||
|
this.reloadingTable = true
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createTemplate(action) {
|
||||||
|
axios.post(action.path).then((response) => {
|
||||||
|
window.location.href = response.data.redirect_url;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
syncFluicsLabels(action) {
|
||||||
|
axios.post(action.path).then((response) => {
|
||||||
|
this.reloadingTable = true
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async deleteTemplates(action, rows) {
|
||||||
|
const ok = await this.$refs.deleteModal.show()
|
||||||
|
if (ok) {
|
||||||
|
axios.delete(action.path, { data: { selected_ids: rows.map((row) => row.id) } }).then((response) => {
|
||||||
|
this.reloadingTable = true
|
||||||
|
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||||
|
}).catch((error) => {
|
||||||
|
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
76
app/javascript/vue/shared/confirmation_modal.vue
Normal file
76
app/javascript/vue/shared/confirmation_modal.vue
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<div ref="modal" @keydown.esc="cancel" class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-sm" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||||
|
<h4 class="modal-title">
|
||||||
|
{{ title }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" v-html="description"></div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button :class="cancelClass" @click="cancel">{{ cancelText || i18n.t('general.cancel') }}</button>
|
||||||
|
<button :class="confirmClass" @click="confirm">{{ confirmText || i18n.t('general.confirm') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'deleteStepModal',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
cancelText: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
cancelClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'btn btn-secondary'
|
||||||
|
},
|
||||||
|
confirmText: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
confirmClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'btn btn-primary'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
$(this.$refs.modal).on('hidden.bs.modal', () => {
|
||||||
|
this.resolvePromise(false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
resolvePromise: null,
|
||||||
|
rejectPromise: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show: function() {
|
||||||
|
$(this.$refs.modal).modal('show');
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.resolvePromise = resolve
|
||||||
|
this.rejectPromise = reject
|
||||||
|
})
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
this.resolvePromise(true)
|
||||||
|
$(this.$refs.modal).modal('hide');
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.resolvePromise(false)
|
||||||
|
$(this.$refs.modal).modal('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
72
app/javascript/vue/shared/datatable/action_toolbar.vue
Normal file
72
app/javascript/vue/shared/datatable/action_toolbar.vue
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-4 w-full rounded bg-sn-light-grey min-h-[68px]">
|
||||||
|
<div class="flex gap-4 items-center h-full">
|
||||||
|
<div v-if="loading && !actions.length" class="sn-action-toolbar__action">
|
||||||
|
<a class="rounded flex items-center py-1.5 px-2.5 bg-transparent text-transparent no-underline"></a>
|
||||||
|
</div>
|
||||||
|
<div v-if="!loading && actions.length === 0" class="text-sn-grey-grey">
|
||||||
|
{{ i18n.t('action_toolbar.no_actions') }}
|
||||||
|
</div>
|
||||||
|
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0">
|
||||||
|
<a :class="`rounded flex gap-2 items-center py-1.5 px-2.5 bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`"
|
||||||
|
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
|
||||||
|
:id="action.button_id"
|
||||||
|
:title="action.label"
|
||||||
|
@click="doAction(action, $event)">
|
||||||
|
<i :class="action.icon"></i>
|
||||||
|
<span class="sn-action-toolbar__button-text">{{ action.label }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ActionToolbar',
|
||||||
|
props: {
|
||||||
|
actionsUrl: { type: String, required: true },
|
||||||
|
params: { type: Object }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
actions: [],
|
||||||
|
multiple: false,
|
||||||
|
reloadCallback: null,
|
||||||
|
loaded: false,
|
||||||
|
loading: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
params() {
|
||||||
|
this.loadActions()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.loadActions();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadActions() {
|
||||||
|
this.loading = true;
|
||||||
|
this.loaded = false;
|
||||||
|
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
|
||||||
|
this.actions = data.actions;
|
||||||
|
this.loading = false;
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
doAction(action, event) {
|
||||||
|
switch(action.type) {
|
||||||
|
case 'emit':
|
||||||
|
event.preventDefault();
|
||||||
|
this.$emit('toolbar:action', action);
|
||||||
|
// do nothing, this is handled by legacy code based on the button class
|
||||||
|
break;
|
||||||
|
case 'link':
|
||||||
|
// do nothing, already handled by href
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
57
app/javascript/vue/shared/datatable/pagination.vue
Normal file
57
app/javascript/vue/shared/datatable/pagination.vue
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="pages.length > 1" class="flex gap-3 select-none">
|
||||||
|
<div class="w-9 h-9">
|
||||||
|
<div class="w-9 h-9 cursor-pointer flex items-center justify-center"
|
||||||
|
@click="$emit('setPage', currentPage - 1)"
|
||||||
|
v-if="totalPage > 5 && currentPage > 1">
|
||||||
|
<i class="sn-icon sn-icon-left cursor-pointer"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-9 h-9 cursor-pointer flex items-center justify-center"
|
||||||
|
v-for="page in pages"
|
||||||
|
:class="{ 'border-solid rounded border-sn-science-blue': page === currentPage }"
|
||||||
|
:key="page"
|
||||||
|
@click="$emit('setPage', page)">
|
||||||
|
<span >{{ page }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-9 h-9">
|
||||||
|
<div class="w-9 h-9 cursor-pointer flex items-center justify-center"
|
||||||
|
@click="$emit('setPage', currentPage + 1)"
|
||||||
|
v-if="totalPage - currentPage > 2 && totalPage > 5">
|
||||||
|
<i class="sn-icon sn-icon-right cursor-pointer"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Pagination',
|
||||||
|
props: {
|
||||||
|
totalPage: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
currentPage: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pages() {
|
||||||
|
let pages = [];
|
||||||
|
for (let i = 1; i <= this.totalPage; i++) {
|
||||||
|
if (i >= this.currentPage - 2 || this.totalPage <= 5) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pages.length === 5) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
237
app/javascript/vue/shared/datatable/table.vue
Normal file
237
app/javascript/vue/shared/datatable/table.vue
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-full">
|
||||||
|
<div class="relative flex flex-col flex-grow">
|
||||||
|
<Toolbar :toolbarActions="toolbarActions" @toolbar:action="emitAction" :searchValue="searchValue" @search:change="setSearchValue" />
|
||||||
|
<ag-grid-vue
|
||||||
|
class="ag-theme-alpine w-full flex-grow h-full"
|
||||||
|
:class="{'opacity-0': initializing}"
|
||||||
|
:columnDefs="columnDefs"
|
||||||
|
:rowData="rowData"
|
||||||
|
:defaultColDef="defaultColDef"
|
||||||
|
:rowSelection="'multiple'"
|
||||||
|
:gridOptions="gridOptions"
|
||||||
|
@grid-ready="onGridReady"
|
||||||
|
@first-data-rendered="onFirstDataRendered"
|
||||||
|
@sortChanged="setOrder"
|
||||||
|
@columnResized="saveColumnsState"
|
||||||
|
@columnMoved="saveColumnsState"
|
||||||
|
@rowSelected="setSelectedRows"
|
||||||
|
:CheckboxSelectionCallback="withCheckboxes"
|
||||||
|
>
|
||||||
|
</ag-grid-vue>
|
||||||
|
<ActionToolbar v-if="selectedRows.length > 0 && actionsUrl" :actionsUrl="actionsUrl" :params="actionsParams" @toolbar:action="emitAction" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center py-4">
|
||||||
|
<div class="mr-auto">
|
||||||
|
<Pagination
|
||||||
|
:totalPage="totalPage"
|
||||||
|
:currentPage="page"
|
||||||
|
@setPage="setPage"
|
||||||
|
></Pagination>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
{{ i18n.t('datatable.show') }}
|
||||||
|
<div class="w-36">
|
||||||
|
<Select
|
||||||
|
:value="perPage"
|
||||||
|
:options="perPageOptions"
|
||||||
|
@change="setPerPage"
|
||||||
|
></Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { AgGridVue } from "ag-grid-vue";
|
||||||
|
import axios from '../../../packs/custom_axios.js';
|
||||||
|
import Select from '../select.vue';
|
||||||
|
import PerfectScrollbar from 'vue2-perfect-scrollbar';
|
||||||
|
import Pagination from './pagination.vue';
|
||||||
|
import CustomHeader from './tableHeader';
|
||||||
|
import ActionToolbar from './action_toolbar.vue';
|
||||||
|
import Toolbar from './toolbar.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "App",
|
||||||
|
props: {
|
||||||
|
withCheckboxes: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
tableId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
columnDefs: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
dataUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
actionsUrl: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
toolbarActions: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
reloadingTable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
rowData: [],
|
||||||
|
gridApi: null,
|
||||||
|
columnApi: null,
|
||||||
|
defaultColDef: {
|
||||||
|
resizable: true
|
||||||
|
},
|
||||||
|
perPage: 20,
|
||||||
|
page: 1,
|
||||||
|
order: null,
|
||||||
|
totalPage: 0,
|
||||||
|
selectedRows: [],
|
||||||
|
searchValue: '',
|
||||||
|
initializing: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
AgGridVue,
|
||||||
|
Select,
|
||||||
|
PerfectScrollbar,
|
||||||
|
Pagination,
|
||||||
|
agColumnHeader: CustomHeader,
|
||||||
|
ActionToolbar,
|
||||||
|
Toolbar
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
perPageOptions() {
|
||||||
|
return [10, 20, 50, 100].map(value => [ value, `${value} ${this.i18n.t('datatable.rows')}` ]);
|
||||||
|
},
|
||||||
|
tableState() {
|
||||||
|
if (!localStorage.getItem(`datatable:${this.tableId}_columns_state`)) return null;
|
||||||
|
|
||||||
|
return JSON.parse(localStorage.getItem(`datatable:${this.tableId}_columns_state`));
|
||||||
|
},
|
||||||
|
actionsParams() {
|
||||||
|
return {
|
||||||
|
item_ids: this.selectedRows.map(row => row.id).join(',')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gridOptions() {
|
||||||
|
return {
|
||||||
|
suppressCellFocus: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
if (this.withCheckboxes) {
|
||||||
|
this.columnDefs.unshift({
|
||||||
|
field: "checkbox",
|
||||||
|
headerCheckboxSelection: true,
|
||||||
|
headerCheckboxSelectionFilteredOnly: true,
|
||||||
|
checkboxSelection: true,
|
||||||
|
width: 48,
|
||||||
|
minWidth: 48,
|
||||||
|
resizable: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
reloadingTable() {
|
||||||
|
if (this.reloadingTable) {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadData();
|
||||||
|
window.addEventListener('resize', this.resize);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this.resize);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resize() {
|
||||||
|
if (this.tableState) return;
|
||||||
|
|
||||||
|
this.gridApi.sizeColumnsToFit();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
axios
|
||||||
|
.get(this.dataUrl, {
|
||||||
|
params: {
|
||||||
|
per_page: this.perPage,
|
||||||
|
page: this.page,
|
||||||
|
order: this.order,
|
||||||
|
search: this.searchValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
this.selectedRows = [];
|
||||||
|
this.gridApi.setRowData(response.data.data);
|
||||||
|
this.totalPage = response.data.pageTotal;
|
||||||
|
this.$emit('tableReloaded');
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onGridReady(params) {
|
||||||
|
this.gridApi = params.api;
|
||||||
|
this.columnApi = params.columnApi;
|
||||||
|
|
||||||
|
if (this.tableState) {
|
||||||
|
this.columnApi.applyColumnState({
|
||||||
|
state: this.tableState,
|
||||||
|
applyOrder: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initializing = false;
|
||||||
|
}, 200);
|
||||||
|
},
|
||||||
|
onFirstDataRendered(params) {
|
||||||
|
this.resize();
|
||||||
|
},
|
||||||
|
setPerPage(value) {
|
||||||
|
this.perPage = value;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
setPage(page) {
|
||||||
|
this.page = page;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
setOrder() {
|
||||||
|
const orderState = this.columnApi.getColumnState().filter(column => column.sort).map(column => {
|
||||||
|
return {
|
||||||
|
column: column.colId,
|
||||||
|
dir: column.sort
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.order = orderState[0];
|
||||||
|
this.saveColumnsState();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
saveColumnsState() {
|
||||||
|
if (!this.columnApi) return;
|
||||||
|
|
||||||
|
const columnsState = this.columnApi.getColumnState();
|
||||||
|
localStorage.setItem(`datatable:${this.tableId}_columns_state`, JSON.stringify(columnsState));
|
||||||
|
},
|
||||||
|
setSelectedRows() {
|
||||||
|
this.selectedRows = this.gridApi.getSelectedRows();
|
||||||
|
},
|
||||||
|
emitAction(action) {
|
||||||
|
this.$emit(action.name, action, this.selectedRows);
|
||||||
|
},
|
||||||
|
setSearchValue(value) {
|
||||||
|
this.searchValue = value;
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
40
app/javascript/vue/shared/datatable/tableHeader.js
Normal file
40
app/javascript/vue/shared/datatable/tableHeader.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
export default {
|
||||||
|
template: `
|
||||||
|
<div class="w-full grid items-center gap-2 grid-cols-[auto_1.5rem] cursor-pointer" @click="onSortRequested((activeSort == 'asc' ? 'desc' : 'asc'), $event)">
|
||||||
|
<div v-if="params.html" class="customHeaderLabel truncate" v-html="params.html"></div>
|
||||||
|
<div v-else class="customHeaderLabel truncate">{{ params.displayName }}</div>
|
||||||
|
<div v-if="activeSort == 'asc'" class="customSortDownLabel text-sn-sleepy-grey">
|
||||||
|
<i class="sn-icon sn-icon-sort-up"></i>
|
||||||
|
</div>
|
||||||
|
<div v-if="activeSort == 'desc'" class="customSortUpLabel text-sn-sleepy-grey">
|
||||||
|
<i class="sn-icon sn-icon-sort-down"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeSort: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
beforeMount() {},
|
||||||
|
mounted() {
|
||||||
|
this.params.column.addEventListener('sortChanged', this.onSortChanged);
|
||||||
|
this.onSortChanged();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSortChanged() {
|
||||||
|
this.activeSort = null;
|
||||||
|
if (this.params.column.isSortAscending()) {
|
||||||
|
this.activeSort = 'asc';
|
||||||
|
} else if (this.params.column.isSortDescending()) {
|
||||||
|
this.activeSort = 'desc';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onSortRequested(order, event) {
|
||||||
|
if (!this.params.enableSorting) return;
|
||||||
|
|
||||||
|
this.params.setSort(order, event.shiftKey);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
82
app/javascript/vue/shared/datatable/toolbar.vue
Normal file
82
app/javascript/vue/shared/datatable/toolbar.vue
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex py-4 items-center">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a v-for="action in toolbarActions.left" :key="action.label"
|
||||||
|
:class="action.buttonStyle"
|
||||||
|
:href="action.path"
|
||||||
|
@click="doAction(action, $event)">
|
||||||
|
<i :class="action.icon"></i>
|
||||||
|
{{ action.label }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto flex items-center gap-4">
|
||||||
|
<a v-for="action in toolbarActions.right" :key="action.label"
|
||||||
|
:class="action.buttonStyle"
|
||||||
|
:href="action.path"
|
||||||
|
@click="doAction(action, $event)">
|
||||||
|
<i :class="action.icon"></i>
|
||||||
|
{{ action.label }}
|
||||||
|
</a>
|
||||||
|
<div class="sci-input-container-v2" :class="{'w-48': showSearch, 'w-11': !showSearch}">
|
||||||
|
<input
|
||||||
|
ref="searchInput"
|
||||||
|
class="sci-input-field !pr-8"
|
||||||
|
type="text"
|
||||||
|
@focus="openSearch"
|
||||||
|
@blur="hideSearch"
|
||||||
|
:value="searchValue"
|
||||||
|
:placeholder="'Search...'"
|
||||||
|
@change="$emit('search:change', $event.target.value)"
|
||||||
|
/>
|
||||||
|
<i class="sn-icon sn-icon-search !m-2.5 !ml-auto right-0"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Toolbar',
|
||||||
|
props: {
|
||||||
|
toolbarActions: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
searchValue: {
|
||||||
|
type: String,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showSearch: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
searchValue() {
|
||||||
|
if (this.searchValue.length > 0) {
|
||||||
|
this.openSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openSearch() {
|
||||||
|
this.showSearch = true;
|
||||||
|
},
|
||||||
|
hideSearch() {
|
||||||
|
if (this.searchValue.length === 0) {
|
||||||
|
this.showSearch = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doAction(action, event) {
|
||||||
|
switch(action.type) {
|
||||||
|
case 'emit':
|
||||||
|
event.preventDefault();
|
||||||
|
this.$emit('toolbar:action', action);
|
||||||
|
break;
|
||||||
|
case 'link':
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -116,8 +116,10 @@
|
||||||
updateOptionPosition() {
|
updateOptionPosition() {
|
||||||
const container = $(this.$refs.container);
|
const container = $(this.$refs.container);
|
||||||
const rect = container.get(0).getBoundingClientRect();
|
const rect = container.get(0).getBoundingClientRect();
|
||||||
|
const screenHeight = window.innerHeight;
|
||||||
let width = rect.width;
|
let width = rect.width;
|
||||||
let top = rect.top + rect.height;
|
let top = rect.top + rect.height;
|
||||||
|
let bottom = screenHeight - rect.bottom + rect.height;
|
||||||
let left = rect.left;
|
let left = rect.left;
|
||||||
|
|
||||||
const modal = container.parents('.modal-content');
|
const modal = container.parents('.modal-content');
|
||||||
|
|
@ -125,10 +127,23 @@
|
||||||
if (modal.length > 0) {
|
if (modal.length > 0) {
|
||||||
const modalRect = modal.get(0).getBoundingClientRect();
|
const modalRect = modal.get(0).getBoundingClientRect();
|
||||||
top -= modalRect.top;
|
top -= modalRect.top;
|
||||||
|
bottom -= modalRect.bottom;
|
||||||
left -= modalRect.left;
|
left -= modalRect.left;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.optionPositionStyle = `position: fixed; top: ${top}px; left: ${left}px; width: ${width}px`
|
if (rect.bottom > screenHeight / 2) {
|
||||||
|
this.optionPositionStyle = `
|
||||||
|
position: fixed;
|
||||||
|
bottom: ${bottom}px;
|
||||||
|
left: ${left}px;
|
||||||
|
width: ${width}px`
|
||||||
|
} else {
|
||||||
|
this.optionPositionStyle = `
|
||||||
|
position: fixed;
|
||||||
|
top: ${top}px;
|
||||||
|
left: ${left}px;
|
||||||
|
width: ${width}px`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setUpBlurHandlers() {
|
setUpBlurHandlers() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ module Toolbars
|
||||||
name: 'set_as_default',
|
name: 'set_as_default',
|
||||||
label: I18n.t("label_templates.index.toolbar.set_#{@label_templates.first.type}_default"),
|
label: I18n.t("label_templates.index.toolbar.set_#{@label_templates.first.type}_default"),
|
||||||
icon: 'fas fa-thumbtack',
|
icon: 'fas fa-thumbtack',
|
||||||
button_id: 'setZplDefaultLabelTemplate',
|
path: set_default_label_template_path(@label_templates.first),
|
||||||
type: :legacy
|
type: :emit
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -70,9 +70,8 @@ module Toolbars
|
||||||
name: 'duplicate',
|
name: 'duplicate',
|
||||||
label: I18n.t('label_templates.index.toolbar.duplicate'),
|
label: I18n.t('label_templates.index.toolbar.duplicate'),
|
||||||
icon: 'sn-icon sn-icon-duplicate',
|
icon: 'sn-icon sn-icon-duplicate',
|
||||||
button_id: 'duplicateLabelTemplate',
|
|
||||||
path: duplicate_label_templates_path,
|
path: duplicate_label_templates_path,
|
||||||
type: :legacy
|
type: :emit
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -87,8 +86,8 @@ module Toolbars
|
||||||
name: 'delete',
|
name: 'delete',
|
||||||
label: I18n.t('label_templates.index.toolbar.delete'),
|
label: I18n.t('label_templates.index.toolbar.delete'),
|
||||||
icon: 'sn-icon sn-icon-delete',
|
icon: 'sn-icon sn-icon-delete',
|
||||||
button_id: 'deleteLabelTemplate',
|
path: delete_label_templates_path,
|
||||||
type: :legacy
|
type: :emit
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<div class="modal" id="deleteLabelTemplatesModal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog modal-sm" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
|
||||||
<h4 class="modal-title">
|
|
||||||
<%= t('label_templates.index.delete_modal.title') %>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<%= t('label_templates.index.delete_modal.description') %>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t("general.cancel") %></button>
|
|
||||||
<button type="button" class="btn btn-danger"
|
|
||||||
id="confirmLabeleDeletion" data-url="<%= delete_label_templates_path %>"><%= t("general.delete") %></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
<template id="labelTemplatesToolbar">
|
|
||||||
<% if can_manage_label_templates?(current_team) %>
|
|
||||||
<button data-url="<%= label_templates_path %>" title="<%= t('label_templates.index.toolbar.new_tooltip') %>"
|
|
||||||
class="btn btn-primary auto-shrink-button" id="newLabelTemplate">
|
|
||||||
<i class="sn-icon sn-icon-new-task"></i>
|
|
||||||
<span class="button-text"><%= t('label_templates.index.toolbar.new') %></span>
|
|
||||||
</button>
|
|
||||||
<button title="<%= t('label_templates.index.toolbar.update_fluics_labels') %>"
|
|
||||||
class="btn btn-light" id="syncFluicsTemplates" data-url="<%= sync_fluics_templates_label_templates_path %>">
|
|
||||||
<i class="fas fa-sync"></i>
|
|
||||||
<span class="button-text"><%= t('label_templates.index.toolbar.update_fluics_labels') %></span>
|
|
||||||
</button>
|
|
||||||
<% end %>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,60 +1,23 @@
|
||||||
<% if current_team %>
|
<% if current_team %>
|
||||||
<% provide(:sidebar_title, t('sidebar.templates.sidebar_title')) %>
|
<% provide(:sidebar_title, t('sidebar.templates.sidebar_title')) %>
|
||||||
<% provide(:container_class, "no-second-nav-container") %>
|
<% provide(:container_class, "no-second-nav-container") %>
|
||||||
<%= content_for :sidebar do %>
|
<div class="content-pane flexible label-templates-index">
|
||||||
<%= render partial: "/shared/sidebar/templates_sidebar", locals: {active: :label} %>
|
<div class="content-header sticky-header">
|
||||||
<% end %>
|
<div class="title-row">
|
||||||
|
<h1>
|
||||||
<% content_for :head do %>
|
<%= t('label_templates.index.head_title') %>
|
||||||
<meta name="turbolinks-cache-control" content="no-cache">
|
</h1>
|
||||||
<% end %>
|
</div>
|
||||||
|
</div>
|
||||||
<%= stylesheet_link_tag 'datatables' %>
|
<div id="labelTemplatesTable" class="fixed-content-body"
|
||||||
|
>
|
||||||
|
<label-templates-table
|
||||||
<div class="content-pane flexible label-templates-index">
|
:actions-url="'<%= actions_toolbar_label_templates_url %>'"
|
||||||
<div class="content-header sticky-header">
|
:data-source="'<%= datatable_label_templates_path(format: :json) %>'"
|
||||||
<div class="title-row">
|
:create-url="'<%= label_templates_path if can_manage_label_templates?(current_team) %>'"
|
||||||
<h1>
|
:sync-fluics-url="'<%= sync_fluics_templates_label_templates_path if can_manage_label_templates?(current_team) %>'"
|
||||||
<%= t('label_templates.index.head_title') %>
|
/>
|
||||||
</h1>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="content-label-templates-index">
|
<%= javascript_include_tag 'vue_label_templates_table' %>
|
||||||
<div class="label-templates-datatable">
|
|
||||||
<table id="label-templates-table"
|
|
||||||
class="table"
|
|
||||||
data-source="<%= datatable_label_templates_path(format: :json) %>">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th id="select-all">
|
|
||||||
<div class="sci-checkbox-container">
|
|
||||||
<input name="select_all" type="checkbox" class="sci-checkbox">
|
|
||||||
<span class="sci-checkbox-label"></span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th id="label-template-selected"><i class="fas fa-thumbtack"></i></th>
|
|
||||||
<th id="label-template-name"><%= t('label_templates.index.thead_name') %></th>
|
|
||||||
<th id="label-template-format"><%= t('label_templates.index.format') %></th>
|
|
||||||
<th id="label-template-description"><%= t('label_templates.index.description') %></th>
|
|
||||||
<th id="label-template-updated-by"><%= t('label_templates.index.updated_by') %></th>
|
|
||||||
<th id="label-template-updated-at"><%= t('label_templates.index.updated_at') %></th>
|
|
||||||
<th id="label-template-created-by"><%= t('label_templates.index.created_by') %></th>
|
|
||||||
<th id="label-template-created-at"><%= t('label_templates.index.created_at') %></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="actionToolbar" data-behaviour="vue">
|
|
||||||
<action-toolbar actions-url="<%= actions_toolbar_label_templates_url %>" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render partial: "index_toolbar" %>
|
|
||||||
<%= render partial: "delete_modal" %>
|
|
||||||
<%= javascript_include_tag 'label_templates/label_templates_datatable' %>
|
|
||||||
<%= javascript_include_tag 'vue_components_action_toolbar' %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@ Rails.application.config.assets.precompile += %w(reports/reports_datatable.js)
|
||||||
Rails.application.config.assets.precompile += %w(reports/save_pdf_to_inventory.js)
|
Rails.application.config.assets.precompile += %w(reports/save_pdf_to_inventory.js)
|
||||||
Rails.application.config.assets.precompile += %w(reports/content.js)
|
Rails.application.config.assets.precompile += %w(reports/content.js)
|
||||||
Rails.application.config.assets.precompile += %w(session_end.js)
|
Rails.application.config.assets.precompile += %w(session_end.js)
|
||||||
Rails.application.config.assets.precompile += %w(label_templates/label_templates_datatable.js)
|
|
||||||
Rails.application.config.assets.precompile += %w(users/connected_devices.js)
|
Rails.application.config.assets.precompile += %w(users/connected_devices.js)
|
||||||
Rails.application.config.assets.precompile += %w(BrowserPrint-3.0.216.min.js)
|
Rails.application.config.assets.precompile += %w(BrowserPrint-3.0.216.min.js)
|
||||||
Rails.application.config.assets.precompile += %w(BrowserPrint-Zebra-1.0.216.min.js)
|
Rails.application.config.assets.precompile += %w(BrowserPrint-Zebra-1.0.216.min.js)
|
||||||
|
|
|
||||||
|
|
@ -3583,6 +3583,9 @@ en:
|
||||||
secret_key_hint: "(Optional) A secret key that will be included in the Webhook-Secret-Key header, for authentication purposes."
|
secret_key_hint: "(Optional) A secret key that will be included in the Webhook-Secret-Key header, for authentication purposes."
|
||||||
# This section contains general words that can be used in any parts of
|
# This section contains general words that can be used in any parts of
|
||||||
# application.
|
# application.
|
||||||
|
datatable:
|
||||||
|
show: 'Show:'
|
||||||
|
rows: 'rows'
|
||||||
tiny_mce:
|
tiny_mce:
|
||||||
upload_window_title: 'Insert an image from your computer'
|
upload_window_title: 'Insert an image from your computer'
|
||||||
upload_window_label: 'Choose an image'
|
upload_window_label: 'Choose an image'
|
||||||
|
|
@ -3603,6 +3606,7 @@ en:
|
||||||
edit: "Edit"
|
edit: "Edit"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
cancel: "Cancel"
|
cancel: "Cancel"
|
||||||
|
confirm: "Confirm"
|
||||||
duplicate: "Duplicate"
|
duplicate: "Duplicate"
|
||||||
okay: "Okay"
|
okay: "Okay"
|
||||||
back: "Back"
|
back: "Back"
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
collection do
|
collection do
|
||||||
post :duplicate
|
post :duplicate
|
||||||
post :delete
|
delete :delete
|
||||||
get :datatable
|
get :datatable
|
||||||
get :template_tags
|
get :template_tags
|
||||||
get :zpl_preview
|
get :zpl_preview
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ const entryList = {
|
||||||
vue_components_action_toolbar: './app/javascript/packs/vue/action_toolbar.js',
|
vue_components_action_toolbar: './app/javascript/packs/vue/action_toolbar.js',
|
||||||
vue_components_open_vector_editor: './app/javascript/packs/vue/open_vector_editor.js',
|
vue_components_open_vector_editor: './app/javascript/packs/vue/open_vector_editor.js',
|
||||||
vue_navigation_breadcrumbs: './app/javascript/packs/vue/navigation/breadcrumbs.js',
|
vue_navigation_breadcrumbs: './app/javascript/packs/vue/navigation/breadcrumbs.js',
|
||||||
vue_protocol_file_import_modal: './app/javascript/packs/vue/protocol_file_import_modal.js'
|
vue_protocol_file_import_modal: './app/javascript/packs/vue/protocol_file_import_modal.js',
|
||||||
|
vue_label_templates_table: './app/javascript/packs/vue/label_templates_table.js'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||||
|
|
@ -125,7 +126,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
// Add additional file types
|
// Add additional file types
|
||||||
extensions: ['.js', '.jsx', '.scss', '.css', '.vue', '.less']
|
extensions: ['.js', '.jsx', '.scss', '.css', '.vue', '.less'],
|
||||||
|
alias: {
|
||||||
|
'vue$': 'vue/dist/vue.esm.js'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.LimitChunkCountPlugin({
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
"@fortawesome/fontawesome-free": "^5.2.0",
|
"@fortawesome/fontawesome-free": "^5.2.0",
|
||||||
"@joeattardi/emoji-button": "^4.6.2",
|
"@joeattardi/emoji-button": "^4.6.2",
|
||||||
"@teselagen/ove": "^0.3.15",
|
"@teselagen/ove": "^0.3.15",
|
||||||
|
"ag-grid-community": "^30.1.0",
|
||||||
|
"ag-grid-vue": "^30.1.0",
|
||||||
"ajv": "6.12.6",
|
"ajv": "6.12.6",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
|
@ -78,6 +80,7 @@
|
||||||
"twemoji": "^12.1.4",
|
"twemoji": "^12.1.4",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-loader": "^15.9.1",
|
"vue-loader": "^15.9.1",
|
||||||
|
"vue-property-decorator": "^8.0.0",
|
||||||
"vue-template-compiler": "^2.6.12",
|
"vue-template-compiler": "^2.6.12",
|
||||||
"vue-turbolinks": "^2.2.1",
|
"vue-turbolinks": "^2.2.1",
|
||||||
"vue2-perfect-scrollbar": "^1.5.56",
|
"vue2-perfect-scrollbar": "^1.5.56",
|
||||||
|
|
|
||||||
22
yarn.lock
22
yarn.lock
|
|
@ -1780,6 +1780,16 @@ acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1:
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
|
||||||
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
||||||
|
|
||||||
|
ag-grid-community@^30.1.0:
|
||||||
|
version "30.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-30.1.0.tgz#50c532df2e6cc22596300b801651eca76459a189"
|
||||||
|
integrity sha512-D69e63CUALxfgLZSu1rXC8Xiyhu6+17zxzTV8cWsyvt5GeSDv2frQ3BKOqGZbUfVoOCLv2SQoHVTTqw8OjxavA==
|
||||||
|
|
||||||
|
ag-grid-vue@^30.1.0:
|
||||||
|
version "30.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ag-grid-vue/-/ag-grid-vue-30.1.0.tgz#79d43c5884aefe63c9169f6294f23343d7e8d7b9"
|
||||||
|
integrity sha512-5ALSbCG5u4g5Lt1nVVYD5fMLA3WhLd3sR92fCTKKGlCCoZrrnNdoqwf9q1wO1/ZbhwBp1t8c+JmfUsnzS3wFcg==
|
||||||
|
|
||||||
agent-base@6, agent-base@^6.0.2:
|
agent-base@6, agent-base@^6.0.2:
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||||
|
|
@ -7302,6 +7312,11 @@ vm-browserify@^1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||||
|
|
||||||
|
vue-class-component@^7.1.0:
|
||||||
|
version "7.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.2.6.tgz#8471e037b8e4762f5a464686e19e5afc708502e4"
|
||||||
|
integrity sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==
|
||||||
|
|
||||||
vue-hot-reload-api@^2.3.0:
|
vue-hot-reload-api@^2.3.0:
|
||||||
version "2.3.4"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||||
|
|
@ -7318,6 +7333,13 @@ vue-loader@^15.9.1:
|
||||||
vue-hot-reload-api "^2.3.0"
|
vue-hot-reload-api "^2.3.0"
|
||||||
vue-style-loader "^4.1.0"
|
vue-style-loader "^4.1.0"
|
||||||
|
|
||||||
|
vue-property-decorator@^8.0.0:
|
||||||
|
version "8.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-8.5.1.tgz#571a91cf8d2b507f537d79bf8275af3184572fff"
|
||||||
|
integrity sha512-O6OUN2OMsYTGPvgFtXeBU3jPnX5ffQ9V4I1WfxFQ6dqz6cOUbR3Usou7kgFpfiXDvV7dJQSFcJ5yUPgOtPPm1Q==
|
||||||
|
dependencies:
|
||||||
|
vue-class-component "^7.1.0"
|
||||||
|
|
||||||
vue-style-loader@^4.1.0:
|
vue-style-loader@^4.1.0:
|
||||||
version "4.1.3"
|
version "4.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
|
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue