mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Merge branch 'features/navigator-resize' into SCI-9318-making-navigator-resizable
This commit is contained in:
commit
9c302f186b
11
app/assets/images/sn-loader.svg
Normal file
11
app/assets/images/sn-loader.svg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1629_19712)">
|
||||||
|
<path d="M15.005 28.1194C11.5882 28.025 8.3431 26.6014 5.95975 24.1513C3.57641 21.7013 2.24294 18.4181 2.24294 15C2.24294 11.5819 3.57641 8.29872 5.95975 5.84865C8.3431 3.39859 11.5882 1.97498 15.005 1.88063V0C12.0373 0 9.13624 0.880028 6.66868 2.5288C4.20112 4.17757 2.27789 6.52103 1.14219 9.26284C0.00650146 12.0046 -0.290648 15.0216 0.288324 17.9323C0.867295 20.843 2.29638 23.5167 4.39487 25.6151C6.49336 27.7136 9.16699 29.1427 12.0777 29.7217C14.9884 30.3007 18.0054 30.0035 20.7472 28.8678C23.489 27.7321 25.8324 25.8089 27.4812 23.3413C29.13 20.8738 30.01 17.9727 30.01 15.005H28.1194C28.1141 18.4815 26.7307 21.8141 24.2724 24.2724C21.8141 26.7307 18.4815 28.1141 15.005 28.1194Z" fill="#EAECF0"/>
|
||||||
|
<path d="M15.005 0V1.88063C18.4833 1.88592 21.8174 3.27069 24.2759 5.73112C26.7345 8.19156 28.1167 11.5267 28.1194 15.005H30.01C30.01 11.0254 28.4291 7.20885 25.6151 4.39486C22.8012 1.58088 18.9846 0 15.005 0Z" fill="#1D2939"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1629_19712">
|
||||||
|
<rect width="30" height="30" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -182,8 +182,14 @@ var MyModuleRepositories = (function() {
|
||||||
targets: 0,
|
targets: 0,
|
||||||
className: 'item-name',
|
className: 'item-name',
|
||||||
render: function(data, type, row) {
|
render: function(data, type, row) {
|
||||||
var recordName = "<a href='" + row.recordInfoUrl + "'"
|
let recordName;
|
||||||
+ "class='record-info-link'>" + data + '</a>';
|
|
||||||
|
if (row.recordInfoUrl) {
|
||||||
|
recordName = `<a href="${row.recordInfoUrl}" class="record-info-link">${data}</a>`;
|
||||||
|
} else {
|
||||||
|
recordName = `<div class="inline-block my-2 mx-0">${data}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
if (row.hasActiveReminders) {
|
if (row.hasActiveReminders) {
|
||||||
recordName = `<div class="dropdown row-reminders-dropdown"
|
recordName = `<div class="dropdown row-reminders-dropdown"
|
||||||
data-row-reminders-url="${row.rowRemindersUrl}" tabindex='-1'>
|
data-row-reminders-url="${row.rowRemindersUrl}" tabindex='-1'>
|
||||||
|
|
|
@ -14,6 +14,31 @@ var DateTimeHelper = (function() {
|
||||||
return ('0' + value).slice(-2);
|
return ('0' + value).slice(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setDateTimePickerOpeningDirection(event) {
|
||||||
|
const element = $(event.target);
|
||||||
|
const dateTimePickerWidget = $('.bootstrap-datetimepicker-widget');
|
||||||
|
|
||||||
|
const windowHeight = element.closest('table').offset().top;
|
||||||
|
const inputTop = element.offset().top;
|
||||||
|
const pickerHeight = $('.bootstrap-datetimepicker-widget').outerHeight();
|
||||||
|
|
||||||
|
if (inputTop - windowHeight > pickerHeight) {
|
||||||
|
dateTimePickerWidget.addClass('top')
|
||||||
|
.removeClass('bottom')
|
||||||
|
.css({
|
||||||
|
top: 'auto',
|
||||||
|
bottom: '36px',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dateTimePickerWidget.addClass('bottom')
|
||||||
|
.removeClass('top')
|
||||||
|
.css({
|
||||||
|
top: '36px',
|
||||||
|
bottom: 'auto',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function recalcTimestamp(date, timeStr) {
|
function recalcTimestamp(date, timeStr) {
|
||||||
if (!isValidTimeStr(timeStr)) {
|
if (!isValidTimeStr(timeStr)) {
|
||||||
date.setHours(0);
|
date.setHours(0);
|
||||||
|
@ -221,7 +246,11 @@ var DateTimeHelper = (function() {
|
||||||
hourFormat: 24
|
hourFormat: 24
|
||||||
}).mask($cell.find('input[data-mask-type="time"]'));
|
}).mask($cell.find('input[data-mask-type="time"]'));
|
||||||
|
|
||||||
$cell.find('.calendar-input').datetimepicker({ ignoreReadonly: true, locale: 'en', format: formatJS });
|
$cell.find('.calendar-input')
|
||||||
|
.datetimepicker({ ignoreReadonly: true, locale: 'en', format: formatJS })
|
||||||
|
.on('dp.show', (e) => {
|
||||||
|
setDateTimePickerOpeningDirection(e);
|
||||||
|
});
|
||||||
initChangeEvents($cell);
|
initChangeEvents($cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,9 +302,13 @@ var DateTimeHelper = (function() {
|
||||||
|
|
||||||
$cal1.on('dp.change', function(e) {
|
$cal1.on('dp.change', function(e) {
|
||||||
$cal2.data('DateTimePicker').minDate(e.date);
|
$cal2.data('DateTimePicker').minDate(e.date);
|
||||||
|
}).on('dp.show', (e) => {
|
||||||
|
setDateTimePickerOpeningDirection(e);
|
||||||
});
|
});
|
||||||
$cal2.on('dp.change', function(e) {
|
$cal2.on('dp.change', function(e) {
|
||||||
$cal1.data('DateTimePicker').maxDate(e.date);
|
$cal1.data('DateTimePicker').maxDate(e.date);
|
||||||
|
}).on('dp.show', (e) => {
|
||||||
|
setDateTimePickerOpeningDirection(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
initChangeEvents($cell);
|
initChangeEvents($cell);
|
||||||
|
|
|
@ -284,10 +284,6 @@ var RepositoryDatatable = (function(global) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedRowsForAssignments() {
|
|
||||||
window.AssignItemsToTaskModalComponent.setShowCallback(() => rowsSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkAvailableColumns() {
|
function checkAvailableColumns() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: $(TABLE_ID).data('available-columns'),
|
url: $(TABLE_ID).data('available-columns'),
|
||||||
|
@ -817,6 +813,11 @@ var RepositoryDatatable = (function(global) {
|
||||||
initRepositoryViewSwitcher();
|
initRepositoryViewSwitcher();
|
||||||
DataTableHelpers.initLengthAppearance($(TABLE_ID).closest('.dataTables_wrapper'));
|
DataTableHelpers.initLengthAppearance($(TABLE_ID).closest('.dataTables_wrapper'));
|
||||||
|
|
||||||
|
$('.dataTables_wrapper').on('click', '.pagination', () => {
|
||||||
|
const dataTablesScrollBody = document.querySelector('.dataTables_scrollBody');
|
||||||
|
dataTablesScrollBody.scrollTo(0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
$('.dataTables_filter').addClass('hidden');
|
$('.dataTables_filter').addClass('hidden');
|
||||||
addRepositorySearch();
|
addRepositorySearch();
|
||||||
|
|
||||||
|
@ -882,7 +883,6 @@ var RepositoryDatatable = (function(global) {
|
||||||
})
|
})
|
||||||
|
|
||||||
initRowSelection();
|
initRowSelection();
|
||||||
updateSelectedRowsForAssignments();
|
|
||||||
|
|
||||||
return TABLE;
|
return TABLE;
|
||||||
}
|
}
|
||||||
|
@ -1018,7 +1018,7 @@ var RepositoryDatatable = (function(global) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
window.AssignItemsToTaskModalComponentContainer.showModal();
|
window.AssignItemsToTaskModalComponentContainer.showModal(rowsSelected);
|
||||||
})
|
})
|
||||||
.on('click', '#deleteRepositoryRecords', function(e) {
|
.on('click', '#deleteRepositoryRecords', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -17,7 +17,8 @@ var RepositoryDateColumnType = (function() {
|
||||||
$modal.on('change', `${columnContainer} #date-reminder, ${columnContainer} #date-range`, function() {
|
$modal.on('change', `${columnContainer} #date-reminder, ${columnContainer} #date-range`, function() {
|
||||||
let reminderCheckbox = $(columnContainer).find('#date-reminder');
|
let reminderCheckbox = $(columnContainer).find('#date-reminder');
|
||||||
let rangeCheckbox = $(columnContainer).find('#date-range');
|
let rangeCheckbox = $(columnContainer).find('#date-range');
|
||||||
rangeCheckbox.attr('disabled', reminderCheckbox.is(':checked'));
|
const isExistingRecord = $('#new-repo-column-submit').css('display') === 'none';
|
||||||
|
rangeCheckbox.attr('disabled', isExistingRecord || reminderCheckbox.is(':checked'));
|
||||||
reminderCheckbox.attr('disabled', rangeCheckbox.is(':checked'));
|
reminderCheckbox.attr('disabled', rangeCheckbox.is(':checked'));
|
||||||
$(columnContainer).find('.reminder-group').toggleClass('hidden', !reminderCheckbox.is(':checked'));
|
$(columnContainer).find('.reminder-group').toggleClass('hidden', !reminderCheckbox.is(':checked'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,8 @@ var RepositoryDateTimeColumnType = (function() {
|
||||||
$modal.on('change', `${columnContainer} #datetime-reminder, ${columnContainer} #datetime-range`, function() {
|
$modal.on('change', `${columnContainer} #datetime-reminder, ${columnContainer} #datetime-range`, function() {
|
||||||
let reminderCheckbox = $(columnContainer).find('#datetime-reminder');
|
let reminderCheckbox = $(columnContainer).find('#datetime-reminder');
|
||||||
let rangeCheckbox = $(columnContainer).find('#datetime-range');
|
let rangeCheckbox = $(columnContainer).find('#datetime-range');
|
||||||
rangeCheckbox.attr('disabled', reminderCheckbox.is(':checked'));
|
const isExistingRecord = $('#new-repo-column-submit').css('display') === 'none';
|
||||||
|
rangeCheckbox.attr('disabled', isExistingRecord || reminderCheckbox.is(':checked'));
|
||||||
reminderCheckbox.attr('disabled', rangeCheckbox.is(':checked'));
|
reminderCheckbox.attr('disabled', rangeCheckbox.is(':checked'));
|
||||||
$(columnContainer).find('.reminder-group').toggleClass('hidden', !reminderCheckbox.is(':checked'));
|
$(columnContainer).find('.reminder-group').toggleClass('hidden', !reminderCheckbox.is(':checked'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* global I18n HelperModule truncateLongString animateSpinner RepositoryListColumnType RepositoryStockColumnType */
|
/* global I18n HelperModule truncateLongString animateSpinner RepositoryListColumnType RepositoryStockColumnType */
|
||||||
/* global RepositoryDatatable RepositoryStatusColumnType RepositoryChecklistColumnType dropdownSelector RepositoryDateTimeColumnType */
|
/* global RepositoryDatatable RepositoryStatusColumnType RepositoryChecklistColumnType dropdownSelector RepositoryDateTimeColumnType */
|
||||||
/* global RepositoryDateColumnType RepositoryDatatable */
|
/* global RepositoryDateColumnType RepositoryDatatable _ */
|
||||||
/* eslint-disable no-restricted-globals */
|
/* eslint-disable no-restricted-globals */
|
||||||
|
|
||||||
|
|
||||||
|
@ -297,6 +297,8 @@ var RepositoryColumns = (function() {
|
||||||
} else {
|
} else {
|
||||||
thederName = el.innerText;
|
thederName = el.innerText;
|
||||||
}
|
}
|
||||||
|
thederName = _.escape(thederName);
|
||||||
|
|
||||||
if (['row-name', 'archived-by', 'archived-on'].includes(el.id)) {
|
if (['row-name', 'archived-by', 'archived-on'].includes(el.id)) {
|
||||||
visClass = '';
|
visClass = '';
|
||||||
visText = '';
|
visText = '';
|
||||||
|
|
|
@ -31,6 +31,15 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initResultComments() {
|
||||||
|
$(document).on('click', '.shareable-link-open-comments-sidebar', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('.comments-sidebar').removeClass('open');
|
||||||
|
|
||||||
|
$($(this).data('objectTarget')).addClass('open');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initResultsExpandCollapse() {
|
function initResultsExpandCollapse() {
|
||||||
$(document).on('click', '#results-collapse-btn', function() {
|
$(document).on('click', '#results-collapse-btn', function() {
|
||||||
$('.result .panel-collapse').collapse('hide');
|
$('.result .panel-collapse').collapse('hide');
|
||||||
|
@ -44,6 +53,7 @@
|
||||||
function initMyModuleResultsShow() {
|
function initMyModuleResultsShow() {
|
||||||
initAttachments();
|
initAttachments();
|
||||||
initResultsExpandCollapse();
|
initResultsExpandCollapse();
|
||||||
|
initResultComments();
|
||||||
|
|
||||||
$('.hot-table-container').each(function() {
|
$('.hot-table-container').each(function() {
|
||||||
initializeHandsonTable($(this));
|
initializeHandsonTable($(this));
|
||||||
|
|
|
@ -200,6 +200,8 @@ var MarvinJsEditorApi = (function() {
|
||||||
$('#modal_link' + json.id + ' .attachment-label').text(json.file_name);
|
$('#modal_link' + json.id + ' .attachment-label').text(json.file_name);
|
||||||
}
|
}
|
||||||
$(marvinJsModal).modal('hide');
|
$(marvinJsModal).modal('hide');
|
||||||
|
|
||||||
|
config.editor.focus();
|
||||||
config.button.dataset.inProgress = false;
|
config.button.dataset.inProgress = false;
|
||||||
|
|
||||||
if (MarvinJsEditor.saveCallback) MarvinJsEditor.saveCallback();
|
if (MarvinJsEditor.saveCallback) MarvinJsEditor.saveCallback();
|
||||||
|
|
72
app/assets/javascripts/sitewide/repository_row_card.js
Normal file
72
app/assets/javascripts/sitewide/repository_row_card.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/* global PrintModalComponent RepositoryDatatable HelperModule MyModuleRepositories */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
$(document).on('click', '.record-info-link', function(e) {
|
||||||
|
const myModuleId = $('.my-modules-protocols-index').data('task-id');
|
||||||
|
const repositoryRowURL = $(this).attr('href');
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
window.repositoryItemSidebarComponent.toggleShowHideSidebar(repositoryRowURL, myModuleId);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.print-label-button', function(e) {
|
||||||
|
var selectedRows = $(this).data('rows');
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (typeof PrintModalComponent !== 'undefined') {
|
||||||
|
PrintModalComponent.showModal = true;
|
||||||
|
if (selectedRows && selectedRows.length) {
|
||||||
|
$('#modal-info-repository-row').modal('hide');
|
||||||
|
PrintModalComponent.row_ids = selectedRows;
|
||||||
|
} else {
|
||||||
|
PrintModalComponent.row_ids = [...RepositoryDatatable.selectedRows()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.assign-inventory-button', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const assignUrl = $(this).attr('data-assign-url');
|
||||||
|
const repositoryRowId = $(this).attr('data-repository-row-id');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: assignUrl,
|
||||||
|
type: 'POST',
|
||||||
|
data: { repository_row_id: repositoryRowId },
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
HelperModule.flashAlertMsg(data.flash, 'success');
|
||||||
|
$('#modal-info-repository-row').modal('hide');
|
||||||
|
if (typeof MyModuleRepositories !== 'undefined') {
|
||||||
|
MyModuleRepositories.reloadRepositoriesList(repositoryRowId);
|
||||||
|
}
|
||||||
|
window.repositoryItemSidebarComponent.reload();
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
HelperModule.flashAlertMsg(error.responseJSON.flash, 'danger');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.export-consumption-button', function(e) {
|
||||||
|
const selectedRows = $(this).data('rows') || RepositoryDatatable.selectedRows();
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
window.initExportStockConsumptionModal();
|
||||||
|
|
||||||
|
if (window.exportStockConsumptionModalComponent) {
|
||||||
|
window.exportStockConsumptionModalComponent.fetchRepositoryData(
|
||||||
|
selectedRows,
|
||||||
|
{ repository_id: $(this).data('objectId') },
|
||||||
|
);
|
||||||
|
$('#modal-info-repository-row').modal('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}());
|
|
@ -1,125 +0,0 @@
|
||||||
/* global bwipjs PrintModalComponent RepositoryDatatable HelperModule MyModuleRepositories */
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
$(document).on('click', '.record-info-link', function(e) {
|
|
||||||
var that = $(this);
|
|
||||||
let params = {};
|
|
||||||
if ($('.my-modules-protocols-index, #results').length) {
|
|
||||||
params.my_module_id = $('.my-modules-protocols-index, #results').data('task-id');
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
method: 'GET',
|
|
||||||
url: that.attr('href'),
|
|
||||||
data: params,
|
|
||||||
dataType: 'json'
|
|
||||||
}).done(function(xhr, settings, data) {
|
|
||||||
if ($('#modal-info-repository-row').length) {
|
|
||||||
$('#modal-info-repository-row').find('.modal-body #repository_row-info-table').DataTable().destroy();
|
|
||||||
$('#modal-info-repository-row').remove();
|
|
||||||
$('.modal-backdrop').remove();
|
|
||||||
}
|
|
||||||
$('body').append($.parseHTML(data.responseJSON.html));
|
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
|
||||||
$('#modal-info-repository-row').modal('show', {
|
|
||||||
backdrop: true,
|
|
||||||
keyboard: false
|
|
||||||
}).on('hidden.bs.modal', function() {
|
|
||||||
$(this).find('.modal-body #repository_row-info-table').DataTable().destroy();
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
let barCodeCanvas = bwipjs.toCanvas('bar-code-canvas', {
|
|
||||||
bcid: 'qrcode',
|
|
||||||
text: $('#modal-info-repository-row #bar-code-canvas').data('id').toString(),
|
|
||||||
scale: 3
|
|
||||||
});
|
|
||||||
$('#modal-info-repository-row #bar-code-image').attr('src', barCodeCanvas.toDataURL('image/png'));
|
|
||||||
|
|
||||||
$('#repository_row-info-table').DataTable({
|
|
||||||
dom: 'RBltpi',
|
|
||||||
stateSave: false,
|
|
||||||
buttons: [],
|
|
||||||
processing: true,
|
|
||||||
colReorder: {
|
|
||||||
fixedColumnsLeft: 1000000 // Disable reordering
|
|
||||||
},
|
|
||||||
columnDefs: [{
|
|
||||||
targets: 0,
|
|
||||||
searchable: false,
|
|
||||||
orderable: false
|
|
||||||
}],
|
|
||||||
fnDrawCallback: function(settings, json) {
|
|
||||||
animateSpinner(this, false);
|
|
||||||
},
|
|
||||||
preDrawCallback: function(settings) {
|
|
||||||
animateSpinner(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('click', '.export-consumption-button', function() {
|
|
||||||
let selectedRows = [];
|
|
||||||
|
|
||||||
if ($(this).attr('id') === 'exportStockConsumptionButton') {
|
|
||||||
selectedRows = RepositoryDatatable.selectedRows();
|
|
||||||
} else {
|
|
||||||
selectedRows = $('#modal-info-repository-row .print-label-button').data('rows');
|
|
||||||
}
|
|
||||||
|
|
||||||
window.initExportStockConsumptionModal();
|
|
||||||
|
|
||||||
if (window.exportStockConsumptionModalComponent) {
|
|
||||||
window.exportStockConsumptionModalComponent.fetchRepositoryData(
|
|
||||||
selectedRows,
|
|
||||||
{ repository_id: $(this).data('objectId') },
|
|
||||||
);
|
|
||||||
$('#modal-info-repository-row').modal('hide');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('click', '.print-label-button', function(e) {
|
|
||||||
var selectedRows = $(this).data('rows');
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (typeof PrintModalComponent !== 'undefined') {
|
|
||||||
PrintModalComponent.showModal = true;
|
|
||||||
if (selectedRows && selectedRows.length) {
|
|
||||||
$('#modal-info-repository-row').modal('hide');
|
|
||||||
PrintModalComponent.row_ids = selectedRows;
|
|
||||||
} else {
|
|
||||||
PrintModalComponent.row_ids = [...RepositoryDatatable.selectedRows()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('click', '.assign-inventory-button', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
let assignUrl = $(this).data('assignUrl');
|
|
||||||
let repositoryRowId = $(this).data('repositoryRowId');
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: assignUrl,
|
|
||||||
type: 'POST',
|
|
||||||
data: { repository_row_id: repositoryRowId },
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(data) {
|
|
||||||
HelperModule.flashAlertMsg(data.flash, 'success');
|
|
||||||
$('#modal-info-repository-row').modal('hide');
|
|
||||||
if (typeof MyModuleRepositories !== 'undefined') {
|
|
||||||
MyModuleRepositories.reloadRepositoriesList(repositoryRowId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
HelperModule.flashAlertMsg(error.responseJSON.flash, 'danger');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}());
|
|
|
@ -2,6 +2,7 @@
|
||||||
@import "tailwind/buttons";
|
@import "tailwind/buttons";
|
||||||
@import "tailwind/modals";
|
@import "tailwind/modals";
|
||||||
@import "tailwind/flyouts";
|
@import "tailwind/flyouts";
|
||||||
|
@import "tailwind/loader.css";
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|
|
@ -103,14 +103,3 @@
|
||||||
color: var(--sn-grey);
|
color: var(--sn-grey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1395px) {
|
|
||||||
.task-section-header {
|
|
||||||
height: 7.44rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.protocol-buttons-group {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -288,9 +288,6 @@
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
@include font-button;
|
@include font-button;
|
||||||
min-width: 200px;
|
|
||||||
padding: .5em 0;
|
|
||||||
z-index: 102;
|
|
||||||
|
|
||||||
.divider-label {
|
.divider-label {
|
||||||
@include font-small;
|
@include font-small;
|
||||||
|
|
|
@ -2,16 +2,6 @@
|
||||||
// scss-lint:disable NestingDepth
|
// scss-lint:disable NestingDepth
|
||||||
|
|
||||||
.step-checklist-items {
|
.step-checklist-items {
|
||||||
.sci-inline-edit {
|
|
||||||
margin-top: 5px;
|
|
||||||
|
|
||||||
.sci-inline-edit__content {
|
|
||||||
margin-bottom: 7px;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: .5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-checklist-item {
|
.step-checklist-item {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
@ -59,14 +49,6 @@
|
||||||
.step-checklist-item-ghost {
|
.step-checklist-item-ghost {
|
||||||
border: 1px solid $brand-primary;
|
border: 1px solid $brand-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sci-checkbox-container {
|
|
||||||
margin: 11px 0;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-checklist-container {
|
.step-checklist-container {
|
||||||
|
|
|
@ -137,4 +137,18 @@
|
||||||
.btn.btn-danger.disabled {
|
.btn.btn-danger.disabled {
|
||||||
@apply bg-sn-delete-red-disabled;
|
@apply bg-sn-delete-red-disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-text-link {
|
||||||
|
@apply text-sn-blue text-sm cursor-pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text-link:visited,
|
||||||
|
.btn-text-link:hover {
|
||||||
|
@apply text-sn-blue no-underline
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text-link.disabled,
|
||||||
|
.btn-text-link:disabled {
|
||||||
|
@apply text-sn-sleepy-grey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
6
app/assets/stylesheets/tailwind/loader.css
Normal file
6
app/assets/stylesheets/tailwind/loader.css
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@layer components {
|
||||||
|
.sci-loader {
|
||||||
|
@apply flex m-auto h-[30px] w-[30px] animate-spin;
|
||||||
|
background: image-url("sn-loader.svg") center center no-repeat;
|
||||||
|
}
|
||||||
|
}
|
|
@ -112,6 +112,6 @@ class CommentsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_manage_permissions
|
def check_manage_permissions
|
||||||
comment_editable?(@comment)
|
render_403 unless comment_editable?(@comment)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -350,7 +350,7 @@ class ProjectsController < ApplicationController
|
||||||
def notifications
|
def notifications
|
||||||
@modules = @project.assigned_modules(current_user).order(due_date: :desc)
|
@modules = @project.assigned_modules(current_user).order(due_date: :desc)
|
||||||
render json: {
|
render json: {
|
||||||
html: render_to_string(partial: 'notifications')
|
html: render_to_string(partial: 'notifications', formats: :html)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class RepositoryRowsController < ApplicationController
|
||||||
before_action :load_repository_row_print, only: %i(print rows_to_print print_zpl validate_label_template_columns)
|
before_action :load_repository_row_print, only: %i(print rows_to_print print_zpl validate_label_template_columns)
|
||||||
before_action :load_repository_or_snapshot, only: %i(print rows_to_print print_zpl validate_label_template_columns)
|
before_action :load_repository_or_snapshot, only: %i(print rows_to_print print_zpl validate_label_template_columns)
|
||||||
before_action :load_repository_row, only: %i(update assigned_task_list active_reminder_repository_cells)
|
before_action :load_repository_row, only: %i(update assigned_task_list active_reminder_repository_cells)
|
||||||
before_action :check_read_permissions, except: %i(show create update delete_records
|
before_action :check_read_permissions, except: %i(create update delete_records
|
||||||
copy_records reminder_repository_cells
|
copy_records reminder_repository_cells
|
||||||
delete_records archive_records restore_records
|
delete_records archive_records restore_records
|
||||||
actions_toolbar)
|
actions_toolbar)
|
||||||
|
@ -42,6 +42,28 @@ class RepositoryRowsController < ApplicationController
|
||||||
render json: { custom_error: I18n.t('repositories.show.repository_filter.errors.value_not_found') }
|
render json: { custom_error: I18n.t('repositories.show.repository_filter.errors.value_not_found') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@repository_row = @repository.repository_rows.find_by(id: params[:id])
|
||||||
|
return render_404 unless @repository_row
|
||||||
|
|
||||||
|
@my_module = if params[:my_module_id].present?
|
||||||
|
MyModule.repository_row_assignable_by_user(current_user).find_by(id: params[:my_module_id])
|
||||||
|
end
|
||||||
|
return render_403 if @my_module && !can_read_my_module?(@my_module)
|
||||||
|
|
||||||
|
if @my_module
|
||||||
|
@my_module_assign_error = if !can_assign_my_module_repository_rows?(@my_module)
|
||||||
|
I18n.t('repository_row.modal_info.assign_to_task_error.no_access')
|
||||||
|
elsif @repository_row.my_modules.where(id: @my_module.id).any?
|
||||||
|
I18n.t('repository_row.modal_info.assign_to_task_error.already_assigned')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@assigned_modules = @repository_row.my_modules.joins(experiment: :project)
|
||||||
|
@viewable_modules = @assigned_modules.viewable_by_user(current_user, current_user.teams)
|
||||||
|
@reminders_present = @repository_row.repository_cells.with_active_reminder(@current_user).any?
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
service = RepositoryRows::CreateRepositoryRowService
|
service = RepositoryRows::CreateRepositoryRowService
|
||||||
.call(repository: @repository, user: current_user, params: update_params)
|
.call(repository: @repository, user: current_user, params: update_params)
|
||||||
|
@ -83,7 +105,7 @@ class RepositoryRowsController < ApplicationController
|
||||||
@private_modules = @assigned_modules - @viewable_modules
|
@private_modules = @assigned_modules - @viewable_modules
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
html: render_to_string(partial: 'repositories/repository_row_info_modal')
|
html: render_to_string(partial: 'repositories/repository_row_info_modal', formats: :html)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RepositoryStockValuesController < ApplicationController
|
class RepositoryStockValuesController < ApplicationController
|
||||||
include RepositoryDatatableHelper # for use of display_cell_value method on stock update
|
include RepositoryDatatableHelper # for use of serialize_repository_cell_value method on stock update
|
||||||
|
|
||||||
before_action :load_vars
|
before_action :load_vars
|
||||||
before_action :check_manage_permissions
|
before_action :check_manage_permissions
|
||||||
|
@ -51,7 +51,7 @@ class RepositoryStockValuesController < ApplicationController
|
||||||
stock_managable: true,
|
stock_managable: true,
|
||||||
stock_status: @repository_stock_value.status,
|
stock_status: @repository_stock_value.status,
|
||||||
manageStockUrl: edit_repository_stock_repository_repository_row_url(@repository, @repository_row)
|
manageStockUrl: edit_repository_stock_repository_repository_row_url(@repository, @repository_row)
|
||||||
}.merge(display_cell_value(@repository_stock_value.repository_cell, current_team, @repository))
|
}.merge(serialize_repository_cell_value(@repository_stock_value.repository_cell, current_team, @repository))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -6,7 +6,7 @@ class TeamRepositoriesController < ApplicationController
|
||||||
|
|
||||||
# DELETE :team_id/repositories/:repository_id/team_repositories/:id
|
# DELETE :team_id/repositories/:repository_id/team_repositories/:id
|
||||||
def destroy
|
def destroy
|
||||||
team_shared_object = @repository.team_shared_objects.find(destory_params[:id])
|
team_shared_object = @repository.team_shared_objects.find(destroy_params[:id])
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
log_activity(:unshare_inventory, team_shared_object)
|
log_activity(:unshare_inventory, team_shared_object)
|
||||||
team_shared_object.destroy!
|
team_shared_object.destroy!
|
||||||
|
@ -50,7 +50,7 @@ class TeamRepositoriesController < ApplicationController
|
||||||
params.permit(:team_id, :repository_id, :target_team_id, :permission_level)
|
params.permit(:team_id, :repository_id, :target_team_id, :permission_level)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destory_params
|
def destroy_params
|
||||||
params.permit(:team_id, :id)
|
params.permit(:team_id, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,7 @@ module ApplicationHelper
|
||||||
|
|
||||||
popover_for_user_name(user, team, false, false, base64_encoded_imgs)
|
popover_for_user_name(user, team, false, false, base64_encoded_imgs)
|
||||||
end
|
end
|
||||||
new_text
|
sanitize_input(new_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate smart annotation link for one user object
|
# Generate smart annotation link for one user object
|
||||||
|
|
|
@ -4,8 +4,16 @@ require 'sanitize'
|
||||||
require 'cgi'
|
require 'cgi'
|
||||||
|
|
||||||
module InputSanitizeHelper
|
module InputSanitizeHelper
|
||||||
def sanitize_input(html, _tags = [], _attributes = [], sanitizer_config: Constants::INPUT_SANITIZE_CONFIG)
|
def sanitize_input(html, _tags = [], _attributes = [], sanitizer_config: nil)
|
||||||
Sanitize.fragment(html, sanitizer_config).html_safe
|
config =
|
||||||
|
if Rails.application.config.x.custom_sanitizer_config.present?
|
||||||
|
Rails.application.config.x.custom_sanitizer_config
|
||||||
|
elsif sanitizer_config.present?
|
||||||
|
sanitizer_config
|
||||||
|
else
|
||||||
|
Constants::INPUT_SANITIZE_CONFIG
|
||||||
|
end
|
||||||
|
Sanitize.fragment(html, config).html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
def escape_input(text)
|
def escape_input(text)
|
||||||
|
|
|
@ -112,4 +112,53 @@ module MyModulesHelper
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def serialize_assigned_my_module_value(my_module)
|
||||||
|
[
|
||||||
|
serialize_assigned_my_module_team_data(my_module.team),
|
||||||
|
serialize_assigned_my_module_project_data(my_module.project),
|
||||||
|
serialize_assigned_my_module_experiment_data(my_module.experiment),
|
||||||
|
serialize_assigned_my_module_data(my_module)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def serialize_assigned_my_module_team_data(team)
|
||||||
|
{
|
||||||
|
type: team.class.name.underscore,
|
||||||
|
value: team.name,
|
||||||
|
url: projects_path(team: team.id),
|
||||||
|
archived: false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_assigned_my_module_project_data(project)
|
||||||
|
archived = project.archived?
|
||||||
|
{
|
||||||
|
type: project.class.name.underscore,
|
||||||
|
value: project.name,
|
||||||
|
url: project_path(project, view_mode: archived ? 'archived' : 'active'),
|
||||||
|
archived: archived
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_assigned_my_module_experiment_data(experiment)
|
||||||
|
archived = experiment.archived_branch?
|
||||||
|
{
|
||||||
|
type: experiment.class.name.underscore,
|
||||||
|
value: experiment.name,
|
||||||
|
url: archived ? module_archive_experiment_path(experiment) : my_modules_experiment_path(experiment),
|
||||||
|
archived: archived
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_assigned_my_module_data(my_module)
|
||||||
|
{
|
||||||
|
type: my_module.class.name.underscore,
|
||||||
|
value: my_module.name,
|
||||||
|
url: protocols_my_module_path(my_module, view_mode: my_module.archived_branch? ? 'archived' : 'active'),
|
||||||
|
archived: my_module.archived_branch?
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,7 +43,7 @@ module RepositoryDatatableHelper
|
||||||
|
|
||||||
custom_cells.each do |cell|
|
custom_cells.each do |cell|
|
||||||
row[columns_mappings[cell.repository_column.id]] =
|
row[columns_mappings[cell.repository_column.id]] =
|
||||||
display_cell_value(cell, team, repository, reminders_enabled: reminders_enabled)
|
serialize_repository_cell_value(cell, team, repository, reminders_enabled: reminders_enabled)
|
||||||
end
|
end
|
||||||
|
|
||||||
if has_stock_management
|
if has_stock_management
|
||||||
|
@ -64,7 +64,12 @@ module RepositoryDatatableHelper
|
||||||
stock_cell = record.repository_cells.find { |cell| cell.value_type == 'RepositoryStockValue' }
|
stock_cell = record.repository_cells.find { |cell| cell.value_type == 'RepositoryStockValue' }
|
||||||
|
|
||||||
# always add stock cell, even if empty
|
# always add stock cell, even if empty
|
||||||
row['stock'] = stock_cell.present? ? display_cell_value(record.repository_stock_cell, team, repository) : {}
|
row['stock'] =
|
||||||
|
if stock_cell.present?
|
||||||
|
serialize_repository_cell_value(record.repository_stock_cell, team, repository)
|
||||||
|
else
|
||||||
|
{}
|
||||||
|
end
|
||||||
row['stock'][:stock_managable] = stock_managable
|
row['stock'][:stock_managable] = stock_managable
|
||||||
row['stock']['displayWarnings'] = display_stock_warnings?(repository)
|
row['stock']['displayWarnings'] = display_stock_warnings?(repository)
|
||||||
row['stock'][:stock_status] = stock_cell&.value&.status
|
row['stock'][:stock_status] = stock_cell&.value&.status
|
||||||
|
@ -114,7 +119,6 @@ module RepositoryDatatableHelper
|
||||||
DT_RowId: record.id,
|
DT_RowId: record.id,
|
||||||
DT_RowAttr: { 'data-state': row_style(record) },
|
DT_RowAttr: { 'data-state': row_style(record) },
|
||||||
'0': escape_input(record.name),
|
'0': escape_input(record.name),
|
||||||
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record),
|
|
||||||
rowRemindersUrl:
|
rowRemindersUrl:
|
||||||
Rails.application.routes.url_helpers
|
Rails.application.routes.url_helpers
|
||||||
.active_reminder_repository_cells_repository_repository_row_url(
|
.active_reminder_repository_cells_repository_repository_row_url(
|
||||||
|
@ -123,6 +127,11 @@ module RepositoryDatatableHelper
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unless record.repository.is_a?(RepositorySnapshot)
|
||||||
|
row['recordInfoUrl'] = Rails.application.routes.url_helpers.repository_repository_row_path(record.repository,
|
||||||
|
record)
|
||||||
|
end
|
||||||
|
|
||||||
if reminders_enabled
|
if reminders_enabled
|
||||||
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
|
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
|
||||||
end
|
end
|
||||||
|
@ -132,7 +141,12 @@ module RepositoryDatatableHelper
|
||||||
|
|
||||||
consumption_managable = stock_consumption_managable?(record, repository, my_module)
|
consumption_managable = stock_consumption_managable?(record, repository, my_module)
|
||||||
|
|
||||||
row['stock'] = stock_present ? display_cell_value(record.repository_stock_cell, record.repository.team, repository) : {}
|
row['stock'] =
|
||||||
|
if stock_present
|
||||||
|
serialize_repository_cell_value(record.repository_stock_cell, record.repository.team, repository)
|
||||||
|
else
|
||||||
|
{}
|
||||||
|
end
|
||||||
row['stock']['displayWarnings'] = display_stock_warnings?(repository)
|
row['stock']['displayWarnings'] = display_stock_warnings?(repository)
|
||||||
row['stock'][:stock_status] = record.repository_stock_cell&.value&.status
|
row['stock'][:stock_status] = record.repository_stock_cell&.value&.status
|
||||||
|
|
||||||
|
@ -140,7 +154,9 @@ module RepositoryDatatableHelper
|
||||||
if record.repository.is_a?(RepositorySnapshot)
|
if record.repository.is_a?(RepositorySnapshot)
|
||||||
row['consumedStock'] =
|
row['consumedStock'] =
|
||||||
if record.repository_stock_consumption_value.present?
|
if record.repository_stock_consumption_value.present?
|
||||||
display_cell_value(record.repository_stock_consumption_cell, record.repository.team, repository)
|
serialize_repository_cell_value(record.repository_stock_consumption_cell,
|
||||||
|
record.repository.team,
|
||||||
|
repository)
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
@ -184,24 +200,26 @@ module RepositoryDatatableHelper
|
||||||
'2': escape_input(record.name),
|
'2': escape_input(record.name),
|
||||||
'3': I18n.l(record.created_at, format: :full),
|
'3': I18n.l(record.created_at, format: :full),
|
||||||
'4': escape_input(record.created_by.full_name),
|
'4': escape_input(record.created_by.full_name),
|
||||||
'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(repository_snapshot, record)
|
'recordInfoUrl': Rails.application.routes.url_helpers
|
||||||
|
.repository_repository_row_path(repository_snapshot, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add custom columns
|
# Add custom columns
|
||||||
record.repository_cells.each do |cell|
|
record.repository_cells.each do |cell|
|
||||||
row[columns_mappings[cell.repository_column.id]] = display_cell_value(cell, team, repository_snapshot)
|
row[columns_mappings[cell.repository_column.id]] =
|
||||||
|
serialize_repository_cell_value(cell, team, repository_snapshot)
|
||||||
end
|
end
|
||||||
|
|
||||||
if has_stock_management
|
if has_stock_management
|
||||||
row['stock'] = if record.repository_stock_cell.present?
|
row['stock'] = if record.repository_stock_cell.present?
|
||||||
display_cell_value(record.repository_stock_cell, team, repository_snapshot)
|
serialize_repository_cell_value(record.repository_stock_cell, team, repository_snapshot)
|
||||||
else
|
else
|
||||||
{ value_type: 'RepositoryStockValue' }
|
{ value_type: 'RepositoryStockValue' }
|
||||||
end
|
end
|
||||||
|
|
||||||
row['consumedStock'] =
|
row['consumedStock'] =
|
||||||
if record.repository_stock_consumption_cell.present?
|
if record.repository_stock_consumption_cell.present?
|
||||||
display_cell_value(record.repository_stock_consumption_cell, team, repository_snapshot)
|
serialize_repository_cell_value(record.repository_stock_consumption_cell, team, repository_snapshot)
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
@ -220,13 +238,6 @@ module RepositoryDatatableHelper
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_perform_repository_actions(repository)
|
|
||||||
can_read_repository?(repository) ||
|
|
||||||
can_manage_repository?(repository) ||
|
|
||||||
can_create_repositories?(repository.team) ||
|
|
||||||
can_manage_repository_rows?(repository)
|
|
||||||
end
|
|
||||||
|
|
||||||
def repository_default_columns(record)
|
def repository_default_columns(record)
|
||||||
{
|
{
|
||||||
'1': assigned_row(record),
|
'1': assigned_row(record),
|
||||||
|
@ -252,7 +263,7 @@ module RepositoryDatatableHelper
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_cell_value(cell, team, repository, options = {})
|
def serialize_repository_cell_value(cell, team, repository, options = {})
|
||||||
serializer_class = "RepositoryDatatable::#{cell.repository_column.data_type}Serializer".constantize
|
serializer_class = "RepositoryDatatable::#{cell.repository_column.data_type}Serializer".constantize
|
||||||
serializer_class.new(
|
serializer_class.new(
|
||||||
cell.value,
|
cell.value,
|
||||||
|
|
|
@ -20,6 +20,7 @@ function initAssignItemsToTaskModalComponent() {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visibility: false,
|
visibility: false,
|
||||||
|
rowsToAssign: [],
|
||||||
urls: {
|
urls: {
|
||||||
assign: container.data('assign-url'),
|
assign: container.data('assign-url'),
|
||||||
projects: container.data('projects-url'),
|
projects: container.data('projects-url'),
|
||||||
|
@ -29,7 +30,8 @@ function initAssignItemsToTaskModalComponent() {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showModal() {
|
showModal(repositoryRows) {
|
||||||
|
this.rowsToAssign = repositoryRows;
|
||||||
this.visibility = true;
|
this.visibility = true;
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
|
|
|
@ -3,16 +3,13 @@
|
||||||
// eg v-click-outside="{handler: 'handlerToTrigger', exclude: [refs to ignore on click (eg 'searchInput', 'searchInputBtn')]}"
|
// eg v-click-outside="{handler: 'handlerToTrigger', exclude: [refs to ignore on click (eg 'searchInput', 'searchInputBtn')]}"
|
||||||
// eslint-enable-next-line max-len
|
// eslint-enable-next-line max-len
|
||||||
|
|
||||||
let handleOutsideClick;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
bind(el, binding, vnode) {
|
bind(el, binding, vnode) {
|
||||||
const { handler, exclude } = binding.value;
|
el._vueClickOutside_ = (e) => {
|
||||||
|
|
||||||
handleOutsideClick = (e) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
let clickedOnExcludedEl = false;
|
let clickedOnExcludedEl = false;
|
||||||
|
const { exclude } = binding.value;
|
||||||
exclude.forEach(refName => {
|
exclude.forEach(refName => {
|
||||||
if (!clickedOnExcludedEl) {
|
if (!clickedOnExcludedEl) {
|
||||||
const excludedEl = vnode.context.$refs[refName];
|
const excludedEl = vnode.context.$refs[refName];
|
||||||
|
@ -21,15 +18,17 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!el.contains(e.target) && !clickedOnExcludedEl) {
|
if (!el.contains(e.target) && !clickedOnExcludedEl) {
|
||||||
|
const { handler } = binding.value;
|
||||||
vnode.context[handler]();
|
vnode.context[handler]();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('click', handleOutsideClick);
|
document.addEventListener('click', el._vueClickOutside_);
|
||||||
document.addEventListener('touchstart', handleOutsideClick);
|
document.addEventListener('touchstart', el._vueClickOutside_);
|
||||||
},
|
},
|
||||||
unbind() {
|
unbind(el) {
|
||||||
document.removeEventListener('click', handleOutsideClick);
|
document.removeEventListener('click', el._vueClickOutside_);
|
||||||
document.removeEventListener('touchstart', handleOutsideClick);
|
document.removeEventListener('touchstart', el._vueClickOutside_);
|
||||||
|
el._vueClickOutside_ = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
21
app/javascript/packs/vue/repository_item_sidebar.js
Normal file
21
app/javascript/packs/vue/repository_item_sidebar.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* global notTurbolinksPreview */
|
||||||
|
|
||||||
|
import TurbolinksAdapter from 'vue-turbolinks';
|
||||||
|
import ScrollSpy from 'vue2-scrollspy';
|
||||||
|
import Vue from 'vue/dist/vue.esm';
|
||||||
|
import RepositoryItemSidebar from '../../vue/repository_item_sidebar/RepositoryItemSidebar.vue';
|
||||||
|
|
||||||
|
Vue.use(TurbolinksAdapter);
|
||||||
|
Vue.use(ScrollSpy);
|
||||||
|
Vue.prototype.i18n = window.I18n;
|
||||||
|
|
||||||
|
function initRepositoryItemSidebar() {
|
||||||
|
new Vue({
|
||||||
|
el: '#repositoryItemSidebar',
|
||||||
|
components: {
|
||||||
|
RepositoryItemSidebar
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initRepositoryItemSidebar();
|
|
@ -145,11 +145,11 @@ export default {
|
||||||
name: "AssignItemsToTaskModalContainer",
|
name: "AssignItemsToTaskModalContainer",
|
||||||
props: {
|
props: {
|
||||||
visibility: Boolean,
|
visibility: Boolean,
|
||||||
urls: Object
|
urls: Object,
|
||||||
|
rowsToAssign: Array
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
rowsToAssign: [],
|
|
||||||
projects: [],
|
projects: [],
|
||||||
experiments: [],
|
experiments: [],
|
||||||
tasks: [],
|
tasks: [],
|
||||||
|
@ -159,15 +159,11 @@ export default {
|
||||||
projectsLoading: null,
|
projectsLoading: null,
|
||||||
experimentsLoading: null,
|
experimentsLoading: null,
|
||||||
tasksLoading: null,
|
tasksLoading: null,
|
||||||
showCallback: null
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
SelectSearch
|
SelectSearch
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
window.AssignItemsToTaskModalComponent = this;
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
$(this.$refs.modal).on("shown.bs.modal", () => {
|
$(this.$refs.modal).on("shown.bs.modal", () => {
|
||||||
this.projectsLoading = true;
|
this.projectsLoading = true;
|
||||||
|
@ -239,8 +235,6 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
showModal() {
|
showModal() {
|
||||||
$(this.$refs.modal).modal("show");
|
$(this.$refs.modal).modal("show");
|
||||||
|
|
||||||
this.rowsToAssign = this.showCallback();
|
|
||||||
},
|
},
|
||||||
hideModal() {
|
hideModal() {
|
||||||
$(this.$refs.modal).modal("hide");
|
$(this.$refs.modal).modal("hide");
|
||||||
|
@ -317,11 +311,9 @@ export default {
|
||||||
}).always(() => {
|
}).always(() => {
|
||||||
this.resetSelectors();
|
this.resetSelectors();
|
||||||
this.reloadTable();
|
this.reloadTable();
|
||||||
|
window.repositoryItemSidebarComponent.reload();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setShowCallback(callback) {
|
|
||||||
this.showCallback = callback;
|
|
||||||
},
|
|
||||||
reloadTable() {
|
reloadTable() {
|
||||||
$('.repository-row-selector:checked').trigger('click');
|
$('.repository-row-selector:checked').trigger('click');
|
||||||
$('.repository-table')
|
$('.repository-table')
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
<button type="button" class="close" data-dismiss="modal" :aria-label="i18n.t('general.close')">
|
||||||
|
<i class="sn-icon sn-icon-close"></i>
|
||||||
|
</button>
|
||||||
<h4 class="modal-title">{{ i18n.t(`protocols.import_modal.${state}.title`) }}</h4>
|
<h4 class="modal-title">{{ i18n.t(`protocols.import_modal.${state}.title`) }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body text-xs" v-html="i18n.t(`protocols.import_modal.${state}.body_html`, { url: protocolTemplateTableUrl })">
|
<div class="modal-body text-sm" v-html="i18n.t(`protocols.import_modal.${state}.body_html`, { url: protocolTemplateTableUrl })">
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button v-if="state === 'confirm'" type="button"
|
<button v-if="state === 'confirm'" type="button"
|
||||||
|
|
|
@ -0,0 +1,352 @@
|
||||||
|
<template>
|
||||||
|
<div ref="wrapper"
|
||||||
|
class='bg-white overflow-auto gap-2.5 self-stretch rounded-tl-4 rounded-bl-4 transition-transform ease-in-out transform shadow-lg'
|
||||||
|
:class="{ 'translate-x-0 w-[565px] h-full': isShowing, 'transition-transform ease-in-out duration-400 transform translate-x-0 translate-x-full w-0': !isShowing }">
|
||||||
|
|
||||||
|
<div id="repository-item-sidebar" class="w-full h-auto pb-6 px-6 bg-white flex flex-col">
|
||||||
|
|
||||||
|
<div id="sticky-header-wrapper" class="sticky top-0 right-0 bg-white flex z-50 flex-col h-[102px] pt-6">
|
||||||
|
<div class="header flex w-full h-[30px]">
|
||||||
|
<h4 class="item-name my-auto truncate" :title="defaultColumns?.name">
|
||||||
|
{{ defaultColumns?.archived ? i18n.t('labels.archived') : '' }}
|
||||||
|
{{ defaultColumns?.name }}
|
||||||
|
</h4>
|
||||||
|
<i id="close-icon" @click="toggleShowHideSidebar(currentItemUrl)"
|
||||||
|
class="sn-icon sn-icon-close ml-auto cursor-pointer my-auto mx-0"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="divider" class="w-500 bg-sn-light-grey flex items-center self-stretch h-px my-6"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="dataLoading" class="h-full flex flex-grow-1">
|
||||||
|
<div class="sci-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else id="body-wrapper" class="flex flex-1 flex-grow-1 justify-between">
|
||||||
|
<div id="left-col" class="flex flex-col gap-4">
|
||||||
|
|
||||||
|
<!-- INFORMATION -->
|
||||||
|
<div id="information">
|
||||||
|
<div ref="information-label" id="information-label"
|
||||||
|
class="font-inter text-base font-semibold leading-7 mb-4 transition-colors duration-300">{{
|
||||||
|
i18n.t('repositories.item_card.section.information') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<!-- REPOSITORY NAME -->
|
||||||
|
<div class="flex flex-col ">
|
||||||
|
<span class="inline-block font-semibold pb-[6px]">{{
|
||||||
|
i18n.t('repositories.item_card.default_columns.repository_name') }}</span>
|
||||||
|
<span class="repository-name flex text-sn-dark-grey" :title="repository?.name">
|
||||||
|
{{ repository?.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sci-divider"></div>
|
||||||
|
|
||||||
|
<!-- CODE -->
|
||||||
|
<div class="flex flex-col ">
|
||||||
|
<span class="inline-block font-semibold pb-[6px]">{{ i18n.t('repositories.item_card.default_columns.id')
|
||||||
|
}}</span>
|
||||||
|
<span class="inline-block text-sn-dark-grey" :title="defaultColumns?.code">
|
||||||
|
{{ defaultColumns?.code }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sci-divider"></div>
|
||||||
|
|
||||||
|
<!-- ADDED ON -->
|
||||||
|
<div class="flex flex-col ">
|
||||||
|
<span class="inline-block font-semibold pb-[6px]">{{
|
||||||
|
i18n.t('repositories.item_card.default_columns.added_on')
|
||||||
|
}}</span>
|
||||||
|
<span class="inline-block text-sn-dark-grey" :title="defaultColumns?.added_on">
|
||||||
|
{{ defaultColumns?.added_on }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sci-divider"></div>
|
||||||
|
|
||||||
|
<!-- ADDED BY -->
|
||||||
|
<div class="flex flex-col ">
|
||||||
|
<span class="inline-block font-semibold pb-[6px]">{{
|
||||||
|
i18n.t('repositories.item_card.default_columns.added_by')
|
||||||
|
}}</span>
|
||||||
|
<span class="inline-block text-sn-dark-grey" :title="defaultColumns?.added_by">
|
||||||
|
{{ defaultColumns?.added_by }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="divider" class="w-500 bg-sn-light-grey flex items-center self-stretch h-px "></div>
|
||||||
|
|
||||||
|
<!-- CUSTOM COLUMNS, ASSIGNED, QR CODE -->
|
||||||
|
<div id="custom-col-assigned-qr-wrapper" class="flex flex-col gap-4">
|
||||||
|
|
||||||
|
<!-- CUSTOM COLUMNS -->
|
||||||
|
<div id="custom-columns-wrapper" class="flex flex-col min-h-[64px] h-auto">
|
||||||
|
<div ref="custom-columns-label" id="custom-columns-label"
|
||||||
|
class="font-inter text-base font-semibold leading-7 pb-4 transition-colors duration-300">
|
||||||
|
{{ i18n.t('repositories.item_card.custom_columns_label') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="customColumns?.length > 0" class="flex flex-col gap-4 w-[350px] h-auto">
|
||||||
|
<div v-for="(column, index) in customColumns" class="flex flex-col gap-4 w-[350px] h-auto relative">
|
||||||
|
<span class="absolute right-2 top-6" v-if="column?.value?.reminder === true">
|
||||||
|
<Reminder :value="column?.value" :valueType="column?.value_type" />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<component :is="column?.data_type" :key="index" :data_type="column?.data_type" :colId="column?.id"
|
||||||
|
:colName="column?.name" :colVal="column?.value" :repositoryRowId="repositoryRowId"
|
||||||
|
:repositoryId="repository?.id"
|
||||||
|
:permissions="permissions" @closeSidebar="toggleShowHideSidebar(null)" />
|
||||||
|
|
||||||
|
<div class="sci-divider" :class="{ 'hidden': index === customColumns?.length - 1 }"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.no_custom_columns_label') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="divider" class="w-500 bg-sn-light-grey flex px-8 items-center self-stretch h-px"></div>
|
||||||
|
|
||||||
|
<!-- ASSIGNED -->
|
||||||
|
<section id="assigned_wrapper" class="flex flex-col">
|
||||||
|
<div class="flex flex-row text-base font-semibold w-[350px] pb-4 leading-7 items-center justify-between" ref="assigned-label">
|
||||||
|
{{ i18n.t('repositories.item_card.section.assigned', {
|
||||||
|
count: assignedModules ?
|
||||||
|
assignedModules.total_assigned_size : 0
|
||||||
|
}) }}
|
||||||
|
<a v-if="actions?.assign_repository_row || (inRepository && !defaultColumns?.archived)"
|
||||||
|
class="btn-text-link font-normal"
|
||||||
|
:class= "{'assign-inventory-button': actions?.assign_repository_row,
|
||||||
|
'disabled': actions?.assign_repository_row && actions.assign_repository_row.disabled }"
|
||||||
|
:data-assign-url="actions?.assign_repository_row ? actions.assign_repository_row.assign_url : ''"
|
||||||
|
:data-repository-row-id="repositoryRowId"
|
||||||
|
@click="showRepositoryAssignModal">
|
||||||
|
{{ i18n.t('repositories.item_card.assigned.assign') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-if="assignedModules && assignedModules.total_assigned_size > 0">
|
||||||
|
<div v-if="privateModuleSize() > 0" class="pb-6">
|
||||||
|
{{ i18n.t('repositories.item_card.assigned.private', { count: privateModuleSize() }) }}
|
||||||
|
<hr v-if="assignedModules.viewable_modules.length > 0"
|
||||||
|
class="h-1 w-[350px] m-0 mt-6 border-dashed border-1 border-sn-light-grey" />
|
||||||
|
</div>
|
||||||
|
<div v-for="(assigned, index) in assignedModules.viewable_modules" :key="`assigned_module_${index}`"
|
||||||
|
class="flex flex-col w-[350px] mb-6 h-auto">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div v-for="(item, index_assigned) in assigned" :key="`assigned_element_${index_assigned}`">
|
||||||
|
{{ i18n.t(`repositories.item_card.assigned.labels.${item.type}`) }}
|
||||||
|
<a :href="item.url" class="text-sn-science-blue">
|
||||||
|
{{ item.archived ? i18n.t('labels.archived') : '' }} {{ item.value }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr v-if="index < assignedModules.viewable_modules.length - 1"
|
||||||
|
class="h-1 w-[350px] mt-6 mb-0 border-dashed border-1 border-sn-light-grey" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="mb-3">
|
||||||
|
{{ i18n.t('repositories.item_card.assigned.empty') }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div id="divider" class="w-500 bg-sn-light-grey flex px-8 items-center self-stretch h-px "></div>
|
||||||
|
|
||||||
|
<!-- QR -->
|
||||||
|
<section id="qr-wrapper" ref="QR-label">
|
||||||
|
<div class="font-inter text-base font-semibold leading-7 mb-4 mt-0">{{ i18n.t('repositories.item_card.section.qr') }}</div>
|
||||||
|
<div class="bar-code-container">
|
||||||
|
<canvas id="bar-code-canvas" class="hidden"></canvas>
|
||||||
|
<img :src="barCodeSrc" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NAVIGATION -->
|
||||||
|
<div ref="navigationRef" id="navigation"
|
||||||
|
class="flex item-end gap-x-4 min-w-[130px] min-h-[130px] h-fit absolute top-[102px] right-[24px] ">
|
||||||
|
<scroll-spy :itemsToCreate="[
|
||||||
|
{ id: 'highlight-item-1', textId: 'text-item-1', labelAlias: 'information_label', label: 'information-label' },
|
||||||
|
{ id: 'highlight-item-2', textId: 'text-item-2', labelAlias: 'custom_columns_label', label: 'custom-columns-label' },
|
||||||
|
{ id: 'highlight-item-3', textId: 'text-item-3', labelAlias: 'assigned_label', label: 'assigned-label' },
|
||||||
|
{ id: 'highlight-item-4', textId: 'text-item-4', labelAlias: 'QR_label', label: 'QR-label' }
|
||||||
|
]" :stickyHeaderHeightPx="102" :cardTopPaddingPx="null">
|
||||||
|
</scroll-spy>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BOTTOM -->
|
||||||
|
<div id="bottom" class="h-[100px] flex flex-col justify-end mt-4" :class="{ 'pb-6': customColumns?.length }">
|
||||||
|
<div id="divider" class="w-500 bg-sn-light-grey flex px-8 items-center self-stretch h-px mb-6"></div>
|
||||||
|
<div id="bottom-button-wrapper" class="flex h-10 justify-end">
|
||||||
|
<button type="button" class="btn btn-primary print-label-button" :data-rows="JSON.stringify([repositoryRowId])">
|
||||||
|
{{ i18n.t('repositories.item_card.print_label') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import RepositoryStockValue from './repository_values/RepositoryStockValue.vue';
|
||||||
|
import RepositoryTextValue from './repository_values/RepositoryTextValue.vue';
|
||||||
|
import RepositoryNumberValue from './repository_values/RepositoryNumberValue.vue';
|
||||||
|
import RepositoryAssetValue from './repository_values/RepositoryAssetValue.vue';
|
||||||
|
import RepositoryListValue from './repository_values/RepositoryListValue.vue';
|
||||||
|
import RepositoryChecklistValue from './repository_values/RepositoryChecklistValue.vue';
|
||||||
|
import RepositoryStatusValue from './repository_values/RepositoryStatusValue.vue';
|
||||||
|
import RepositoryDateTimeValue from './repository_values/RepositoryDateTimeValue.vue';
|
||||||
|
import RepositoryDateTimeRangeValue from './repository_values/RepositoryDateTimeRangeValue.vue';
|
||||||
|
import RepositoryDateValue from './repository_values/RepositoryDateValue.vue';
|
||||||
|
import RepositoryDateRangeValue from './repository_values/RepositoryDateRangeValue.vue';
|
||||||
|
import RepositoryTimeRangeValue from './repository_values/RepositoryTimeRangeValue.vue'
|
||||||
|
import RepositoryTimeValue from './repository_values/RepositoryTimeValue.vue'
|
||||||
|
import ScrollSpy from './repository_values/ScrollSpy.vue';
|
||||||
|
import Reminder from './reminder.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryItemSidebar',
|
||||||
|
components: {
|
||||||
|
Reminder,
|
||||||
|
RepositoryStockValue,
|
||||||
|
RepositoryTextValue,
|
||||||
|
RepositoryNumberValue,
|
||||||
|
RepositoryAssetValue,
|
||||||
|
RepositoryListValue,
|
||||||
|
RepositoryChecklistValue,
|
||||||
|
RepositoryStatusValue,
|
||||||
|
RepositoryDateTimeValue,
|
||||||
|
RepositoryDateTimeRangeValue,
|
||||||
|
RepositoryDateValue,
|
||||||
|
RepositoryDateRangeValue,
|
||||||
|
RepositoryTimeRangeValue,
|
||||||
|
RepositoryTimeValue,
|
||||||
|
'scroll-spy': ScrollSpy
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentItemUrl: null,
|
||||||
|
dataLoading: false,
|
||||||
|
repositoryRowId: null,
|
||||||
|
repository: null,
|
||||||
|
defaultColumns: null,
|
||||||
|
customColumns: null,
|
||||||
|
assignedModules: null,
|
||||||
|
isShowing: false,
|
||||||
|
barCodeSrc: null,
|
||||||
|
permissions: null,
|
||||||
|
repositoryRowUrl: null,
|
||||||
|
actions: null,
|
||||||
|
myModuleId: null,
|
||||||
|
inRepository: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
window.repositoryItemSidebarComponent = this;
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// Add a click event listener to the document
|
||||||
|
document.addEventListener('click', this.handleOutsideClick);
|
||||||
|
this.inRepository = $('.assign-items-to-task-modal-container').length > 0;
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
delete window.repositoryItemSidebarComponent;
|
||||||
|
document.removeEventListener('click', this.handleDocumentClick);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleOutsideClick(event) {
|
||||||
|
if (!this.isShowing) return
|
||||||
|
|
||||||
|
const sidebar = this.$refs.wrapper;
|
||||||
|
// Check if the clicked element is not within the sidebar and it's not another item link
|
||||||
|
if (!sidebar.contains(event.target) && !event.target.closest('a')) {
|
||||||
|
this.toggleShowHideSidebar(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleShowHideSidebar(repositoryRowUrl, myModuleId = null) {
|
||||||
|
// initial click
|
||||||
|
if (this.currentItemUrl === null) {
|
||||||
|
this.myModuleId = myModuleId;
|
||||||
|
this.isShowing = true;
|
||||||
|
this.loadRepositoryRow(repositoryRowUrl);
|
||||||
|
this.currentItemUrl = repositoryRowUrl;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// click on the same item - should just open/close it
|
||||||
|
else if (this.currentItemUrl === repositoryRowUrl) {
|
||||||
|
this.isShowing = false;
|
||||||
|
this.currentItemUrl = null;
|
||||||
|
this.myModuleId = null;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// explicit close (from emit)
|
||||||
|
else if (repositoryRowUrl === null) {
|
||||||
|
this.isShowing = false;
|
||||||
|
this.currentItemUrl = null;
|
||||||
|
this.myModuleId = null;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// click on a different item - should just fetch new data
|
||||||
|
else {
|
||||||
|
this.myModuleId = myModuleId;
|
||||||
|
this.loadRepositoryRow(repositoryRowUrl);
|
||||||
|
this.currentItemUrl = repositoryRowUrl;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadRepositoryRow(repositoryRowUrl) {
|
||||||
|
this.dataLoading = true
|
||||||
|
$.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
url: repositoryRowUrl,
|
||||||
|
data: { my_module_id: this.myModuleId },
|
||||||
|
dataType: 'json',
|
||||||
|
success: (result) => {
|
||||||
|
this.repositoryRowId = result?.id
|
||||||
|
this.repository = result?.repository;
|
||||||
|
this.defaultColumns = result?.default_columns;
|
||||||
|
this.customColumns = result?.custom_columns;
|
||||||
|
this.assignedModules = result?.assigned_modules;
|
||||||
|
this.permissions = result?.permissions
|
||||||
|
this.actions = result?.actions;
|
||||||
|
this.dataLoading = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.generateBarCode(this?.defaultColumns?.code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reload() {
|
||||||
|
if(this.isShowing) {
|
||||||
|
this.loadRepositoryRow(this.currentItemUrl);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showRepositoryAssignModal() {
|
||||||
|
if (this.inRepository) {
|
||||||
|
window.AssignItemsToTaskModalComponentContainer.showModal([this.repositoryRowId]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generateBarCode(text) {
|
||||||
|
if(!text) return;
|
||||||
|
const barCodeCanvas = bwipjs.toCanvas('bar-code-canvas', {
|
||||||
|
bcid: 'qrcode',
|
||||||
|
text,
|
||||||
|
scale: 3
|
||||||
|
});
|
||||||
|
this.barCodeSrc = barCodeCanvas.toDataURL('image/png');
|
||||||
|
},
|
||||||
|
privateModuleSize() {
|
||||||
|
return this.assignedModules.total_assigned_size - this.assignedModules.viewable_modules.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
25
app/javascript/vue/repository_item_sidebar/reminder.vue
Normal file
25
app/javascript/vue/repository_item_sidebar/reminder.vue
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<template v-if="value.reminder === true">
|
||||||
|
<div class="inline-block float-right cursor-pointer relative" data-placement="top" data-toggle="tooltip" :title="value.text"
|
||||||
|
tabindex='-1'>
|
||||||
|
<i class="sn-icon sn-icon-notifications row-reminders-icon"></i>
|
||||||
|
<span :class="`inline-block absolute rounded-full w-2 h-2 right-1 top-0.5 ${reminderColor}`"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Reminder',
|
||||||
|
props: {
|
||||||
|
valueType: null,
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
reminderColor() {
|
||||||
|
if (this.value.reminder && (this.value.stock_amount > 0 || this.value.days_left > 0)) {
|
||||||
|
return 'bg-sn-alert-brittlebush'
|
||||||
|
}
|
||||||
|
return 'bg-sn-alert-passion';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-asset-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="file_name" @mouseover="tooltipShowing = true" @mouseout="tooltipShowing = false"
|
||||||
|
class="w-fit cursor-pointer text-sn-science-blue relative">
|
||||||
|
<a @click="$emit('closeSidebar')" class="file-preview-link" :id="modalPreviewLinkId" data-no-turbolink="true"
|
||||||
|
data-id="true" data-status="asset-present" :data-preview-url=this?.preview_url :href=this?.url>
|
||||||
|
{{ file_name }}
|
||||||
|
</a>
|
||||||
|
<tooltip-preview v-if="tooltipShowing && medium_preview_url" :id="id" :url="url" :file_name="file_name"
|
||||||
|
:preview_url="preview_url" :icon_html="icon_html" :medium_preview_url="medium_preview_url">
|
||||||
|
</tooltip-preview>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_asset_value.no_asset') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TooltipPreview from './TooltipPreview.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryAssetvalue',
|
||||||
|
components: {
|
||||||
|
"tooltip-preview": TooltipPreview
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipShowing: false,
|
||||||
|
id: null,
|
||||||
|
url: null,
|
||||||
|
preview_url: null,
|
||||||
|
file_name: null,
|
||||||
|
icon_html: null,
|
||||||
|
medium_preview_url: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.id = this?.colVal?.id
|
||||||
|
this.url = this?.colVal?.url
|
||||||
|
this.preview_url = this?.colVal?.preview_url
|
||||||
|
this.file_name = this?.colVal?.file_name
|
||||||
|
this.icon_html = this?.colVal?.icon_html
|
||||||
|
this.medium_preview_url = this?.colVal?.medium_preview_url
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modalPreviewLinkId() {
|
||||||
|
return `modal_link${this?.id}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-checklist-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="allChecklistItems">
|
||||||
|
<div v-if="isEditing"
|
||||||
|
class="text-sn-dark-grey font-inter text-sm font-normal leading-5 grid grid-rows-2 grid-cols-2 overflow-auto h-12">
|
||||||
|
<div v-for="(checklistItem, index) in allChecklistItems" :key="index">
|
||||||
|
<div class="sci-checkbox-container">
|
||||||
|
<input type="checkbox" class="sci-checkbox" :value="checklistItem?.value" v-model="selectedChecklistItems" />
|
||||||
|
<span class="sci-checkbox-label"></span>
|
||||||
|
</div>
|
||||||
|
{{ checklistItem?.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else
|
||||||
|
class="text-sn-dark-grey font-inter text-sm font-normal leading-5 h-10 w-[370px] overflow-x-auto flex flex-wrap">
|
||||||
|
<div v-for="(checklistItem, index) in allChecklistItems" :key="index">
|
||||||
|
<div id="checklist-item" class="flex w-fit h-[18px] break-words mx-1">
|
||||||
|
{{ `${checklistItem?.label} |` }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_checklist_value.no_checklist') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryChecklistValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isEditing: false,
|
||||||
|
id: null,
|
||||||
|
allChecklistItems: [],
|
||||||
|
selectedChecklistItems: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Array
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.allChecklistItems = this?.colVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-date-range-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="start_time?.formatted && end_time?.formatted"
|
||||||
|
class="text-sn-dark-grey font-inter text-sm font-normal leading-5 flex">
|
||||||
|
<div>{{ start_time?.formatted }} - {{ end_time?.formatted }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_date_range_value.no_date_range') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryDateRangeValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
start_time: null,
|
||||||
|
end_time: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.start_time = this?.colVal?.start_time
|
||||||
|
this.end_time = this?.colVal?.end_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-date-time-range-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="start_time?.formatted && end_time?.formatted"
|
||||||
|
class="text-sn-dark-grey font-inter text-sm font-normal leading-5 flex">
|
||||||
|
<div>{{ start_time?.formatted }} - {{ end_time?.formatted }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_date_time_range_value.no_date_time_range') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryDateTimeRangeValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
start_time: null,
|
||||||
|
end_time: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.start_time = this?.colVal?.start_time
|
||||||
|
this.end_time = this?.colVal?.end_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-date-time-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="formatted" class="text-sn-dark-grey font-inter text-sm font-normal leading-5 flex">
|
||||||
|
<div>{{ formatted }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_date_time_value.no_date_time') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryDateTimeValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formatted: null,
|
||||||
|
date_formatted: null,
|
||||||
|
time_formatted: null,
|
||||||
|
datetime: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.formatted = this?.colVal?.formatted
|
||||||
|
this.date_formatted = this?.colVal?.date_formatted
|
||||||
|
this.time_formatted = this?.colVal?.time_formatted
|
||||||
|
this.formatdatetimeted = this?.colVal?.datetime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-date-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="formatted" class="text-sn-dark-grey font-inter text-sm font-normal leading-5 flex">
|
||||||
|
<div>{{ formatted }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_date_value.no_date') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryDateValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formatted: null,
|
||||||
|
datetime: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.formatted = this?.colVal?.formatted
|
||||||
|
this.datetime = this?.colVal?.datetime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-list-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="text" class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_list_value.no_list') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryListValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: null,
|
||||||
|
text: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.id = this?.colVal?.id
|
||||||
|
this.text = this?.colVal?.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-number-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="colVal" class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ colVal }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_number_value.no_number') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryNumberValue',
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Number
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-status-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="status && icon"
|
||||||
|
class="flex flex-row items-center text-sn-dark-grey font-inter text-sm font-normal leading-5 ">
|
||||||
|
<div v-html="parseEmoji(icon)" class="flex mr-1.5 h-6"></div>
|
||||||
|
{{ status }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_status_value.no_status') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import twemoji from 'twemoji';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryStatusValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: null,
|
||||||
|
icon: null,
|
||||||
|
status: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.id = this?.colVal?.id
|
||||||
|
this.icon = this?.colVal?.icon
|
||||||
|
this.status = this?.colVal?.status
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
parseEmoji(content) {
|
||||||
|
return twemoji.parse(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-stock-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5 relative">
|
||||||
|
<span>{{ colName }}</span>
|
||||||
|
<a style="text-decoration: none;" class="absolute right-0 text-sn-science-blue visited:text-sn-science-blue hover:text-sn-science-blue
|
||||||
|
font-inter text-sm font-normal cursor-pointer export-consumption-button"
|
||||||
|
v-if="permissions?.can_export_repository_stock === true" :data-rows="JSON.stringify([repositoryRowId])"
|
||||||
|
:data-object-id="repositoryId">
|
||||||
|
{{ i18n.t('repositories.item_card.stock_export') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-if="colVal?.stock_formatted" class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ colVal?.stock_formatted }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_stock_value.no_stock') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryStockValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stock_formatted: null,
|
||||||
|
stock_amount: null,
|
||||||
|
low_stock_threshold: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object,
|
||||||
|
repositoryId: Number,
|
||||||
|
repositoryRowId: null,
|
||||||
|
permissions: null
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.stock_formatted = this?.colVal?.stock_formatted
|
||||||
|
this.stock_amount = this?.colVal?.stock_amount
|
||||||
|
this.low_stock_threshold = this?.colVal?.low_stock_threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-text-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="edit" class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ edit }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_text_value.no_text') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryTextValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
edit: null,
|
||||||
|
view: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.edit = this?.colVal?.edit
|
||||||
|
this.view = this?.colVal?.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-time-range-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="start_time?.formatted && end_time?.formatted"
|
||||||
|
class="text-sn-dark-grey font-inter text-sm font-normal leading-5 flex">
|
||||||
|
<div>{{ start_time?.formatted }} - {{ end_time?.formatted }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">{{
|
||||||
|
i18n.t('repositories.item_card.repository_time_range_value.no_time_range') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryTimeRangeValue',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
start_time: null,
|
||||||
|
end_time: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.start_time = this?.colVal?.start_time
|
||||||
|
this.end_time = this?.colVal?.end_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div id="repository-time-value-wrapper" class="flex flex-col min-min-h-[46px] h-auto gap-[6px]">
|
||||||
|
<div class="font-inter text-sm font-semibold leading-5">
|
||||||
|
{{ colName }}
|
||||||
|
</div>
|
||||||
|
<div v-if="formatted" class="text-sn-dark-grey font-inter text-sm font-normal leading-5 flex">
|
||||||
|
<div>
|
||||||
|
{{ formatted }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
|
||||||
|
{{ i18n.t('repositories.item_card.repository_time_value.no_time') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RepositoryTimeValue',
|
||||||
|
props: {
|
||||||
|
data_type: String,
|
||||||
|
colId: Number,
|
||||||
|
colName: String,
|
||||||
|
colVal: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formatted: null,
|
||||||
|
datetime: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.formatted = this?.colVal?.formatted
|
||||||
|
this.datetime = this?.colVal?.datetime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,94 @@
|
||||||
|
<template>
|
||||||
|
<!-- This will be re-implemented using vue2-scrollspy library in a following ticket -->
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<div id="navigation-text">
|
||||||
|
<div class="flex flex-col py-2 px-0 gap-3 self-stretch w-[130px] h-[130px] justify-center items-center">
|
||||||
|
<div v-for="(itemObj, index) in itemsToCreate" :key="index"
|
||||||
|
class="flex flex-col w-[130px] h-[130px] justify-between text-right">
|
||||||
|
<div @click="handleSideNavClick" :id="itemObj?.textId" class="hover:cursor-pointer text-sn-grey"
|
||||||
|
:class="{ 'text-sn-science-blue': selectedNavText === itemObj?.textId }">{{
|
||||||
|
i18n.t(`repositories.highlight_component.${itemObj?.labelAlias}`) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="highlight-container" class="w-[1px] h-[130px] flex flex-col justify-evenly bg-sn-light-grey">
|
||||||
|
<div v-for="(itemObj, index) in itemsToCreate" :key="index">
|
||||||
|
<div :id="itemObj?.id" class="w-[5px] h-[28px] rounded-[11px]"
|
||||||
|
:class="{ 'bg-sn-science-blue relative left-[-2px]': itemObj?.id === selectedNavIndicator }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ScrollSpy',
|
||||||
|
props: {
|
||||||
|
itemsToCreate: Array,
|
||||||
|
stickyHeaderHeightPx: Number || null,
|
||||||
|
cardTopPaddingPx: Number || null
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
rootContainerEl: null,
|
||||||
|
selectedNavText: null,
|
||||||
|
selectedNavIndicator: null,
|
||||||
|
positions: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.rootContainerEl = this.$parent.$refs.wrapper
|
||||||
|
this.rootContainerEl?.addEventListener('scroll', this.handleScrollBehaviour)
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
handleScrollBehaviour() {
|
||||||
|
// used for keeping scroll spy sticky
|
||||||
|
this.updateNavigationPositionOnScroll()
|
||||||
|
},
|
||||||
|
updateNavigationPositionOnScroll() {
|
||||||
|
const navigationDom = this?.$parent?.$refs?.navigationRef
|
||||||
|
// Get the current scroll position
|
||||||
|
const scrollPosition = this?.rootContainerEl?.scrollTop
|
||||||
|
// Adjust navigationDom position equal to the scrollPosition + the header height and the card top padding (if present)
|
||||||
|
navigationDom.style.top = `${scrollPosition + this?.stickyHeaderHeightPx + this?.cardTopPaddingPx}px`;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSideNavClick(e) {
|
||||||
|
if (!this.rootContainerEl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let refToScrollTo
|
||||||
|
const targetId = e.target.id
|
||||||
|
|
||||||
|
const foundObj = this.itemsToCreate.find((obj) => obj?.textId === targetId)
|
||||||
|
if (!foundObj) return
|
||||||
|
|
||||||
|
refToScrollTo = foundObj.label
|
||||||
|
this.selectedNavText = foundObj?.textId
|
||||||
|
this.selectedNavIndicator = foundObj?.id
|
||||||
|
const sectionLabels = this.itemsToCreate.map((obj) => obj?.label)
|
||||||
|
const labelsToUnhighlight = sectionLabels.filter((i) => i !== refToScrollTo)
|
||||||
|
|
||||||
|
// scrolling to desired section
|
||||||
|
const domElToScrollTo = this.$parent.$refs[refToScrollTo]
|
||||||
|
this.rootContainerEl.scrollTo({
|
||||||
|
top: domElToScrollTo.offsetTop - this?.stickyHeaderHeightPx - this?.cardTopPaddingPx,
|
||||||
|
behavior: "auto"
|
||||||
|
})
|
||||||
|
|
||||||
|
// flashing the title color to blue and back over 300ms
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
// wrapped in timeout to ensure that the color-change animation happens after the scrolling animation is completed
|
||||||
|
domElToScrollTo?.classList.add('text-sn-science-blue')
|
||||||
|
labelsToUnhighlight.forEach(id => document.getElementById(id)?.classList.remove('text-sn-science-blue'))
|
||||||
|
setTimeout(() => {
|
||||||
|
domElToScrollTo?.classList.remove('text-sn-science-blue')
|
||||||
|
}, 400)
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<img :src="this?.medium_preview_url" @load="onImageLoaded($event)"
|
||||||
|
class="absolute bg-sn-light-grey text-sn-black rounded pointer-events-none flex shadow-lg z-10"
|
||||||
|
:class="{ hidden: !showImage, 'top-0 transform -translate-y-full': showTop }" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TooltipPreview',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showTop: false,
|
||||||
|
showImage: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
tooltipId: String,
|
||||||
|
url: String,
|
||||||
|
preview_url: String,
|
||||||
|
file_name: String,
|
||||||
|
icon_html: String || null,
|
||||||
|
medium_preview_url: String || null,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onImageLoaded(event) {
|
||||||
|
this.showTop = !this.isInViewPort(event.target);
|
||||||
|
this.showImage = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
isInViewPort(el) {
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const height = el.naturalHeight;
|
||||||
|
const rect = el.parentElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
return (
|
||||||
|
(rect.bottom + height) <=
|
||||||
|
(window.innerHeight || document.documentElement.clientHeight)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -14,7 +14,7 @@
|
||||||
<h4 class="modal-title"> {{ i18n.t('zip_export.consumption_modal_label') }} </h4>
|
<h4 class="modal-title"> {{ i18n.t('zip_export.consumption_modal_label') }} </h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>{{ i18n.t('zip_export.consumption_header_html', { repository: this.repository?.name }) }} </p>
|
<p>{{ i18n.t('zip_export.consumption_header_html', { repository: repository?.name }) }} </p>
|
||||||
<p v-html="i18n.t('zip_export.consumption_body_html')"> </p>
|
<p v-html="i18n.t('zip_export.consumption_body_html')"> </p>
|
||||||
<p class='pb-0' v-html="i18n.t('zip_export.consumption_footer_html')"></p>
|
<p class='pb-0' v-html="i18n.t('zip_export.consumption_footer_html')"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,19 +34,22 @@ module Reports
|
||||||
proxy.set_user(user, scope: :user, store: false)
|
proxy.set_user(user, scope: :user, store: false)
|
||||||
ApplicationController.renderer.defaults[:http_host] = Rails.application.routes.default_url_options[:host]
|
ApplicationController.renderer.defaults[:http_host] = Rails.application.routes.default_url_options[:host]
|
||||||
renderer = ApplicationController.renderer.new(warden: proxy)
|
renderer = ApplicationController.renderer.new(warden: proxy)
|
||||||
|
Rails.application.config.x.custom_sanitizer_config = build_custom_sanitizer_config
|
||||||
|
|
||||||
file << renderer.render(
|
file << renderer.render(
|
||||||
pdf: 'report', header: { html: { template: "reports/templates/#{template}/header",
|
pdf: 'report',
|
||||||
locals: { report: report, user: user, logo: report_logo },
|
header: { html: { template: "reports/templates/#{template}/header",
|
||||||
layout: 'reports/footer_header' } },
|
locals: { report: report, user: user, logo: report_logo },
|
||||||
footer: { html: { template: "reports/templates/#{template}/footer",
|
layout: 'reports/footer_header' } },
|
||||||
locals: { report: report, user: user, logo: report_logo },
|
footer: { html: { template: "reports/templates/#{template}/footer",
|
||||||
layout: 'reports/footer_header' } },
|
locals: { report: report, user: user, logo: report_logo },
|
||||||
assigns: { settings: report.settings },
|
layout: 'reports/footer_header' } },
|
||||||
locals: { report: report },
|
assigns: { settings: report.settings },
|
||||||
disable_javascript: false,
|
locals: { report: report },
|
||||||
template: 'reports/report',
|
disable_javascript: false,
|
||||||
formats: :pdf
|
disable_external_links: true,
|
||||||
|
template: 'reports/report',
|
||||||
|
formats: :pdf
|
||||||
)
|
)
|
||||||
|
|
||||||
file.rewind
|
file.rewind
|
||||||
|
@ -69,6 +72,7 @@ module Reports
|
||||||
)
|
)
|
||||||
notification.create_user_notification(user)
|
notification.create_user_notification(user)
|
||||||
ensure
|
ensure
|
||||||
|
Rails.application.config.x.custom_sanitizer_config = nil
|
||||||
I18n.backend.date_format = nil
|
I18n.backend.date_format = nil
|
||||||
file.close(true)
|
file.close(true)
|
||||||
end
|
end
|
||||||
|
@ -178,6 +182,15 @@ module Reports
|
||||||
'scinote_logo.svg'
|
'scinote_logo.svg'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_custom_sanitizer_config
|
||||||
|
sanitizer_config = Constants::INPUT_SANITIZE_CONFIG.deep_dup
|
||||||
|
sanitizer_config[:protocols] = {
|
||||||
|
'a' => { 'href' => ['http', 'https', :relative] },
|
||||||
|
'img' => { 'src' => %w(data) }
|
||||||
|
}
|
||||||
|
sanitizer_config
|
||||||
|
end
|
||||||
|
|
||||||
# Overrides method from FailedDeliveryNotifiableJob concern
|
# Overrides method from FailedDeliveryNotifiableJob concern
|
||||||
def failed_notification_title
|
def failed_notification_title
|
||||||
I18n.t('projects.reports.index.generation.error_pdf_notification_title')
|
I18n.t('projects.reports.index.generation.error_pdf_notification_title')
|
||||||
|
|
|
@ -110,13 +110,17 @@ module TinyMceImages
|
||||||
next if asset.object == self
|
next if asset.object == self
|
||||||
next unless asset.can_read?(user)
|
next unless asset.can_read?(user)
|
||||||
else
|
else
|
||||||
url = image['src']
|
image_type = nil
|
||||||
image_type = FastImage.type(url).to_s
|
|
||||||
next unless image_type
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
new_image = Down.download(url, max_size: Rails.configuration.x.file_max_size_mb.megabytes)
|
uri = URI.parse(image['src'])
|
||||||
rescue Down::TooLarge => e
|
if uri.scheme != 'https'
|
||||||
|
uri.scheme = Rails.application.config.force_ssl ? 'https' : 'http'
|
||||||
|
end
|
||||||
|
image_type = FastImage.type(uri.to_s).to_s
|
||||||
|
next unless image_type
|
||||||
|
|
||||||
|
new_image = Down.download(uri.to_s, max_size: Rails.configuration.x.file_max_size_mb.megabytes)
|
||||||
|
rescue StandardError => e
|
||||||
Rails.logger.error e.message
|
Rails.logger.error e.message
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,8 @@ module RepositoryDatatable
|
||||||
url: rails_blob_path(asset.file, disposition: 'attachment'),
|
url: rails_blob_path(asset.file, disposition: 'attachment'),
|
||||||
preview_url: asset_file_preview_path(asset),
|
preview_url: asset_file_preview_path(asset),
|
||||||
file_name: escape_input(asset.file_name),
|
file_name: escape_input(asset.file_name),
|
||||||
icon_html: sn_icon_for(asset)
|
icon_html: sn_icon_for(asset),
|
||||||
|
medium_preview_url: asset.previewable? && rails_representation_url(asset.medium_preview)
|
||||||
}
|
}
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error e.message
|
Rails.logger.error e.message
|
||||||
|
|
|
@ -12,9 +12,22 @@ module RepositoryDatatable
|
||||||
|
|
||||||
if scope.dig(:options, :reminders_enabled) &&
|
if scope.dig(:options, :reminders_enabled) &&
|
||||||
!scope[:repository].is_a?(RepositorySnapshot) &&
|
!scope[:repository].is_a?(RepositorySnapshot) &&
|
||||||
scope[:column].reminder_value.present? && scope[:column].reminder_unit.present?
|
scope[:column].reminder_value.present? &&
|
||||||
|
scope[:column].reminder_unit.present?
|
||||||
reminder_delta = scope[:column].reminder_value.to_i * scope[:column].reminder_unit.to_i
|
reminder_delta = scope[:column].reminder_value.to_i * scope[:column].reminder_unit.to_i
|
||||||
data[:reminder] = reminder_delta + DateTime.now.to_i >= value_object.data.to_i
|
data[:reminder] = reminder_delta + DateTime.now.to_i >= value_object.data.to_i
|
||||||
|
data[:reminder_message] = scope[:column].reminder_message
|
||||||
|
days_left = ((value_object.data - Time.now.utc) / 1.day).ceil
|
||||||
|
if data[:reminder] && days_left.positive?
|
||||||
|
data[:days_left] = days_left
|
||||||
|
date_expiration =
|
||||||
|
"#{days_left} #{I18n.t("repositories.item_card.reminders.day.#{days_left == 1 ? 'one' : 'other'}")}"
|
||||||
|
data[:text] =
|
||||||
|
"#{I18n.t('repositories.item_card.reminders.date_expiration', date_expiration: date_expiration)}\n
|
||||||
|
#{data[:reminder_message]}"
|
||||||
|
elsif data[:reminder]
|
||||||
|
data[:text] = "#{I18n.t('repositories.item_card.reminders.item_expired')}\n#{data[:reminder_message]}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
data
|
data
|
||||||
|
|
|
@ -10,11 +10,23 @@ module RepositoryDatatable
|
||||||
|
|
||||||
if scope.dig(:options, :reminders_enabled) &&
|
if scope.dig(:options, :reminders_enabled) &&
|
||||||
!scope[:repository].is_a?(RepositorySnapshot) &&
|
!scope[:repository].is_a?(RepositorySnapshot) &&
|
||||||
scope[:column].reminder_value.present? && scope[:column].reminder_unit.present?
|
scope[:column].reminder_value.present? &&
|
||||||
|
scope[:column].reminder_unit.present?
|
||||||
reminder_delta = scope[:column].reminder_value.to_i * scope[:column].reminder_unit.to_i
|
reminder_delta = scope[:column].reminder_value.to_i * scope[:column].reminder_unit.to_i
|
||||||
data[:reminder] = reminder_delta + DateTime.now.to_i >= value_object.data.to_i
|
data[:reminder] = reminder_delta + DateTime.now.to_i >= value_object.data.to_i
|
||||||
|
data[:reminder_message] = scope[:column].reminder_message
|
||||||
|
days_left = ((value_object.data - Time.now.utc) / 1.day).ceil
|
||||||
|
if data[:reminder] && days_left.positive?
|
||||||
|
data[:days_left] = days_left
|
||||||
|
date_expiration =
|
||||||
|
"#{days_left} #{I18n.t("repositories.item_card.reminders.day.#{days_left == 1 ? 'one' : 'other'}")}"
|
||||||
|
data[:text] =
|
||||||
|
"#{I18n.t('repositories.item_card.reminders.date_expiration', date_expiration: date_expiration)}\n
|
||||||
|
#{data[:reminder_message]}"
|
||||||
|
elsif data[:reminder]
|
||||||
|
data[:text] = "#{I18n.t('repositories.item_card.reminders.item_expired')}\n#{data[:reminder_message]}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
data
|
data
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,11 +5,23 @@ module RepositoryDatatable
|
||||||
include Canaid::Helpers::PermissionsHelper
|
include Canaid::Helpers::PermissionsHelper
|
||||||
|
|
||||||
def value
|
def value
|
||||||
{
|
data = {
|
||||||
stock_formatted: value_object.formatted,
|
stock_formatted: value_object.formatted,
|
||||||
stock_amount: value_object.data,
|
stock_amount: value_object.data,
|
||||||
low_stock_threshold: value_object.low_stock_threshold
|
low_stock_threshold: value_object.low_stock_threshold
|
||||||
}
|
}
|
||||||
|
if scope.dig(:options, :reminders_enabled) &&
|
||||||
|
!scope[:repository].is_a?(RepositorySnapshot) &&
|
||||||
|
value_object.data.present? &&
|
||||||
|
value_object.low_stock_threshold.present?
|
||||||
|
data[:reminder] = value_object.low_stock_threshold > value_object.data
|
||||||
|
if data[:reminder] && (data[:stock_amount]).positive?
|
||||||
|
data[:text] = I18n.t('repositories.item_card.reminders.stock_low', stock_formated: data[:stock_formatted])
|
||||||
|
elsif data[:reminder]
|
||||||
|
data[:text] = I18n.t('repositories.item_card.reminders.stock_empty')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
data
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||||
<h4 class="modal-title">
|
<h4 class="modal-title">
|
||||||
<%= link_to assignable_path, remote: true, class: 'pull-left spacer', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
|
<%= link_to assignable_path, remote: true, class: 'pull-left spacer', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i class="fas fa-arrow-right"></i>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= t '.title', resource_name: assignable.name %>
|
<%= t '.title', resource_name: assignable.name %>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
|
@ -101,6 +101,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= render "shared/comments/comments_sidebar" %>
|
<%= render "shared/comments/comments_sidebar" %>
|
||||||
|
<%= render "shared/repository_row_sidebar" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= render partial: 'shared/flash_alerts',
|
<%= render partial: 'shared/flash_alerts',
|
||||||
|
@ -121,5 +122,7 @@
|
||||||
<span style="display: none;" data-hook="application-body-end-html"></span>
|
<span style="display: none;" data-hook="application-body-end-html"></span>
|
||||||
|
|
||||||
<%= javascript_include_tag 'prism' %>
|
<%= javascript_include_tag 'prism' %>
|
||||||
|
<%= javascript_include_tag "vue_components_repository_item_sidebar" %>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
<% if current_team.shareable_links_enabled? && can_share_my_module?(@my_module) %>
|
<% if current_team.shareable_links_enabled? && can_share_my_module?(@my_module) %>
|
||||||
<div class="share-task-container" data-behaviour="vue">
|
<div class="share-task-container" data-behaviour="vue">
|
||||||
<share-task-container
|
<share-task-container
|
||||||
<%= 'shared' if @my_module.shared? %>
|
|
||||||
shareable-link-url="<%= my_module_shareable_link_path(@my_module) %>"
|
shareable-link-url="<%= my_module_shareable_link_path(@my_module) %>"
|
||||||
<%= 'disabled' if !can_manage_my_module?(current_user, @my_module) %> />
|
:shared="<%= @my_module.shared? %>"
|
||||||
|
:disabled="<%= !can_share_my_module?(@my_module) %>" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= javascript_include_tag 'vue_share_task_container' %>
|
<%= javascript_include_tag 'vue_share_task_container' %>
|
||||||
|
|
|
@ -75,9 +75,4 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li class="form-dropdown-item">
|
|
||||||
<div class="form-dropdown-item-info">
|
|
||||||
<small><%= t('experiments.experiment_id') %>: <strong><%= experiment.code %></strong></small>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -15,10 +15,10 @@
|
||||||
<div class="report-element-body">
|
<div class="report-element-body">
|
||||||
<% if step_text.text.present? %>
|
<% if step_text.text.present? %>
|
||||||
<%= custom_auto_link(step_text.prepare_for_report(:text),
|
<%= custom_auto_link(step_text.prepare_for_report(:text),
|
||||||
team: current_team,
|
team: current_team,
|
||||||
simple_format: false,
|
simple_format: false,
|
||||||
tags: %w(img),
|
tags: %w(img),
|
||||||
base64_encoded_imgs: true) %>
|
base64_encoded_imgs: true) %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<em><%= t('projects.reports.elements.step.no_description') %></em>
|
<em><%= t('projects.reports.elements.step.no_description') %></em>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
>
|
>
|
||||||
<assign-items-to-task-modal-container
|
<assign-items-to-task-modal-container
|
||||||
:visibility="visibility"
|
:visibility="visibility"
|
||||||
|
:rows-to-assign="rowsToAssign"
|
||||||
:urls="urls"
|
:urls="urls"
|
||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
/>
|
/>
|
||||||
|
|
51
app/views/repository_rows/show.json.jbuilder
Normal file
51
app/views/repository_rows/show.json.jbuilder
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
json.id @repository_row.id
|
||||||
|
|
||||||
|
json.repository do
|
||||||
|
json.id @repository.id
|
||||||
|
json.name @repository.name
|
||||||
|
end
|
||||||
|
|
||||||
|
json.permissions do
|
||||||
|
json.can_export_repository_stock can_export_repository_stock?(@repository_row.repository)
|
||||||
|
end
|
||||||
|
|
||||||
|
json.actions do
|
||||||
|
if @my_module.present?
|
||||||
|
json.assign_repository_row do
|
||||||
|
json.assign_url my_module_repositories_path(@my_module.id)
|
||||||
|
json.disabled @my_module_assign_error.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json.default_columns do
|
||||||
|
json.name @repository_row.name
|
||||||
|
json.code @repository_row.code
|
||||||
|
json.added_on I18n.l(@repository_row.created_at, format: :full)
|
||||||
|
json.added_by @repository_row.created_by&.full_name
|
||||||
|
json.archived @repository_row.archived?
|
||||||
|
end
|
||||||
|
|
||||||
|
json.custom_columns do
|
||||||
|
json.array! @repository_row.repository.repository_columns.each do |repository_column|
|
||||||
|
repository_cell = @repository_row.repository_cells.find_by(repository_column: repository_column)
|
||||||
|
if repository_cell
|
||||||
|
json.merge! **serialize_repository_cell_value(repository_cell, @repository.team, @repository, reminders_enabled: @reminders_present).merge(
|
||||||
|
**repository_cell.repository_column.as_json(only: %i(id name data_type))
|
||||||
|
)
|
||||||
|
else
|
||||||
|
json.merge! repository_column.as_json(only: %i(id name data_type))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json.assigned_modules do
|
||||||
|
json.total_assigned_size @assigned_modules.size
|
||||||
|
json.viewable_modules do
|
||||||
|
json.array! @viewable_modules do |my_module|
|
||||||
|
json.merge! serialize_assigned_my_module_value(my_module)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5 max-w-4xl flex-1 bg-sn-white">
|
<div class="my-5 flex-1">
|
||||||
<div class="my-module-position-container">
|
<div class="my-module-position-container">
|
||||||
<!-- Header Actions -->
|
<!-- Header Actions -->
|
||||||
<%= render partial: 'shareable_links/my_modules/header_actions' %>
|
<%= render partial: 'shareable_links/my_modules/header_actions' %>
|
||||||
|
@ -27,10 +27,6 @@
|
||||||
<%= @my_module.code %>
|
<%= @my_module.code %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<%= render partial: 'shareable_links/my_modules/task_flow_button', locals: { my_module: @my_module } if @my_module.my_module_status_flow %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="details-container" class="task-details" data-shareable-link=<%= @shareable_link.uuid %>>
|
<div id="details-container" class="task-details" data-shareable-link=<%= @shareable_link.uuid %>>
|
||||||
<%= render partial: 'shareable_links/my_modules/my_module_details' %>
|
<%= render partial: 'shareable_links/my_modules/my_module_details' %>
|
||||||
|
|
|
@ -6,24 +6,22 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5 max-w-4xl flex-1 bg-sn-white">
|
<div class="my-5 flex-1">
|
||||||
<div class="content-pane flexible">
|
<div class="content-pane with-grey-background flexible">
|
||||||
<%= render partial: 'shareable_links/my_modules/header_actions' %>
|
<%= render partial: 'shareable_links/my_modules/header_actions' %>
|
||||||
<div class="px-4">
|
<div>
|
||||||
<div class="my-5" id="results-toolbar">
|
<div class="my-4 p-3 bg-sn-white" id="results-toolbar">
|
||||||
<div class="sci-btn-group collapse-expand-result">
|
<div class="flex items-center gap-4 collapse-expand-result">
|
||||||
<button class="btn btn-light" id="results-collapse-btn">
|
<button class="btn btn-secondary" id="results-collapse-btn">
|
||||||
<span class="sn-icon sn-icon-up"></span>
|
<span><%= t'my_modules.results.collapse_label' %></span>
|
||||||
<span class="hidden-xs-custom"><%= t'my_modules.results.collapse_label' %></span>
|
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-light" id="results-expand-btn">
|
<button class="btn btn-secondary" id="results-expand-btn">
|
||||||
<span class="sn-icon sn-icon-down"></span>
|
<span><%= t'my_modules.results.expand_label' %></span>
|
||||||
<span class="hidden-xs-custom"><%= t'my_modules.results.expand_label' %></span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sort-result-dropdown dropdown">
|
<div class="sort-result-dropdown dropdown">
|
||||||
<button id="sort-result-button" class="btn btn-light icon-btn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
<button id="sort-result-button" class="btn btn-light icon-btn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||||
<i class="sn-icon sn-icon-sort-up"></i>
|
<i class="sn-icon sn-icon-sort"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="sort-result-button">
|
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="sort-result-button">
|
||||||
<li><%= link_to t('general.sort_new.atoz'), shared_protocol_results_path(@shareable_link.uuid, page: params[:page], order: 'atoz'), class: (@results_order == 'atoz' ? 'selected' : '') %></li>
|
<li><%= link_to t('general.sort_new.atoz'), shared_protocol_results_path(@shareable_link.uuid, page: params[:page], order: 'atoz'), class: (@results_order == 'atoz' ? 'selected' : '') %></li>
|
||||||
|
@ -38,6 +36,7 @@
|
||||||
<div id="results">
|
<div id="results">
|
||||||
<% @results.each do |result| %>
|
<% @results.each do |result| %>
|
||||||
<%= render partial: "shareable_links/my_modules/results/result", locals: { result: result, gallery: @gallery } %>
|
<%= render partial: "shareable_links/my_modules/results/result", locals: { result: result, gallery: @gallery } %>
|
||||||
|
<%= render partial: "shareable_links/my_modules/result_comments_sidebar", locals: { result: result } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="kaminari-pagination">
|
<div class="kaminari-pagination">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<div class="step-attachments">
|
<div class="step-attachments">
|
||||||
|
<div class="sci-divider my-6"></div>
|
||||||
<div class="attachments-actions">
|
<div class="attachments-actions">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h3> <%= t('protocols.steps.files', count: attachments.length) %> </h3>
|
<h3> <%= t('protocols.steps.files', count: attachments.length) %> </h3>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<% user= nil %>
|
<% user= nil %>
|
||||||
<% step.comments.order(created_at: :asc).each do |comment| %>
|
<% oject.comments.order(created_at: :asc).each do |comment| %>
|
||||||
<div class="comment-container" data-comment-id="<%= comment.id %>">
|
<div class="comment-container" data-comment-id="<%= comment.id %>">
|
||||||
<% unless user == comment.user%>
|
<% unless user == comment.user%>
|
||||||
<div class="comment-header">
|
<div class="comment-header">
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<div class="header-actions px-5 border-solid border-0 border-b border-sn-light-grey">
|
<div class="sticky-header-element bg-sn-white border-b border-solid border-0 border-sn-sleepy-grey rounded-t px-4 py-2 top-0 sticky flex items-center flex-wrap z-[106]">
|
||||||
<div class="flex items-center uppercase">
|
<div class="flex items-center gap-4 mr-auto w-full">
|
||||||
<a class="px-4 py-3 border-b-4 border-transparent hover:no-underline capitalize <%= is_module_protocols? ? "text-sn-blue" : "text-sn-black" %>"
|
<a class="p-3 border-b-4 border-transparent hover:no-underline uppercase text-bold capitalize <%= is_module_protocols? ? "text-sn-blue" : "text-sn-black" %>"
|
||||||
href="<%= shared_protocol_url(@shareable_link.uuid) %>"
|
href="<%= shared_protocol_url(@shareable_link.uuid) %>"
|
||||||
title="<%= t("nav2.modules.steps") %>"
|
title="<%= t("nav2.modules.steps") %>"
|
||||||
>
|
>
|
||||||
<%= t("nav2.modules.steps") %>
|
<%= t("nav2.modules.steps") %>
|
||||||
</a>
|
</a>
|
||||||
<a class="px-4 py-3 border-b-4 border-transparent hover:no-underline capitalize <%= is_module_results? ? "text-sn-blue" : "text-sn-black" %>"
|
<a class="p-3 border-b-4 border-transparent hover:no-underline uppercase text-bold capitalize <%= is_module_results? ? "text-sn-blue" : "text-sn-black" %>"
|
||||||
href="<%= shared_protocol_results_path(@shareable_link.uuid) %>"
|
href="<%= shared_protocol_results_path(@shareable_link.uuid) %>"
|
||||||
title="<%= t("nav2.modules.results") %>"
|
title="<%= t("nav2.modules.results") %>"
|
||||||
>
|
>
|
||||||
|
@ -16,5 +16,9 @@
|
||||||
<sup class="navigation-results-counter"><%= @my_module.archived_branch? ? @my_module.results.size : @active_results_size %></sup>
|
<sup class="navigation-results-counter"><%= @my_module.archived_branch? ? @my_module.results.size : @active_results_size %></sup>
|
||||||
<% end %>
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex items-center gap-3 ml-auto">
|
||||||
|
<%= render partial: 'shareable_links/my_modules/task_flow_button', locals: { my_module: @my_module } if @my_module.my_module_status_flow %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,49 +19,47 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="actions-block gap-4">
|
||||||
|
<% if protocol.steps.length > 0 %>
|
||||||
|
<button class="btn btn-secondary" id="steps-collapse-btn" tabindex="0">
|
||||||
|
<%= t("protocols.steps.collapse_label") %>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" id="steps-expand-btn" tabindex="0">
|
||||||
|
<%= t("protocols.steps.expand_label") %>
|
||||||
|
</button>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="protocol-content" class="protocol-content collapse in" aria-expanded="true">
|
<div id="protocol-content" class="protocol-content collapse in " aria-expanded="true">
|
||||||
<div class="protocol-description">
|
<div class="sci-divider my-4"></div>
|
||||||
<div class="protocol-name">
|
<div class="protocol-name">
|
||||||
<span>
|
<span>
|
||||||
<%= protocol.name %>
|
<%= protocol.name %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div id="protocol-description-container" >
|
|
||||||
<% if protocol.description.present? %>
|
|
||||||
<div>
|
|
||||||
<%= smart_annotation_text(protocol.shareable_tinymce_render(:description)) %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="empty-protocol-description">
|
|
||||||
<%= t("protocols.no_text_placeholder") %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div id="protocol-steps-container">
|
<div id="protocol-description-container" >
|
||||||
<% if protocol.steps.length > 0 %>
|
<% if protocol.description.present? %>
|
||||||
<div class="protocol-step-actions">
|
<div>
|
||||||
<button class="btn btn-light" id="steps-collapse-btn" tabindex="0">
|
<%= smart_annotation_text(protocol.shareable_tinymce_render(:description)) %>
|
||||||
<span class="sn-icon sn-icon-collapse"></span>
|
</div>
|
||||||
<%= t("protocols.steps.collapse_label") %>
|
<% else %>
|
||||||
</button>
|
<div class="empty-protocol-description">
|
||||||
<button class="btn btn-light" id="steps-expand-btn" tabindex="0">
|
<%= t("protocols.no_text_placeholder") %>
|
||||||
<span class="sn-icon sn-icon-expand"></span>
|
|
||||||
<%= t("protocols.steps.expand_label") %>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sci-divider my-4"></div>
|
||||||
|
<div>
|
||||||
|
<div id="protocol-steps-container">
|
||||||
<div class="protocol-steps">
|
<div class="protocol-steps">
|
||||||
<% protocol.steps.sort_by(&:position).each do |step| %>
|
<% protocol.steps.sort_by(&:position).each do |step| %>
|
||||||
<div class="step-block">
|
<div class="step-block">
|
||||||
<%= render partial: "shareable_links/my_modules/step", locals: { step: step } %>
|
<%= render partial: "shareable_links/my_modules/step", locals: { step: step } %>
|
||||||
<%= render partial: "shareable_links/my_modules/comments_sidebar", locals: { step: step } %>
|
<%= render partial: "shareable_links/my_modules/step_comments_sidebar", locals: { step: step } %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<div class="comments-sidebar !top-0 !h-screen" id="Result<%= result.id %>">
|
||||||
|
<div class="sidebar-content">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<div class="comments-subject-title">
|
||||||
|
<%= "#{result.name}" %>
|
||||||
|
</div>
|
||||||
|
<div class="btn btn-light icon-btn close-btn">
|
||||||
|
<i class="sn-icon sn-icon-close"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-body">
|
||||||
|
<% if result.comments.present? %>
|
||||||
|
<div class="comments-list">
|
||||||
|
<%= render partial: "shareable_links/my_modules/comments_list", locals: { object: result } %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="no-comments-placeholder !block">
|
||||||
|
<%= image_tag 'comments/placeholder.svg', class: 'no-comments-image' %>
|
||||||
|
<h1><%= t('comments.empty_state.title') %></h1>
|
||||||
|
<p class="description"><%= t('comments.empty_state.description') %></p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-footer"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,26 +1,26 @@
|
||||||
<div class="step-container mt-[6px] mr-0 mb-6 ml-[-1em] pt-[8px] pr-[24px] pb-[8px] pl-0 border-solid border-[1px] border-[#fff]" id="stepContainer<%= step.id %>" >
|
<div class="step-container" id="stepContainer<%= step.id %>" >
|
||||||
<div class="step-header">
|
<div class="step-header">
|
||||||
<div class="step-element-header no-hover">
|
<div class="step-element-header no-hover">
|
||||||
<div class="step-controls flex items-center">
|
<div class="flex items-center gap-4">
|
||||||
<div class="step-element-grip-placeholder"></div>
|
|
||||||
<a class="step-collapse-link hover:no-underline focus:no-underline"
|
<a class="step-collapse-link hover:no-underline focus:no-underline"
|
||||||
href="#stepBody<%= step.id %>"
|
href="#stepBody<%= step.id %>"
|
||||||
data-toggle="collapse"
|
data-toggle="collapse"
|
||||||
data-remote="true">
|
data-remote="true">
|
||||||
<span class="sn-icon sn-icon-right "></span>
|
<span class="sn-icon sn-icon-right "></span>
|
||||||
</a>
|
</a>
|
||||||
<div class="step-complete-container mx-2 step-element--locked">
|
<div class="step-complete-container step-element--locked">
|
||||||
<div class="step-state <%= step.completed ? 'completed' : '' %>"
|
<div class="step-state <%= step.completed ? 'completed' : '' %>"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-position">
|
<div class="step-position">
|
||||||
<%= step.position + 1 %> .
|
<%= step.position + 1 %>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="step-name-container">
|
<div class="step-name-container basis-[calc(100%_-_100px)] relative">
|
||||||
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: step.name, smart_annotation_enabled: false } %>
|
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: step.name, smart_annotation_enabled: false } %>
|
||||||
|
<span class="mt-2 whitespace-nowrap truncate text-xs font-normal w-full absolute -bottom-5"><%= t('protocols.steps.timestamp_iso_html', date: step.created_at.iso8601, user: step.user.full_name) %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="elements-actions-container">
|
<div class="elements-actions-container">
|
||||||
|
@ -41,10 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="collapse in" id="stepBody<%= step.id %>">
|
<div class="collapse in" id="stepBody<%= step.id %>">
|
||||||
<div class="step-elements !pl-[4.5rem]">
|
<div class="step-elements">
|
||||||
<div class="step-timestamp !m-0">
|
|
||||||
<%= t('protocols.steps.timestamp_iso_html', date: step.created_at.iso8601, user: step.user.full_name) %>
|
|
||||||
</div>
|
|
||||||
<% step.step_orderable_elements.sort_by(&:position).each do |element| %>
|
<% step.step_orderable_elements.sort_by(&:position).each do |element| %>
|
||||||
<% if element.orderable_type == 'StepText' %>
|
<% if element.orderable_type == 'StepText' %>
|
||||||
<%= render partial: "shareable_links/my_modules/step_elements/text", locals: { element: element.orderable } %>
|
<%= render partial: "shareable_links/my_modules/step_elements/text", locals: { element: element.orderable } %>
|
||||||
|
@ -59,4 +56,5 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sci-divider my-6"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<hr>
|
|
||||||
<div class="col-xs-12 comments-title">
|
|
||||||
<h4>
|
|
||||||
<%=t('my_modules.results.comments_tab') %>
|
|
||||||
(<span><%= comments.count %></span>)
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="comments-container">
|
|
||||||
<div class="content-comments inline_scroll_block">
|
|
||||||
<div class="comments-list">
|
|
||||||
<%= render partial: "shareable_links/my_modules/results/comments_list", locals: { comments: comments } %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<% comments.order(created_at: :asc).each do |comment| %>
|
|
||||||
<div class="comment-container">
|
|
||||||
<div class="avatar-placehodler">
|
|
||||||
<span class='global-avatar-container'>
|
|
||||||
<%= image_tag user_avatar_absolute_url(comment.user, :icon_small, true), class: 'user-avatar' %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="content-placeholder">
|
|
||||||
<div class="comment-name"><%= comment.user.full_name %></div>
|
|
||||||
<div class="comment-right !w-fit">
|
|
||||||
<div class="comment-datetime !w-fit"><%= l(comment.created_at, format: :full) %></div>
|
|
||||||
</div>
|
|
||||||
<div class="comment-message">
|
|
||||||
<div class="view-mode"><%= smart_annotation_text(comment.message) %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
|
@ -1,37 +1,40 @@
|
||||||
<div class="result">
|
<div class="bg-white p-4 mb-4 rounded">
|
||||||
<div class="panel panel-default">
|
<div class="result-header flex justify-between">
|
||||||
<div class="panel-heading">
|
<div class="result-head-left flex items-start flex-grow gap-4">
|
||||||
<div class="panel-options pull-right">
|
<a class="result-collapse-link hover:no-underline focus:no-underline text-sn-black" href="#result-panel-<%= result.id %>" data-toggle="collapse">
|
||||||
</div>
|
|
||||||
<a class="result-panel-collapse-link" href="#result-panel-<%= result.id %>" data-toggle="collapse">
|
|
||||||
<span class="sn-icon sn-icon-right"></span>
|
<span class="sn-icon sn-icon-right"></span>
|
||||||
<strong><%= result.name %></strong> |
|
|
||||||
<span><%= t('my_modules.results.published_on_iso_html', timestamp: result.created_at.iso8601, user: h(result.user.full_name)) %></span>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<div class="w-full relative flex flex-grow font-bold text-base">
|
||||||
<div class="panel-collapse collapse in" id="result-panel-<%= result.id %>" role="tabpanel">
|
<strong><%= result.name %></strong>
|
||||||
<div class="panel-body">
|
<span class="mt-2 whitespace-nowrap truncate text-xs font-normal absolute bottom-[-1rem] w-full"><%= t('my_modules.results.published_on_iso_html', timestamp: result.created_at.iso8601, user: h(result.user.full_name)) %></span>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col-xs-12">
|
<div class="elements-actions-container">
|
||||||
<% result.result_orderable_elements.sort_by(&:position).each do |element| %>
|
<a href="#"
|
||||||
<% if element.orderable_type == 'ResultText' %>
|
class="shareable-link-open-comments-sidebar btn icon-btn btn-light"
|
||||||
<%= render partial: "shareable_links/my_modules/step_elements/text", locals: { element: element.orderable } %>
|
data-turbolinks="false"
|
||||||
<% elsif element.orderable_type == 'ResultTable'%>
|
data-object-type="Result"
|
||||||
<%= render partial: "shareable_links/my_modules/step_elements/table", locals: { element: element.orderable.table } %>
|
data-object-id="<%= result.id %>"
|
||||||
<% end %>
|
data-object-target="#Result<%= result.id %>">
|
||||||
<% end %>
|
<i class="sn-icon sn-icon-comments"></i>
|
||||||
<% if result.result_assets.present? %>
|
<span class="comments-counter"
|
||||||
<%= render partial: "shareable_links/my_modules/attachments", locals: { attachments: result.assets, step: result } %>
|
id="comment-count-<%= result.id %>"
|
||||||
<% end %>
|
>
|
||||||
</div>
|
<%= result.comments.count %>
|
||||||
</div>
|
</span>
|
||||||
<div class="row">
|
</a>
|
||||||
<div class="result-comment"
|
|
||||||
id="result-comments-<%= result.id %>">
|
|
||||||
<%= render partial: "shareable_links/my_modules/results/comments", locals: { comments: result.comments } %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-collapse collapse in pl-10" id="result-panel-<%= result.id %>" role="tabpanel">
|
||||||
|
<% result.result_orderable_elements.sort_by(&:position).each do |element| %>
|
||||||
|
<% if element.orderable_type == 'ResultText' %>
|
||||||
|
<%= render partial: "shareable_links/my_modules/step_elements/text", locals: { element: element.orderable } %>
|
||||||
|
<% elsif element.orderable_type == 'ResultTable'%>
|
||||||
|
<%= render partial: "shareable_links/my_modules/step_elements/table", locals: { element: element.orderable.table } %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if result.result_assets.present? %>
|
||||||
|
<%= render partial: "shareable_links/my_modules/attachments", locals: { attachments: result.assets, step: result } %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
<i class="sn-icon sn-icon-more-hori"></i>
|
<i class="sn-icon sn-icon-more-hori"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownAssetContextMenu">
|
<ul class="dropdown-menu dropdown-menu-right rounded !p-2.5 sn-shadow-menu-sm" aria-labelledby="dropdownAssetContextMenu">
|
||||||
<li>
|
<li>
|
||||||
<%= link_to shared_protocol_asset_download_path(@shareable_link.uuid, asset), data: { turbolinks: false } do %>
|
<%= link_to shared_protocol_asset_download_path(@shareable_link.uuid, asset), data: { turbolinks: false }, class: "!px-3 !py-2.5 hover:!bg-sn-super-light-blue !text-sn-blue" do %>
|
||||||
<span class="sn-icon sn-icon-export"></span>
|
|
||||||
<%= t('Download') %>
|
<%= t('Download') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
<div class="attachment-container asset">
|
<div class="attachment-container asset group">
|
||||||
<%= link_to '#',
|
<%= link_to '#',
|
||||||
class: "shareable-file-preview-link",
|
class: "file-preview-link group-hover:hidden",
|
||||||
id: "modal_link#{asset.id}",
|
id: "modal_link#{asset.id}",
|
||||||
data: {
|
data: {
|
||||||
no_turbolink: true,
|
no_turbolink: true,
|
||||||
id: asset.id
|
id: asset.id
|
||||||
} do %>
|
} do %>
|
||||||
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
|
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
|
||||||
<% if asset.previewable? && asset.medium_preview&.image&.attached? %>
|
<% if asset.previewable? && asset.medium_preview&.image&.attached? %>
|
||||||
<%= image_tag asset.medium_preview.url(expires_in: Constants::URL_SHORT_EXPIRE_TIME.minutes),
|
<%= image_tag asset.medium_preview.url(expires_in: Constants::URL_SHORT_EXPIRE_TIME.minutes),
|
||||||
class: 'asset-preview-image',
|
class: 'rounded-sm' %>
|
||||||
style: 'opacity: 0' %>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
<div class="w-[186px] h-[186px] bg-sn-super-light-grey rounded-sm"></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="attachment-label"
|
<div class="attachment-label"
|
||||||
|
@ -21,12 +20,28 @@
|
||||||
title="<%= asset.render_file_name %>">
|
title="<%= asset.render_file_name %>">
|
||||||
<%= asset.render_file_name %>
|
<%= asset.render_file_name %>
|
||||||
</div>
|
</div>
|
||||||
<div class="attachment-metadata">
|
<% end %>
|
||||||
<%= t('assets.placeholder.modified_label') %> <span class="iso-formatted-date"><%= asset.updated_at.iso8601 if asset.updated_at %></span><br>
|
<div class="tw-hidden group-hover:block hovered-thumbnail h-full">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="shareable-file-preview-link"
|
||||||
|
id="modal_link<%= asset.id %>"
|
||||||
|
data-no-turbolink="true"
|
||||||
|
data-id="<%= asset.id %>"
|
||||||
|
>
|
||||||
|
<%= asset.render_file_name %>
|
||||||
|
</a>
|
||||||
|
<div class="absolute bottom-16 text-sn-grey">
|
||||||
<%= number_to_human_size(asset.file_size) %>
|
<%= number_to_human_size(asset.file_size) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<div class="absolute bottom-4 w-[184px] grid grid-cols-[repeat(4,_2.5rem)] justify-between">
|
||||||
<% if defined?(show_context) && show_context %>
|
<%= link_to shared_protocol_asset_download_path(@shareable_link.uuid, asset), class: "btn btn-light icon-btn thumbnail-action-btn",data: { turbolinks: false } do %>
|
||||||
<%= render partial: "shareable_links/my_modules/step_attachments/context_menu", locals: { asset: asset } %>
|
<span class="sn-icon sn-icon-export"></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% if defined?(show_context) && show_context %>
|
||||||
|
<%= render partial: "shareable_links/my_modules/step_attachments/context_menu", locals: { asset: asset } %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="step-checklist-container" >
|
<div class="content__checklist-container" >
|
||||||
<div class="step-element-header no-hover">
|
<div class="sci-divider my-6"></div>
|
||||||
<div class="step-element-grip-placeholder"></div>
|
<div class="checklist-header flex rounded mb-1 items-center relative w-full group/checklist-header">
|
||||||
<div class="step-element-name font-bold">
|
<div class="grow-1 text-ellipsis whitespace-nowrap grow my-1 font-bold">
|
||||||
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: element.name, smart_annotation_enabled: true } %>
|
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: element.name, smart_annotation_enabled: true } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<div class="step-checklist-item step-element--locked">
|
<div class="content__checklist-item step-element--locked my-2">
|
||||||
<div class="step-element-header locked">
|
<div class="checklist-item-header flex rounded pl-10 ml-[-2.325rem] items-center relative w-full group/checklist-item-header locked">
|
||||||
<div class="step-element-grip-placeholder"></div>
|
<div class="absolute cursor-grab justify-center left-0 px-2 tw-hidden text-sn-grey step-element-grip-placeholder"></div>
|
||||||
<div class="step-element-name <%= 'done' if checklist_item.checked %>">
|
<div class="flex items-start gap-2 grow <%= 'done' if checklist_item.checked %>">
|
||||||
<div class="sci-checkbox-container disabled">
|
<div class="sci-checkbox-container disabled my-0.5">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
class="sci-checkbox"
|
class="sci-checkbox"
|
||||||
<%= 'checked' if checklist_item.checked %> />
|
<%= 'checked' if checklist_item.checked %> />
|
||||||
<span class="sci-checkbox-label" ></span>
|
<span class="sci-checkbox-label" ></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-checklist-text !mt-1 step-element--locked">
|
<div class="pr-24 relative flex items-start max-w-[90ch] step-element--locked">
|
||||||
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: checklist_item.text, smart_annotation_enabled: true } %>
|
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: checklist_item.text, smart_annotation_enabled: true } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<div class="step-table-container">
|
<div class="content__table-container">
|
||||||
<div class="step-element-header step-element--locked">
|
<div class="sci-divider my-6"></div>
|
||||||
|
<div class="table-header h-9 flex rounded mb-3 items-center relative w-full group/table-header step-element--locked">
|
||||||
<% if element.name.present? %>
|
<% if element.name.present? %>
|
||||||
<div class="step-element-name font-bold">
|
<div class="grow-1 text-ellipsis whitespace-nowrap grow my-1 font-bold">
|
||||||
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: element.name, smart_annotation_enabled: false } %>
|
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: element.name, smart_annotation_enabled: false } %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="step-table view locked" tabindex="0">
|
<div class="table-body group/table-body relative border-solid border-transparent view locked" tabindex="0">
|
||||||
<input type="hidden" class="hot-table-contents" value="<%= element.contents_utf_8 %>" />
|
<input type="hidden" class="hot-table-contents" value="<%= element.contents_utf_8 %>" />
|
||||||
<input type="hidden" class="hot-table-metadata" value="<%= element.metadata ? element.metadata.to_json : nil %>" />
|
<input type="hidden" class="hot-table-metadata" value="<%= element.metadata ? element.metadata.to_json : nil %>" />
|
||||||
<div class="hot-table-container"></div>
|
<div class="hot-table-container"></div>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<div class="step-text-container step-element--locked locked" tabindex="0">
|
<div class="content__text-container locked" tabindex="0">
|
||||||
|
<div class="sci-divider my-6"></div>
|
||||||
<% if element.name.present? %>
|
<% if element.name.present? %>
|
||||||
<div class="step-element-header step-element--locked mt-4">
|
<div class="text-header h-9 flex rounded mb-1 items-center relative w-full group/text-header">
|
||||||
<div class="step-element-name font-bold">
|
<div class="grow-1 text-ellipsis whitespace-nowrap grow my-1 font-bold">
|
||||||
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: element.name, smart_annotation_enabled: false } %>
|
<%= render partial: "shareable_links/my_modules/inline_view", locals: { text: element.name, smart_annotation_enabled: false } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if element.text.present? %>
|
<% if element.text.present? %>
|
||||||
<div class="view-text-element">
|
<div class="flex rounded min-h-[2.25rem] mb-4 relative group/text_container content__text-body max-w-[90ch]">
|
||||||
<%= smart_annotation_text(element.shareable_tinymce_render(:text)) %>
|
<%= smart_annotation_text(element.shareable_tinymce_render(:text)) %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
7
app/views/shared/_repository_row_sidebar.html.erb
Normal file
7
app/views/shared/_repository_row_sidebar.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<div
|
||||||
|
id="repositoryItemSidebar"
|
||||||
|
data-behaviour="vue"
|
||||||
|
class="fixed top-0 right-0 h-full z-[2000]"
|
||||||
|
>
|
||||||
|
<repository-item-sidebar />
|
||||||
|
</div>
|
|
@ -60,6 +60,8 @@ module Scinote
|
||||||
|
|
||||||
config.x.connected_devices_enabled = ENV['CONNECTED_DEVICES_ENABLED'] == 'true'
|
config.x.connected_devices_enabled = ENV['CONNECTED_DEVICES_ENABLED'] == 'true'
|
||||||
|
|
||||||
|
config.x.custom_sanitizer_config = nil
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
config.log_formatter = proc do |severity, datetime, progname, msg|
|
config.log_formatter = proc do |severity, datetime, progname, msg|
|
||||||
"[#{datetime}] #{severity}: #{msg}\n"
|
"[#{datetime}] #{severity}: #{msg}\n"
|
||||||
|
|
|
@ -326,7 +326,7 @@ class Constants
|
||||||
config = Sanitize::Config::RELAXED.deep_dup
|
config = Sanitize::Config::RELAXED.deep_dup
|
||||||
config[:attributes][:all] << 'id'
|
config[:attributes][:all] << 'id'
|
||||||
config[:attributes][:all] << 'contenteditable'
|
config[:attributes][:all] << 'contenteditable'
|
||||||
config[:attributes][:all] << :data
|
config[:attributes]['img'] << 'data-mce-token'
|
||||||
config[:protocols]['img']['src'] << 'data'
|
config[:protocols]['img']['src'] << 'data'
|
||||||
INPUT_SANITIZE_CONFIG = Sanitize::Config.freeze_config(config)
|
INPUT_SANITIZE_CONFIG = Sanitize::Config.freeze_config(config)
|
||||||
|
|
||||||
|
|
|
@ -510,7 +510,7 @@ class Extends
|
||||||
protocol_repository: [80, 103, 89, 87, 79, 90, 91, 88, 85, 86, 84, 81, 82,
|
protocol_repository: [80, 103, 89, 87, 79, 90, 91, 88, 85, 86, 84, 81, 82,
|
||||||
83, 101, 112, 123, 125, 117, 119, 129, 131, 170, 173, 179, 187, 186,
|
83, 101, 112, 123, 125, 117, 119, 129, 131, 170, 173, 179, 187, 186,
|
||||||
190, 191, *204..215, 220, 221, 223, 227, 228, 229, *230..235,
|
190, 191, *204..215, 220, 221, 223, 227, 228, 229, *230..235,
|
||||||
*237..240, *253..256, *259..283],
|
*237..240, *253..256, *279..283],
|
||||||
team: [92, 94, 93, 97, 104, 244, 245],
|
team: [92, 94, 93, 97, 104, 244, 245],
|
||||||
label_templates: [*216..219]
|
label_templates: [*216..219]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# This code will include the listed helpers in all the assets
|
|
||||||
Rails.application.config.assets.configure do |env|
|
|
||||||
env.context_class.class_eval do
|
|
||||||
# This is required for repository_datatable.js.erb
|
|
||||||
include RepositoryDatatableHelper
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -2152,7 +2152,7 @@ en:
|
||||||
disabled_placeholder: "Select Experiment to enable Task"
|
disabled_placeholder: "Select Experiment to enable Task"
|
||||||
no_options_placeholder: "No tasks available to assign items"
|
no_options_placeholder: "No tasks available to assign items"
|
||||||
assign:
|
assign:
|
||||||
text: "Assign to this task"
|
text: "Assign to task"
|
||||||
flash_all_assignments_success: "Successfully assigned %{count} item(s) to the task."
|
flash_all_assignments_success: "Successfully assigned %{count} item(s) to the task."
|
||||||
flash_some_assignments_success: "Successfully assigned %{assigned_count} item(s) to the task. %{skipped_count} item(s) were already assigned to the task."
|
flash_some_assignments_success: "Successfully assigned %{assigned_count} item(s) to the task. %{skipped_count} item(s) were already assigned to the task."
|
||||||
flash_assignments_failure: "Failed to assign item(s) to task."
|
flash_assignments_failure: "Failed to assign item(s) to task."
|
||||||
|
@ -2219,6 +2219,71 @@ en:
|
||||||
invalid_arguments: "Can't find %{key}"
|
invalid_arguments: "Can't find %{key}"
|
||||||
my_module_assigned_snapshot_service:
|
my_module_assigned_snapshot_service:
|
||||||
invalid_arguments: "Can't find %{key}"
|
invalid_arguments: "Can't find %{key}"
|
||||||
|
item_card:
|
||||||
|
section:
|
||||||
|
information: "Information"
|
||||||
|
assigned: "Assigned (%{count})"
|
||||||
|
qr: "QR"
|
||||||
|
print_label: "Print label"
|
||||||
|
assigned:
|
||||||
|
empty: "This item is not assigned to any task."
|
||||||
|
assign: "Assign to task"
|
||||||
|
private:
|
||||||
|
one: "Assigned to %{count} private task"
|
||||||
|
other: "Assigned to %{count} private tasks"
|
||||||
|
labels:
|
||||||
|
team: "Team:"
|
||||||
|
project: "Project:"
|
||||||
|
experiment: "Experiment:"
|
||||||
|
my_module: "Task:"
|
||||||
|
default_columns:
|
||||||
|
repository_name: "Inventory"
|
||||||
|
id: "Item ID"
|
||||||
|
added_on: "Added on"
|
||||||
|
added_at: "Added at"
|
||||||
|
added_by: "Added by"
|
||||||
|
reminders:
|
||||||
|
stock_low: "Only %{stock_formated} left."
|
||||||
|
stock_empty: "No stock left"
|
||||||
|
date_expiration: "This item is expiring in %{date_expiration}."
|
||||||
|
item_expired: "This item has expired."
|
||||||
|
day:
|
||||||
|
one: "day"
|
||||||
|
other: "days"
|
||||||
|
stock_export: "Export"
|
||||||
|
custom_columns_label: "Custom columns"
|
||||||
|
no_custom_columns_label: "This item has no custom columns"
|
||||||
|
repository_time_range_value:
|
||||||
|
no_time_range: 'No time range'
|
||||||
|
repository_text_value:
|
||||||
|
no_text: 'No text'
|
||||||
|
repository_stock_value:
|
||||||
|
no_stock: 'No stock'
|
||||||
|
repository_status_value:
|
||||||
|
no_status: 'No selection'
|
||||||
|
repository_number_value:
|
||||||
|
no_number: 'No number'
|
||||||
|
repository_list_value:
|
||||||
|
no_list: 'No selection'
|
||||||
|
repository_date_value:
|
||||||
|
no_date: 'No date'
|
||||||
|
repository_date_time_value:
|
||||||
|
no_date_time: 'No date and time'
|
||||||
|
repository_date_time_range_value:
|
||||||
|
no_date_time_range: 'No date and time range'
|
||||||
|
repository_date_range_value:
|
||||||
|
no_date_range: 'No date range'
|
||||||
|
repository_checklist_value:
|
||||||
|
no_checklist: 'No selection'
|
||||||
|
repository_asset_value:
|
||||||
|
no_asset: 'No file'
|
||||||
|
repository_time_value:
|
||||||
|
no_time: 'No time'
|
||||||
|
highlight_component:
|
||||||
|
information_label: 'Information'
|
||||||
|
custom_columns_label: 'Custom columns'
|
||||||
|
assigned_label: 'Assigned'
|
||||||
|
QR_label: 'QR'
|
||||||
repository_stock_values:
|
repository_stock_values:
|
||||||
manage_modal:
|
manage_modal:
|
||||||
title: "Stock %{item}"
|
title: "Stock %{item}"
|
||||||
|
@ -2412,7 +2477,7 @@ en:
|
||||||
no_tasks: "This item in not assigned to any task."
|
no_tasks: "This item in not assigned to any task."
|
||||||
amount: "Amount: %{value}"
|
amount: "Amount: %{value}"
|
||||||
unit: "Unit: %{unit}"
|
unit: "Unit: %{unit}"
|
||||||
assign_to_task: "Assign to this task"
|
assign_to_task: "Assign to task"
|
||||||
assign_to_task_error:
|
assign_to_task_error:
|
||||||
no_access: "You can only view this task"
|
no_access: "You can only view this task"
|
||||||
already_assigned: "This item is already assigned to this task"
|
already_assigned: "This item is already assigned to this task"
|
||||||
|
@ -3436,7 +3501,7 @@ en:
|
||||||
description: "Once you create items in the inventory, they will appear here."
|
description: "Once you create items in the inventory, they will appear here."
|
||||||
buttons:
|
buttons:
|
||||||
insert: "Insert"
|
insert: "Insert"
|
||||||
assign: "Assign to this task"
|
assign: "Assign to task"
|
||||||
projects: PROJECTS
|
projects: PROJECTS
|
||||||
experiments: EXPERIMENTS
|
experiments: EXPERIMENTS
|
||||||
tasks: TASKS
|
tasks: TASKS
|
||||||
|
|
|
@ -38,6 +38,7 @@ const entryList = {
|
||||||
vue_share_task_container: './app/javascript/packs/vue/share_task_container.js',
|
vue_share_task_container: './app/javascript/packs/vue/share_task_container.js',
|
||||||
vue_navigation_top_menu: './app/javascript/packs/vue/navigation/top_menu.js',
|
vue_navigation_top_menu: './app/javascript/packs/vue/navigation/top_menu.js',
|
||||||
vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js',
|
vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js',
|
||||||
|
vue_components_repository_item_sidebar: './app/javascript/packs/vue/repository_item_sidebar.js',
|
||||||
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',
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
"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",
|
||||||
|
"vue2-scrollspy": "^2.3.1",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
"webpack": "^5.64.4",
|
"webpack": "^5.64.4",
|
||||||
"webpack-cli": "^4.10.0",
|
"webpack-cli": "^4.10.0",
|
||||||
|
|
|
@ -58,7 +58,7 @@ describe AccessPermissions::ExperimentsController, type: :controller do
|
||||||
{
|
{
|
||||||
id: experiment.id,
|
id: experiment.id,
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
experiment_member: {
|
user_assignment: {
|
||||||
user_role_id: technician_role.id,
|
user_role_id: technician_role.id,
|
||||||
user_id: viewer_user.id
|
user_id: viewer_user.id
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ describe AccessPermissions::MyModulesController, type: :controller do
|
||||||
id: my_module.id,
|
id: my_module.id,
|
||||||
experiment_id: experiment.id,
|
experiment_id: experiment.id,
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
my_module_member: {
|
user_assignment: {
|
||||||
user_role_id: technician_role.id,
|
user_role_id: technician_role.id,
|
||||||
user_id: viewer_user.id
|
user_id: viewer_user.id
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ describe AccessPermissions::ProjectsController, type: :controller do
|
||||||
let(:valid_params) do
|
let(:valid_params) do
|
||||||
{
|
{
|
||||||
id: project.id,
|
id: project.id,
|
||||||
project_member: {
|
user_assignment: {
|
||||||
user_role_id: technician_role.id,
|
user_role_id: technician_role.id,
|
||||||
user_id: normal_user.id
|
user_id: normal_user.id
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ describe AccessPermissions::ProjectsController, type: :controller do
|
||||||
let(:valid_params) do
|
let(:valid_params) do
|
||||||
{
|
{
|
||||||
id: project.id,
|
id: project.id,
|
||||||
access_permissions_new_user_project_form: {
|
access_permissions_new_user_form: {
|
||||||
resource_members: {
|
resource_members: {
|
||||||
0 => {
|
0 => {
|
||||||
assign: '1',
|
assign: '1',
|
||||||
|
@ -193,8 +193,7 @@ describe AccessPermissions::ProjectsController, type: :controller do
|
||||||
it 'removes the user project and user assigment record' do
|
it 'removes the user project and user assigment record' do
|
||||||
expect {
|
expect {
|
||||||
delete :destroy, params: valid_params, format: :json
|
delete :destroy, params: valid_params, format: :json
|
||||||
}.to change(UserProject, :count).by(-1).and \
|
}.to change(UserAssignment, :count).by(-1)
|
||||||
change(UserAssignment, :count).by(-1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders 403 if user does not have manage permissions on project' do
|
it 'renders 403 if user does not have manage permissions on project' do
|
||||||
|
|
|
@ -68,8 +68,10 @@ describe ProjectsController, type: :controller do
|
||||||
let(:action) { put :update, params: params }
|
let(:action) { put :update, params: params }
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ id: projects.first.id,
|
{ id: projects.first.id,
|
||||||
project: { name: projects.first.name, team_id: projects.first.team.id,
|
project: { name: projects.first.name,
|
||||||
visibility: projects.first.visibility } }
|
team_id: projects.first.team.id,
|
||||||
|
visibility: projects.first.visibility,
|
||||||
|
default_public_user_role_id: projects.first.default_public_user_role.id } }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns redirect response' do
|
it 'returns redirect response' do
|
||||||
|
@ -78,10 +80,10 @@ describe ProjectsController, type: :controller do
|
||||||
expect(response.media_type).to eq 'text/html'
|
expect(response.media_type).to eq 'text/html'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calls create activity service (change_project_visibility)' do
|
it 'calls create activity service (project_grant_access_to_all_team_members)' do
|
||||||
params[:project][:visibility] = 'visible'
|
params[:project][:visibility] = 'visible'
|
||||||
expect(Activities::CreateActivityService).to receive(:call)
|
expect(Activities::CreateActivityService).to receive(:call)
|
||||||
.with(hash_including(activity_type: :change_project_visibility))
|
.with(hash_including(activity_type: :project_grant_access_to_all_team_members))
|
||||||
action
|
action
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ require 'rails_helper'
|
||||||
describe ProtocolsController, type: :controller do
|
describe ProtocolsController, type: :controller do
|
||||||
login_user
|
login_user
|
||||||
|
|
||||||
include_context 'reference_project_structure'
|
include_context 'reference_project_structure', { team_role: :owner }
|
||||||
|
|
||||||
describe 'POST create' do
|
describe 'POST create' do
|
||||||
let(:action) { post :create, params: params, format: :json }
|
let(:action) { post :create, params: params, format: :json }
|
||||||
|
@ -78,10 +78,12 @@ describe ProtocolsController, type: :controller do
|
||||||
protocol: {
|
protocol: {
|
||||||
name: 'my_test_protocol',
|
name: 'my_test_protocol',
|
||||||
description: 'description',
|
description: 'description',
|
||||||
authors: 'authors'
|
authors: 'authors',
|
||||||
|
elnVersion: '1.0',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:action) { post :import, params: params, format: :json }
|
let(:action) { post :import, params: params, format: :json }
|
||||||
|
|
||||||
it 'calls create activity for importing protocols' do
|
it 'calls create activity for importing protocols' do
|
||||||
|
@ -98,9 +100,9 @@ describe ProtocolsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'PUT description' do
|
describe 'PATCH description' do
|
||||||
let(:protocol) do
|
let(:protocol) do
|
||||||
create :protocol, :in_public_repository, team: team, added_by: user
|
create :protocol, :in_repository_draft, team: team, added_by: user
|
||||||
end
|
end
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
|
@ -110,8 +112,7 @@ describe ProtocolsController, type: :controller do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:action) { put :update_description, params: params, format: :json }
|
let(:action) { patch :update_description, params: params, format: :json }
|
||||||
|
|
||||||
it 'calls create activity for updating description' do
|
it 'calls create activity for updating description' do
|
||||||
expect(Activities::CreateActivityService)
|
expect(Activities::CreateActivityService)
|
||||||
.to(receive(:call)
|
.to(receive(:call)
|
||||||
|
@ -126,14 +127,14 @@ describe ProtocolsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST update_keywords' do
|
describe 'PATCH update_keywords' do
|
||||||
let(:protocol) do
|
let(:protocol) do
|
||||||
create :protocol, :in_public_repository, team: team, added_by: user
|
create :protocol, :in_repository_draft, team: team, added_by: user
|
||||||
end
|
end
|
||||||
let(:action) { put :update_keywords, params: params, format: :json }
|
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ id: protocol.id, keywords: ['keyword-1', 'keyword-2'] }
|
{ id: protocol.id, keywords: ['keyword-1', 'keyword-2'] }
|
||||||
end
|
end
|
||||||
|
let(:action) { patch :update_keywords, params: params, format: :json }
|
||||||
|
|
||||||
it 'calls create activity for updating keywords' do
|
it 'calls create activity for updating keywords' do
|
||||||
expect(Activities::CreateActivityService)
|
expect(Activities::CreateActivityService)
|
||||||
|
@ -151,7 +152,7 @@ describe ProtocolsController, type: :controller do
|
||||||
|
|
||||||
context 'update protocol' do
|
context 'update protocol' do
|
||||||
let(:protocol_repo) do
|
let(:protocol_repo) do
|
||||||
create :protocol, :in_public_repository, name: ' test protocol',
|
create :protocol, :in_repository_published_original, name: ' test protocol',
|
||||||
team: team,
|
team: team,
|
||||||
added_by: user
|
added_by: user
|
||||||
end
|
end
|
||||||
|
@ -165,7 +166,7 @@ describe ProtocolsController, type: :controller do
|
||||||
let(:params) { { id: protocol.id } }
|
let(:params) { { id: protocol.id } }
|
||||||
|
|
||||||
describe 'POST revert' do
|
describe 'POST revert' do
|
||||||
let(:action) { put :revert, params: params, format: :json }
|
let(:action) { post :revert, params: params, format: :json }
|
||||||
|
|
||||||
it 'calls create activity for updating protocol in task from repository' do
|
it 'calls create activity for updating protocol in task from repository' do
|
||||||
expect(Activities::CreateActivityService)
|
expect(Activities::CreateActivityService)
|
||||||
|
@ -184,13 +185,13 @@ describe ProtocolsController, type: :controller do
|
||||||
|
|
||||||
describe 'POST load_from_repository' do
|
describe 'POST load_from_repository' do
|
||||||
let(:protocol_source) do
|
let(:protocol_source) do
|
||||||
create :protocol, :in_public_repository, team: team, added_by: user
|
create :protocol, :in_repository_published_original, team: team, added_by: user
|
||||||
end
|
end
|
||||||
let(:protocol) { create :protocol, team: team, added_by: user, my_module: my_module }
|
let(:protocol) { create :protocol, team: team, added_by: user, my_module: my_module }
|
||||||
let(:action) { put :load_from_repository, params: params, format: :json }
|
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ source_id: protocol_source.id, id: protocol.id }
|
{ source_id: protocol_source.id, id: protocol.id }
|
||||||
end
|
end
|
||||||
|
let(:action) { post :load_from_repository, params: params, format: :json }
|
||||||
|
|
||||||
it 'calls create activity for loading protocol to task from repository' do
|
it 'calls create activity for loading protocol to task from repository' do
|
||||||
expect(Activities::CreateActivityService)
|
expect(Activities::CreateActivityService)
|
||||||
|
@ -210,13 +211,14 @@ describe ProtocolsController, type: :controller do
|
||||||
let(:protocol) do
|
let(:protocol) do
|
||||||
create :protocol, my_module: my_module, team: team, added_by: user
|
create :protocol, my_module: my_module, team: team, added_by: user
|
||||||
end
|
end
|
||||||
let(:action) { put :load_from_file, params: params, format: :json }
|
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ id: protocol.id,
|
{ id: protocol.id,
|
||||||
protocol: { name: 'my_test_protocol',
|
protocol: { name: 'my_test_protocol',
|
||||||
description: 'description',
|
description: 'description',
|
||||||
authors: 'authors' } }
|
authors: 'authors',
|
||||||
|
elnVersion: '1.1'} }
|
||||||
end
|
end
|
||||||
|
let(:action) { post :load_from_file, params: params, format: :json }
|
||||||
|
|
||||||
it 'calls create activity for loading protocol to task from file' do
|
it 'calls create activity for loading protocol to task from file' do
|
||||||
expect(Activities::CreateActivityService)
|
expect(Activities::CreateActivityService)
|
||||||
|
|
|
@ -16,7 +16,8 @@ describe ResultTablesController, type: :controller do
|
||||||
result:
|
result:
|
||||||
{ name: 'result name created',
|
{ name: 'result name created',
|
||||||
table_attributes:
|
table_attributes:
|
||||||
{ contents: '{\"data\":[[\"a\",\"b\",\"1\",null,null]]}' } } }
|
{ contents: '{\"data\":[[\"a\",\"b\",\"1\",null,null]]}',
|
||||||
|
metadata: "{\"cells\":[{\"row\":\"0\",\"col\":\"0\",\"className\":\"\",\"calculated\":\"\"}]}" } } }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calls create activity service' do
|
it 'calls create activity service' do
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe StepsController, type: :controller do
|
||||||
}
|
}
|
||||||
|
|
||||||
let(:protocol_repo) do
|
let(:protocol_repo) do
|
||||||
create :protocol, :in_public_repository, team: team, added_by: user
|
create :protocol, :in_repository_draft, team: team, added_by: user
|
||||||
end
|
end
|
||||||
let(:step_repo) { create :step, protocol: protocol_repo }
|
let(:step_repo) { create :step, protocol: protocol_repo }
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,11 @@ describe TeamRepositoriesController, type: :controller do
|
||||||
|
|
||||||
describe 'DELETE destroy' do
|
describe 'DELETE destroy' do
|
||||||
let(:second_team) { create :team, created_by: user }
|
let(:second_team) { create :team, created_by: user }
|
||||||
let(:team_repository) { create :team_repository, :read, team: second_team, repository: repository }
|
let(:team_repository) { create :team_shared_object, :read, team: second_team, shared_object: repository }
|
||||||
|
|
||||||
context 'when resource can be deleted' do
|
context 'when resource can be deleted' do
|
||||||
let(:action) do
|
let(:action) do
|
||||||
delete :destroy, params: { repository_id: repository.id, team_id: team.id, id: team_repository.id }
|
delete :destroy, params: { team_id: team.id, id: team_repository.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders 204' do
|
it 'renders 204' do
|
||||||
|
|
|
@ -4,5 +4,15 @@ FactoryBot.define do
|
||||||
factory :settings do
|
factory :settings do
|
||||||
type { Faker::Lorem.sentence.split(' ').sample }
|
type { Faker::Lorem.sentence.split(' ').sample }
|
||||||
values { { key_of_data: Faker::Lorem.sentence.split(' ').sample } }
|
values { { key_of_data: Faker::Lorem.sentence.split(' ').sample } }
|
||||||
|
|
||||||
|
trait :with_load_values_from_env_defined do
|
||||||
|
after(:build) do |application_settings|
|
||||||
|
application_settings.define_singleton_method(:load_values_from_env) do
|
||||||
|
{
|
||||||
|
some_key: Faker::Lorem.sentence.split(' ').sample
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :team_shared_object do
|
factory :team_shared_object do
|
||||||
repository
|
|
||||||
trait :read do
|
trait :read do
|
||||||
permission_level { :shared_read }
|
permission_level { :shared_read }
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :user_assignment do
|
factory :user_assignment do
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,9 +2,7 @@ FactoryBot.define do
|
||||||
factory :user_role do
|
factory :user_role do
|
||||||
factory :owner_role do
|
factory :owner_role do
|
||||||
name { I18n.t('user_roles.predefined.owner') }
|
name { I18n.t('user_roles.predefined.owner') }
|
||||||
permissions { ProjectPermissions.constants.map { |const| ProjectPermissions.const_get(const) } +
|
permissions { PredefinedRoles::OWNER_PERMISSIONS }
|
||||||
ExperimentPermissions.constants.map { |const| ExperimentPermissions.const_get(const) } +
|
|
||||||
MyModulePermissions.constants.map { |const| MyModulePermissions.const_get(const) } }
|
|
||||||
predefined { true }
|
predefined { true }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue