Merge branch 'develop' into features/new-datatable

This commit is contained in:
Anton 2024-01-11 15:14:36 +01:00
commit de72f60cc1
200 changed files with 8271 additions and 6184 deletions

View file

@ -25,13 +25,13 @@
"max-len": [
"error",
{
"code": 120
"code": 180
}
],
"vue/max-len": [
"error",
{
"code": 120,
"code": 180,
"template": 240,
"tabWidth": 2
}

View file

@ -442,7 +442,7 @@ GEM
net-smtp (0.3.3)
net-protocol
newrelic_rpm (9.2.2)
nio4r (2.5.9)
nio4r (2.7.0)
nokogiri (1.14.5)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
@ -505,7 +505,7 @@ GEM
pry (>= 0.10.4)
psych (3.3.4)
public_suffix (5.0.1)
puma (6.3.1)
puma (6.4.2)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.7.1)

View file

@ -1 +1 @@
1.29.5.1
1.29.6

View file

@ -0,0 +1,4 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 16.5H13V11H12V16.5ZM12.5034 21C11.2588 21 10.0887 20.7638 8.9931 20.2915C7.89748 19.8192 6.94444 19.1782 6.13397 18.3685C5.32352 17.5588 4.68192 16.6066 4.20915 15.512C3.73638 14.4174 3.5 13.2479 3.5 12.0033C3.5 10.7588 3.73616 9.58872 4.20848 8.4931C4.68081 7.39748 5.32183 6.44444 6.13153 5.63398C6.94123 4.82352 7.89337 4.18192 8.98795 3.70915C10.0826 3.23638 11.2521 3 12.4966 3C13.7412 3 14.9113 3.23616 16.0069 3.70848C17.1025 4.18081 18.0556 4.82183 18.866 5.63153C19.6765 6.44123 20.3181 7.39337 20.7908 8.48795C21.2636 9.58255 21.5 10.7521 21.5 11.9967C21.5 13.2412 21.2638 14.4113 20.7915 15.5069C20.3192 16.6025 19.6782 17.5556 18.8685 18.366C18.0588 19.1765 17.1066 19.8181 16.012 20.2908C14.9174 20.7636 13.7479 21 12.5034 21ZM12.5 20C14.7333 20 16.625 19.225 18.175 17.675C19.725 16.125 20.5 14.2333 20.5 12C20.5 9.76667 19.725 7.875 18.175 6.325C16.625 4.775 14.7333 4 12.5 4C10.2667 4 8.375 4.775 6.825 6.325C5.275 7.875 4.5 9.76667 4.5 12C4.5 14.2333 5.275 16.125 6.825 17.675C8.375 19.225 10.2667 20 12.5 20Z" fill="#E9A845"/>
<path d="M12.9385 9.05385C12.8205 9.1718 12.6744 9.23077 12.5 9.23077C12.3257 9.23077 12.1795 9.1718 12.0615 9.05385C11.9436 8.9359 11.8846 8.78974 11.8846 8.61537C11.8846 8.44102 11.9436 8.29487 12.0615 8.17692C12.1795 8.05897 12.3257 8 12.5 8C12.6744 8 12.8205 8.05897 12.9385 8.17692C13.0564 8.29487 13.1154 8.44102 13.1154 8.61537C13.1154 8.78974 13.0564 8.9359 12.9385 9.05385Z" fill="#E9A845"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -127,6 +127,10 @@ var MyModuleRepositories = (function() {
render: function(data, type, row) {
return "<a href='" + row.recordInfoUrl + "' class='record-info-link'>" + data + '</a>';
}
}, {
targets: 4,
class: 'relationship',
render: (data, type, row) => $.fn.dataTable.render.RelationshipValue(data, row)
});
} else {
columnDefs.push({

View file

@ -267,3 +267,12 @@ $.fn.dataTable.render.RepositoryStockConsumptionValue = function(data = {}) {
$.fn.dataTable.render.defaultRepositoryStockConsumptionValue = function() {
return $.fn.dataTable.render.RepositoryStockConsumptionValue();
};
$.fn.dataTable.render.RelationshipValue = function(data, row) {
return `<a
style="text-decoration: none !important;"
class="relationships-info-link !text-sn-blue !no-underline pl-4"
href=${row.recordInfoUrl}>
${data}
</a>`;
};

View file

@ -674,8 +674,16 @@ var RepositoryDatatable = (function(global) {
+ "class='record-info-link' data-e2e='e2e-TL-invInventoryTR-Item-" + row.DT_RowId + "'>" + data + '</a>';
}
}, {
// Added on column
targets: 4,
class: 'relationship',
searchable: false,
orderable: true,
render: function(data, type, row) {
return $.fn.dataTable.render.RelationshipValue(data, row);
}
}, {
// Added on column
targets: 5,
class: 'added-on',
visible: true
}, {
@ -747,7 +755,6 @@ var RepositoryDatatable = (function(global) {
// Hide edit button if not all selected rows are on the current page
$('#editRepositoryRecord').prop('disabled', !allSelectedRowsAreOnPage());
TABLE.columns([archivedOnIndex, archivedByIndex]).visible(archived);
},
preDrawCallback: function() {
@ -1137,6 +1144,7 @@ var RepositoryDatatable = (function(global) {
clearRowSelection();
},
selectedRows: () => { return rowsSelected; },
repositoryId: () => $(TABLE_ID).data('repository-id'),
redrawTableOnSidebarToggle: redrawTableOnSidebarToggle,
checkAvailableColumns: checkAvailableColumns
});

View file

@ -283,7 +283,7 @@ var RepositoryColumns = (function() {
var scrollPosition = $columnsList.scrollTop();
// Clear the list
$columnsList.find('li[data-position]').remove();
_.each(TABLE.columns().header(), function(el, index) {
_.each(TABLE.columns().header(), (el) => {
if (!el.dataset.unmanageable) {
let colId = $(el).attr('id');
let colIndex = $(el).attr('data-column-index');

View file

@ -10,7 +10,7 @@
if ($(table).parent().hasClass('table-wrapper')) return;
$(table).wrap(`
<div class="table-wrapper" style="overflow: auto; width: ${$($(rtf)[0]).parent().width()}px"></div>
<div class="table-wrapper w-full" style="overflow: auto;"></div>
`);
}
}

View file

@ -37,11 +37,9 @@
function initializeHandsonTable(el) {
var input = el.siblings('input.hot-table-contents');
var inputObj = JSON.parse(input.attr('value'));
var metadataJson = el.siblings('input.hot-table-metadata');
var data = inputObj.data;
var metadata;
const metadata = JSON.parse(el.siblings('input.hot-table-metadata').val() || '{}');
metadata = JSON.parse(metadataJson.val() || '{}');
el.handsontable({
disableVisualSelection: true,
rowHeaders: tableColRowName.tableRowHeaders(metadata.plateTemplate),

View file

@ -6,11 +6,9 @@
function initializeHandsonTable(el) {
var input = el.siblings('input.hot-table-contents');
var inputObj = JSON.parse(input.attr('value'));
var metadataJson = el.siblings('input.hot-table-metadata');
var data = inputObj.data;
var metadata;
const metadata = JSON.parse(el.siblings('input.hot-table-metadata').val() || '{}');
metadata = JSON.parse(metadataJson.val() || '{}');
el.handsontable({
disableVisualSelection: true,
rowHeaders: true,

View file

@ -148,9 +148,6 @@ let inlineEditing = (function() {
$(editBlocks).click();
}
})
.on('blur', `${editBlocks} textarea, ${editBlocks} input`, function(e) {
saveAllEditFields();
})
.on('click', editBlocks, function(e) {
// 'A' mean that, if we click on <a></a> element we will not go in edit mode
var container = $(this);

View file

@ -3,14 +3,24 @@
(function() {
'use strict';
$(document).on('click', '.record-info-link', function(e) {
$(document).on('click', '.relationships-info-link', (e) => {
const myModuleId = $('.my-module-content').data('task-id');
const repositoryRowURL = $(e.target).attr('href');
e.stopPropagation();
e.preventDefault();
window.repositoryItemSidebarComponent.toggleShowHideSidebar(repositoryRowURL, myModuleId, 'relationships-section');
});
$(document).on('click', '.record-info-link', function (e) {
const myModuleId = $('.my-module-content').data('task-id');
const repositoryRowURL = $(this).attr('href');
e.stopPropagation();
e.preventDefault();
window.repositoryItemSidebarComponent.toggleShowHideSidebar(repositoryRowURL, myModuleId);
window.repositoryItemSidebarComponent.toggleShowHideSidebar(repositoryRowURL, myModuleId, null);
});
$(document).on('click', '.print-label-button', function(e) {
@ -25,8 +35,10 @@
PrintModalComponent.openModal();
if (selectedRows && selectedRows.length) {
$('#modal-info-repository-row').modal('hide');
PrintModalComponent.repository_id = $(this).data('repositoryId');
PrintModalComponent.row_ids = selectedRows;
} else {
PrintModalComponent.repository_id = RepositoryDatatable.repositoryId();
PrintModalComponent.row_ids = [...RepositoryDatatable.selectedRows()];
}
}
@ -83,7 +95,7 @@
updateCallback = (data) => {
if (!data?.value) return;
// reload dataTable
if ($('.dataTable')[0]) $('.dataTable').DataTable().ajax.reload(null, false);
if ($('.dataTable.repository-dataTable')[0]) $('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
// update item card stock column
window.manageStockCallback && window.manageStockCallback(data.value);
$link.data('manageStockUrl', data.value.stock_url)

View file

@ -178,7 +178,8 @@ var zebraPrint = (function() {
printer_name: string,
number_of_copies: int,
label_template_id: int,
repository_row_ids: array[]
repository_row_ids: array[],
repository_id: int
}
*/
print: function(modalUrl, progressModal, printModal, printData) {

View file

@ -513,3 +513,22 @@
}
}
}
#repositoryItemRelationshipsModal {
.item-options {
max-height: 23rem;
.option-label {
-webkit-line-clamp: 1;
}
}
.inventory-options {
left: 0 !important;
max-height: 28rem;
.option-label {
cursor: pointer;
}
}
}

View file

@ -107,11 +107,11 @@
.dp__input_icon {
display: flex;
height: 1.5rem;
left: .5rem;
left: .75rem;
}
img {
width: 1rem;
}
.dp__clear_icon {
width: 1.375rem;
}
.dp__input {
@ -178,6 +178,13 @@
--dp-range-between-border-color: var(--dp-hover-color, var(--sn-super-light-grey));
}
.dp__input_focus {
&:hover {
border-color: var(--sn-science-blue);
}
border-color: var(--sn-science-blue);
}
:root {
/*General*/
--dp-font-family: inherit; /*Font family*/
@ -204,7 +211,7 @@
--dp-overlay-col-padding: .25rem; /*Padding in the overlay column*/
--dp-time-inc-dec-button-size: 1.5rem; /*Sizing for arrow buttons in the time picker*/
--dp-menu-padding: 1rem; /*Menu padding*/
--dp-input-icon-padding: 2rem; /*Padding on the left side of the input if icon is present*/
--dp-input-icon-padding: 2.25rem; /*Padding on the left side of the input if icon is present*/
/*Font sizes*/
--dp-font-size: .875rem; /*Default font-size*/

View file

@ -35,7 +35,7 @@
}
&.disabled {
background: var(--sn-sleepy-grey);
background: var(--sn-light-grey);
pointer-events: none;
.caret {

View file

@ -8,3 +8,7 @@ $modal-shadow: 0px 4px 16px rgba(35, 31, 32, 0.15);
.sn-shadow-menu-sm {
box-shadow: 0px 16px 32px 0px rgba(16, 24, 40, 0.07);
}
.sn-shadow-menu-lg {
box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14);
}

View file

@ -1,16 +1,3 @@
/* Hide arrows on number type input field */
@layer utilities {
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
}
}
@layer components {
.sci-btn-group {
@apply inline-flex items-center gap-2 relative;

View file

@ -11,6 +11,15 @@ module Api
class FilterParamError < StandardError; end
class MutuallyExclusiveParamsError < StandardError
attr_reader :first_param, :second_param
def initialize(first_param, second_param)
@first_param = first_param
@second_param = second_param
end
end
class PermissionError < StandardError
attr_reader :klass, :mode
@ -28,6 +37,14 @@ module Api
:bad_request)
end
rescue_from MutuallyExclusiveParamsError do |e|
render_error(I18n.t('api.core.errors.mutually_exclusive_params_error.title'),
I18n.t('api.core.errors.mutually_exclusive_params_error.detail',
first_param: e.first_param,
second_param: e.second_param),
:bad_request)
end
rescue_from FilterParamError do |e|
logger.error e.message
logger.error e.backtrace.join("\n")

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
module Api
module V2
class InventoryItemRelationshipsController < BaseController
before_action :load_team, :load_inventory, :load_inventory_item
before_action :check_manage_permission, only: %w(create destroy)
before_action :load_create_params, only: :create
def create
parent = @relation == :parent ? @inventory_item : @inventory_item_to_link
child = @relation == :child ? @inventory_item : @inventory_item_to_link
@connection = RepositoryRowConnection.create!(
parent_id: parent,
child_id: child,
created_by: current_user,
last_modified_by: current_user
)
render jsonapi: @connection, serializer: InventoryItemRelationshipSerializer, status: :created
end
def destroy
@connection = @inventory_item.parent_connections
.or(@inventory_item.child_connections)
.find(params.require(:id))
@connection.destroy!
render body: nil
end
private
def check_manage_permission
raise PermissionError.new(Repository, :manage) unless can_manage_repository?(@inventory)
end
def load_create_params
if connection_params[:parent_id].present? && connection_params[:child_id].present?
raise MutuallyExclusiveParamsError.new(:parent_id, :child_id)
end
if connection_params[:parent_id].present?
@relation = :parent
@inventory_item_to_link = RepositoryRow.find(connection_params[:parent_id])
elsif connection_params[:child_id].present?
@relation = :child
@inventory_item_to_link = RepositoryRow.find(connection_params[:child_id])
end
raise ActiveRecord::RecordNotFound unless @inventory_item_to_link
end
def connection_params
raise TypeError unless params.require(:data).require(:type) == 'inventory_item_relationships'
params.require(:data).require(:attributes).permit(%i(parent_id child_id))
end
def permitted_includes
%w(parent child)
end
end
end
end

View file

@ -7,14 +7,14 @@ module Api
steps = timestamps_filter(@protocol.steps).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: steps, each_serializer: StepSerializer,
render jsonapi: steps, each_serializer: Api::V2::StepSerializer,
include: include_params,
rte_rendering: render_rte?,
team: @team
end
def show
render jsonapi: @step, serializer: StepSerializer,
render jsonapi: @step, serializer: Api::V2::StepSerializer,
include: include_params,
rte_rendering: render_rte?,
team: @team
@ -31,7 +31,7 @@ module Api
last_modified_by_id: current_user.id)
)
end
render jsonapi: @step, serializer: StepSerializer, status: :created
render jsonapi: @step, serializer: Api::V2::StepSerializer, status: :created
end
def update
@ -48,7 +48,7 @@ module Api
num_completed: completed_steps.to_s,
num_all: all_steps.to_s)
end
render jsonapi: @step, serializer: StepSerializer, status: :ok
render jsonapi: @step, serializer: Api::V2::StepSerializer, status: :ok
else
render body: nil, status: :no_content
end

View file

@ -0,0 +1,190 @@
# frozen_string_literal: true
class RepositoryRowConnectionsController < ApplicationController
before_action :load_repository, except: %i(repositories)
before_action :load_create_vars, only: :create
before_action :check_read_permissions, except: :repositories
before_action :load_repository_row, except: %i(repositories repository_rows)
before_action :check_manage_permissions, only: %i(create destroy)
def index
parents = @repository_row.parent_connections
.joins('INNER JOIN repository_rows ON
repository_rows.id = repository_row_connections.parent_id')
.select(:id, 'repository_rows.id AS repository_row_id',
'repository_rows.name AS repository_row_name')
.map do |row|
{
id: row.id,
name: row.repository_row_name,
code: "#{RepositoryRow::ID_PREFIX}#{row.repository_row_id}"
}
end
children = @repository_row.child_connections
.joins('INNER JOIN repository_rows ON
repository_rows.id = repository_row_connections.child_id')
.select(:id, 'repository_rows.id AS repository_row_id',
'repository_rows.name AS repository_row_name')
.map do |row|
{
id: row.id,
name: row.repository_row_name,
code: "#{RepositoryRow::ID_PREFIX}#{row.repository_row_id}"
}
end
render json: { parents: parents, children: children }
end
def create
RepositoryRowConnection.transaction do
@connection_repository.repository_rows
.where(id: connection_params[:relation_ids])
.where.not(id: @repository_row.id)
.where.not(
id: if @relation_type == 'parent'
@repository_row.parent_connections.select(:parent_id)
else
@repository_row.child_connections.select(:child_id)
end
)
.find_each do |linked_repository_row|
build_connection(linked_repository_row)
log_activity(:inventory_item_relationships_linked,
@repository_row.repository,
{ repository_row: @repository_row.id,
repository_row_linked: linked_repository_row.id,
relationship_type: @relation_type == 'parent' ? 'parent' : 'child' })
end
@repository_row.save!
relation_key = @relation_type == 'parent' ? :parents : :children
render json: { relation_key => connected_rows_by_relation_type }
end
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error e.message
render json: { errors: @repository_row.errors.full_messages }, status: :unprocessable_entity
end
def destroy
RepositoryRowConnection.transaction do
connection = @repository_row.parent_connections.or(@repository_row.child_connections).find(params[:id])
unlinked_repository_row = connection.parent?(@repository_row) ? connection.child : connection.parent
log_activity(:inventory_item_relationships_unlinked,
@repository_row.repository,
{ repository_row: @repository_row.id,
repository_row_unlinked: unlinked_repository_row.id })
connection.destroy!
head :no_content
end
rescue StandardError
head :unprocessable_entity
end
def repositories
repositories = Repository.accessible_by_teams(current_team)
.search_by_name_and_id(current_user, current_user.teams, params[:query])
.order(name: :asc)
.page(params[:page] || 1)
.per(Constants::SEARCH_LIMIT)
render json: {
data: repositories.select(:id, :name, :archived)
.map { |repository| { id: repository.id, name: repository.name_with_label } },
next_page: repositories.next_page
}
end
def repository_rows
repository_rows = @repository.repository_rows
.search_by_name_and_id(current_user, current_user.teams, params[:query])
.order(name: :asc)
.page(params[:page] || 1)
.per(Constants::SEARCH_LIMIT)
render json: {
data: repository_rows.select(:id, :name, :archived, :repository_id)
.preload(:repository)
.map { |row| { id: row.id, name: row.name_with_label } },
next_page: repository_rows.next_page
}
end
private
def load_create_vars
@relation_type = connection_params[:relation] if connection_params[:relation].in?(%w(parent child))
return render_422(t('.invalid_params')) unless @relation_type
@connection_repository = Repository.accessible_by_teams(current_team)
.find_by(id: connection_params[:connection_repository_id])
return render_404 unless @connection_repository
return render_403 unless can_connect_repository_rows?(@connection_repository)
end
def load_repository
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
render_404 unless @repository
end
def load_repository_row
@repository_row = @repository.repository_rows.find_by(id: params[:repository_row_id])
render_404 unless @repository_row
end
def check_read_permissions
render_403 unless can_read_repository?(@repository)
end
def check_manage_permissions
render_403 unless can_connect_repository_rows?(@repository)
end
def connection_params
params.require(:repository_row_connection).permit(:connection_repository_id, :relation, relation_ids: [])
end
def build_connection(linked_repository_row)
connection_params = {
created_by: current_user,
last_modified_by: current_user
}
if @relation_type == 'parent'
@repository_row.parent_connections.build(connection_params.merge(parent: linked_repository_row))
else
@repository_row.child_connections.build(connection_params.merge(child: linked_repository_row))
end
end
def connected_rows_by_relation_type
repository_rows = if @relation_type == 'parent'
@repository_row.parent_repository_rows
else
@repository_row.child_repository_rows
end
repository_rows.preload(:repository)
.map do |repository_row|
{
name: repository_row.name,
code: repository_row.code,
path: repository_repository_row_path(repository_row.repository, repository_row),
repository_name: repository_row.repository.name,
repository_path: repository_path(repository_row.repository)
}
end
end
def log_activity(type_of, repository, message_items = {})
message_items = { repository: repository.id }.merge(message_items)
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
subject: repository,
team: repository.team,
message_items: message_items)
end
end

View file

@ -5,18 +5,15 @@ class RepositoryRowsController < ApplicationController
include MyModulesHelper
include RepositoryDatatableHelper
MAX_PRINTABLE_ITEM_NAME_LENGTH = 64
before_action :load_repository, except: %i(show print rows_to_print print_zpl
validate_label_template_columns actions_toolbar)
before_action :load_repository_row_print, only: %i(print rows_to_print print_zpl validate_label_template_columns)
before_action :load_show_vars, only: %i(show)
before_action :load_repository_or_snapshot, only: %i(print rows_to_print print_zpl
before_action :load_repository, except: %i(show print rows_to_print print_zpl validate_label_template_columns)
before_action :load_repository_or_snapshot, only: %i(show print rows_to_print print_zpl
validate_label_template_columns)
before_action :load_repository_row, only: %i(update update_cell assigned_task_list active_reminder_repository_cells)
before_action :check_read_permissions, except: %i(create update delete_records
copy_records reminder_repository_cells
delete_records archive_records restore_records
actions_toolbar)
before_action :load_repository_row, only: %i(show update update_cell assigned_task_list
active_reminder_repository_cells relationships)
before_action :load_repository_rows, only: %i(print rows_to_print print_zpl validate_label_template_columns)
before_action :check_read_permissions, except: %i(create update update_cell delete_records
copy_records archive_records restore_records)
before_action :check_snapshotting_status, only: %i(create update delete_records copy_records)
before_action :check_create_permissions, only: :create
before_action :check_delete_permissions, only: %i(delete_records archive_records restore_records)
@ -101,7 +98,7 @@ class RepositoryRowsController < ApplicationController
def validate_label_template_columns
label_template = LabelTemplate.where(team_id: current_team.id).find(params[:label_template_id])
label_code = LabelTemplates::RepositoryRowService.new(label_template, @repository_row.first).render
label_code = LabelTemplates::RepositoryRowService.new(label_template, @repository_rows.first).render
if label_code[:error].empty?
render json: { label_code: label_code[:label] }
else
@ -111,7 +108,7 @@ class RepositoryRowsController < ApplicationController
def print_zpl
label_template = LabelTemplate.find_by(id: params[:label_template_id])
labels = @repository_row.flat_map do |repository_row|
labels = @repository_rows.flat_map do |repository_row|
LabelTemplates::RepositoryRowService.new(label_template,
repository_row).render[:label]
end
@ -128,7 +125,7 @@ class RepositoryRowsController < ApplicationController
end
def rows_to_print
render json: @repository_row, each_serializer: RepositoryRowSerializer, user: current_user
render json: @repository_rows, each_serializer: RepositoryRowSerializer, user: current_user
end
def print
@ -141,7 +138,7 @@ class RepositoryRowsController < ApplicationController
label_printer = LabelPrinter.find(params[:label_printer_id])
label_template = LabelTemplate.find_by(id: params[:label_template_id])
job_ids = @repository_row.flat_map do |repository_row|
job_ids = @repository_rows.flat_map do |repository_row|
LabelPrinters::PrintJob.perform_later(
label_printer,
LabelTemplates::RepositoryRowService.new(label_template,
@ -349,6 +346,14 @@ class RepositoryRowsController < ApplicationController
}
end
def relationships
render json: {
repository_row: RepositoryRowSerializer.new(@repository_row),
parents: @repository_row.parent_repository_rows.map { |r| RepositoryRowSerializer.new(r) },
children: @repository_row.child_repository_rows.map { |r| RepositoryRowSerializer.new(r) }
}
end
private
include StringUtility
@ -361,26 +366,10 @@ class RepositoryRowsController < ApplicationController
render_404 unless @repository
end
def load_repository_row_print
@repository_row = RepositoryRow.where(id: params[:rows])
render_404 unless @repository_row
end
def load_repository_or_snapshot
@repository = Repository.accessible_by_teams(current_team).find_by(id: @repository_row&.first&.repository_id)
@repository ||= RepositorySnapshot.find_by(id: @repository_row&.first&.repository_id)
render_404 unless @repository
end
def load_show_vars
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
@repository ||= RepositorySnapshot.find_by(id: params[:repository_id])
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id]) ||
RepositorySnapshot.find_by(id: params[:repository_id])
return render_404 unless @repository
@repository_row = @repository.repository_rows.eager_load(:repository_columns).find_by(id: params[:id])
render_404 unless @repository_row
end
def load_repository_row
@ -388,6 +377,12 @@ class RepositoryRowsController < ApplicationController
render_404 unless @repository_row
end
def load_repository_rows
@repository_rows = @repository.repository_rows.eager_load(:repository_columns).where(id: params[:row_ids])
render_404 if @repository_rows.blank?
end
def check_read_permissions
render_403 unless can_read_repository?(@repository)
end

View file

@ -60,26 +60,4 @@ module DatabaseHelper
"AS t WHERE t.id = #{id};"
).getvalue(0, 0).to_i
end
# Adds a check constraint to the table
def add_check_constraint(table, constraint_name, constraint)
ActiveRecord::Base.connection.execute(
"ALTER TABLE " \
"#{table} " \
"DROP CONSTRAINT IF EXISTS #{constraint_name}, " \
"ADD CONSTRAINT " \
"#{constraint_name} " \
"CHECK ( #{constraint} ) " \
"not valid;"
)
end
# Remove constraint from the table
def drop_constraint(table, constraint_name)
ActiveRecord::Base.connection.execute(
"ALTER TABLE " \
"#{table} " \
"DROP CONSTRAINT IF EXISTS #{constraint_name}; "
)
end
end

View file

@ -24,7 +24,12 @@ module RepositoryDatatableHelper
.active_reminder_repository_cells_repository_repository_row_url(
repository,
record
)
),
relationshipsUrl:
Rails.application.routes.url_helpers
.relationships_repository_repository_row_url(record.repository_id, record.id),
relationships_enabled: repository_row_connections_enabled,
code: record.code
)
if reminders_enabled
@ -226,10 +231,11 @@ module RepositoryDatatableHelper
'1': assigned_row(record),
'2': record.code,
'3': escape_input(record.name),
'4': I18n.l(record.created_at, format: :full),
'5': escape_input(record.created_by.full_name),
'6': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''),
'7': escape_input(record.archived_by&.full_name)
'4': "#{record.parent_connections_count || 0} / #{record.child_connections_count || 0}",
'5': I18n.l(record.created_at, format: :full),
'6': escape_input(record.created_by.full_name),
'7': (record.archived_on ? I18n.l(record.archived_on, format: :full) : ''),
'8': escape_input(record.archived_by&.full_name)
}
end
@ -311,4 +317,8 @@ module RepositoryDatatableHelper
def display_stock_warnings?(repository)
!repository.is_a?(RepositorySnapshot)
end
def repository_row_connections_enabled
Repository.repository_row_connections_enabled?
end
end

View file

@ -0,0 +1,15 @@
/* global notTurbolinksPreview */
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import { vOnClickOutside } from '@vueuse/components';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
import ItemRelationshipsModal from '../../vue/item_relationships/ItemRelationshipsModal.vue';
const app = createApp({});
app.component('ItemRelationshipsModal', ItemRelationshipsModal);
app.use(ItemRelationshipsModal);
app.use(PerfectScrollbar);
app.directive('click-outside', vOnClickOutside);
app.config.globalProperties.i18n = window.I18n;
mountWithTurbolinks(app, '#itemRelationshipsModalWrapper');

View file

@ -0,0 +1,15 @@
/* global notTurbolinksPreview */
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import { vOnClickOutside } from '@vueuse/components';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
import RepositoryItemRelationshipsModal from '../../vue/item_relationships/RepositoryItemRelationshipsModal.vue';
const app = createApp({});
app.component('RepositoryItemRelationshipsModal', RepositoryItemRelationshipsModal);
app.use(RepositoryItemRelationshipsModal);
app.use(PerfectScrollbar);
app.directive('click-outside', vOnClickOutside);
app.config.globalProperties.i18n = window.I18n;
mountWithTurbolinks(app, '#repositoryItemRelationshipsModalWrapper');

View file

@ -2,9 +2,9 @@
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import RepositoryItemSidebar from '../../vue/repository_item_sidebar/RepositoryItemSidebar.vue';
import { vOnClickOutside } from '@vueuse/components';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
import { vOnClickOutside } from '@vueuse/components'
import RepositoryItemSidebar from '../../vue/repository_item_sidebar/RepositoryItemSidebar.vue';
const app = createApp({});
app.component('RepositoryItemSidebar', RepositoryItemSidebar);

View file

@ -127,7 +127,7 @@
import SelectDropdown from "../shared/select_dropdown.vue";
export default {
name: "AssignItemsToTaskModalContainer",
name: 'AssignItemsToTaskModalContainer',
props: {
visibility: Boolean,
urls: Object,
@ -143,17 +143,17 @@ export default {
selectedTask: null,
projectsLoading: null,
experimentsLoading: null,
tasksLoading: null,
tasksLoading: null
};
},
components: {
SelectDropdown
},
mounted() {
$(this.$refs.modal).on("shown.bs.modal", () => {
$(this.$refs.modal).on('shown.bs.modal', () => {
this.projectsLoading = true;
$.get(this.projectURL, data => {
$.get(this.projectURL, (data) => {
if (Array.isArray(data)) {
this.projects = data;
return false;
@ -164,9 +164,9 @@ export default {
});
});
$(this.$refs.modal).on("hidden.bs.modal", () => {
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.resetSelectors();
this.$emit("close");
this.$emit('close');
});
},
beforeUnmount() {
@ -176,36 +176,36 @@ export default {
experimentsSelectorPlaceholder() {
if (this.selectedProject) {
return this.i18n.t(
"repositories.modal_assign_items_to_task.body.experiment_select.placeholder"
'repositories.modal_assign_items_to_task.body.experiment_select.placeholder'
);
}
return this.i18n.t(
"repositories.modal_assign_items_to_task.body.experiment_select.disabled_placeholder"
'repositories.modal_assign_items_to_task.body.experiment_select.disabled_placeholder'
);
},
tasksSelectorPlaceholder() {
if (this.selectedExperiment) {
return this.i18n.t(
"repositories.modal_assign_items_to_task.body.task_select.placeholder"
'repositories.modal_assign_items_to_task.body.task_select.placeholder'
);
}
return this.i18n.t(
"repositories.modal_assign_items_to_task.body.task_select.disabled_placeholder"
'repositories.modal_assign_items_to_task.body.task_select.disabled_placeholder'
);
},
projectURL() {
return `${this.urls.projects}`;
},
experimentURL() {
return `${this.urls.experiments}?project_id=${this.selectedProject ||
""}`;
return `${this.urls.experiments}?project_id=${this.selectedProject
|| ''}`;
},
taskURL() {
return `${this.urls.tasks}?experiment_id=${this.selectedExperiment ||
""}`;
return `${this.urls.tasks}?experiment_id=${this.selectedExperiment
|| ''}`;
},
assignURL() {
return this.urls.assign.replace(":module_id", this.selectedTask);
return this.urls.assign.replace(':module_id', this.selectedTask);
}
},
watch: {
@ -219,10 +219,10 @@ export default {
},
methods: {
showModal() {
$(this.$refs.modal).modal("show");
$(this.$refs.modal).modal('show');
},
hideModal() {
$(this.$refs.modal).modal("hide");
$(this.$refs.modal).modal('hide');
},
changeProject(value) {
this.selectedProject = value;
@ -230,7 +230,7 @@ export default {
this.resetTaskSelector();
this.experimentsLoading = true;
$.get(this.experimentURL, data => {
$.get(this.experimentURL, (data) => {
if (Array.isArray(data)) {
this.experiments = data;
return false;
@ -245,7 +245,7 @@ export default {
this.resetTaskSelector();
this.tasksLoading = true;
$.get(this.taskURL, data => {
$.get(this.taskURL, (data) => {
if (Array.isArray(data)) {
this.tasks = data;
return false;
@ -280,16 +280,16 @@ export default {
$.ajax({
url: this.assignURL,
type: "PATCH",
dataType: "json",
type: 'PATCH',
dataType: 'json',
data: { rows_to_assign: this.rowsToAssign }
}).done(({assigned_count}) => {
}).done(({ assigned_count }) => {
const skipped_count = this.rowsToAssign.length - assigned_count;
if (skipped_count) {
HelperModule.flashAlertMsg(this.i18n.t('repositories.modal_assign_items_to_task.assign.flash_some_assignments_success', {assigned_count: assigned_count, skipped_count: skipped_count }), 'success');
HelperModule.flashAlertMsg(this.i18n.t('repositories.modal_assign_items_to_task.assign.flash_some_assignments_success', { assigned_count, skipped_count }), 'success');
} else {
HelperModule.flashAlertMsg(this.i18n.t('repositories.modal_assign_items_to_task.assign.flash_all_assignments_success', {count: assigned_count}), 'success');
HelperModule.flashAlertMsg(this.i18n.t('repositories.modal_assign_items_to_task.assign.flash_all_assignments_success', { count: assigned_count }), 'success');
}
}).fail(() => {
HelperModule.flashAlertMsg(this.i18n.t('repositories.modal_assign_items_to_task.assign.flash_assignments_failure'), 'danger');

View file

@ -74,136 +74,135 @@
</template>
<script>
import {debounce} from '../shared/debounce.js';
import { debounce } from '../shared/debounce.js';
export default {
name: 'ActionToolbar',
props: {
actionsUrl: { type: String, required: true }
},
data() {
return {
actions: [],
shown: false,
multiple: false,
params: {},
reloadCallback: null,
actionsLoadedCallback: null,
loaded: false,
loading: false,
width: 0,
bottomOffset: 0,
leftOffset: 0,
buttonOverflow: false
}
},
created() {
window.actionToolbarComponent = this;
window.onresize = this.setWidth;
export default {
name: 'ActionToolbar',
props: {
actionsUrl: { type: String, required: true }
},
data() {
return {
actions: [],
shown: false,
multiple: false,
params: {},
reloadCallback: null,
actionsLoadedCallback: null,
loaded: false,
loading: false,
width: 0,
bottomOffset: 0,
leftOffset: 0,
buttonOverflow: false
};
},
created() {
window.actionToolbarComponent = this;
window.onresize = this.setWidth;
this.debouncedFetchActions = debounce((params) => {
this.params = params;
this.debouncedFetchActions = debounce((params) => {
this.params = params;
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
this.actions = data.actions;
this.loading = false;
this.setButtonOverflow();
if (this.actionsLoadedCallback) this.$nextTick(this.actionsLoadedCallback);
});
}, 10);
},
mounted() {
this.$nextTick(this.setWidth);
window.addEventListener('scroll', this.setLeftOffset);
},
beforeUnmount() {
delete window.actionToolbarComponent;
window.removeEventListener('scroll', this.setLeftOffset);
},
computed: {
paramsAreBlank() {
let values = Object.values(this.params);
if (values.length === 0) return true;
return !values.some((v) => v.length);
}
},
methods: {
setWidth() {
this.width = $(this.$el).parent().width();
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
this.actions = data.actions;
this.loading = false;
this.setButtonOverflow();
},
setButtonOverflow() {
// detects if the last action button is outside the toolbar container
this.buttonOverflow = false;
if (this.actionsLoadedCallback) this.$nextTick(this.actionsLoadedCallback);
});
}, 10);
},
mounted() {
this.$nextTick(this.setWidth);
window.addEventListener('scroll', this.setLeftOffset);
},
beforeUnmount() {
delete window.actionToolbarComponent;
window.removeEventListener('scroll', this.setLeftOffset);
},
computed: {
paramsAreBlank() {
const values = Object.values(this.params);
this.$nextTick(() => {
if (
!(this.$el.getBoundingClientRect &&
document.querySelector('.sn-action-toolbar__action:last-child'))
) return;
if (values.length === 0) return true;
let containerRect = this.$el.getBoundingClientRect();
let lastActionRect = document.querySelector('.sn-action-toolbar__action:last-child').getBoundingClientRect();
return !values.some((v) => v.length);
}
},
methods: {
setWidth() {
this.width = $(this.$el).parent().width();
this.setButtonOverflow();
},
setButtonOverflow() {
// detects if the last action button is outside the toolbar container
this.buttonOverflow = false;
this.buttonOverflow = containerRect.left + containerRect.width < lastActionRect.left + lastActionRect.width;
});
},
setLeftOffset() {
this.leftOffset = -(window.pageXOffset || document.documentElement.scrollLeft);
},
setBottomOffset(pixels) {
this.bottomOffset = pixels;
},
fetchActions(params) {
this.loading = true;
this.debouncedFetchActions(params);
},
setReloadCallback(func) {
this.reloadCallback = func;
},
setActionsLoadedCallback(func) {
this.actionsLoadedCallback = func;
},
doAction(action, event) {
switch(action.type) {
case 'legacy':
// do nothing, this is handled by legacy code based on the button class
break;
case 'link':
// do nothing, already handled by href
break;
case 'modal':
// do nothihg, boostrap modal handled by data-toggle="modal" and data-target
case 'remote-modal':
// do nothing, handled by the data-action="remote-modal" binding
break;
case 'download':
event.stopPropagation();
window.location.href = action.path;
break;
case 'request':
event.stopPropagation();
this.$nextTick(() => {
if (
!(this.$el.getBoundingClientRect
&& document.querySelector('.sn-action-toolbar__action:last-child'))
) return;
$.ajax({
type: action.request_method,
url: action.path,
data: this.params
}).done((data) => {
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'success');
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'danger');
}).always(() => {
if (this.reloadCallback) this.reloadCallback();
});
break;
}
},
closeExportDropdown(event) {
event.preventDefault();
$(event.target).closest('.export-actions-dropdown').removeClass('open')
const containerRect = this.$el.getBoundingClientRect();
const lastActionRect = document.querySelector('.sn-action-toolbar__action:last-child').getBoundingClientRect();
this.buttonOverflow = containerRect.left + containerRect.width < lastActionRect.left + lastActionRect.width;
});
},
setLeftOffset() {
this.leftOffset = -(window.pageXOffset || document.documentElement.scrollLeft);
},
setBottomOffset(pixels) {
this.bottomOffset = pixels;
},
fetchActions(params) {
this.loading = true;
this.debouncedFetchActions(params);
},
setReloadCallback(func) {
this.reloadCallback = func;
},
setActionsLoadedCallback(func) {
this.actionsLoadedCallback = func;
},
doAction(action, event) {
switch (action.type) {
case 'legacy':
// do nothing, this is handled by legacy code based on the button class
break;
case 'link':
// do nothing, already handled by href
break;
case 'modal':
// do nothihg, boostrap modal handled by data-toggle="modal" and data-target
case 'remote-modal':
// do nothing, handled by the data-action="remote-modal" binding
break;
case 'download':
event.stopPropagation();
window.location.href = action.path;
break;
case 'request':
event.stopPropagation();
$.ajax({
type: action.request_method,
url: action.path,
data: this.params
}).done((data) => {
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'success');
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'danger');
}).always(() => {
if (this.reloadCallback) this.reloadCallback();
});
break;
}
},
closeExportDropdown(event) {
event.preventDefault();
$(event.target).closest('.export-actions-dropdown').removeClass('open');
}
}
};
</script>

View file

@ -0,0 +1,85 @@
<template>
<div ref="itemRelationshipsModal" @keydown.esc="cancel" id="itemRelationshipsModal" tabindex="-1" role="dialog"
class="modal">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<!-- header -->
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-destroy-team-label">
{{ rowCode }}
</h4>
</div>
<div v-if="isLoading" class="flex justify-center h-40 w-auto">
<p class="m-auto h-fit w-fit">Loading...</p>
</div>
<div v-else class="modal-body ">
<!-- parents -->
<div v-if="parents?.length > 0">
<p>Parents:</p>
<div v-for="(parentObj, index) in parents" :key="index" class="flex flex-col">
<div>{{ parentObj?.code }} | {{ parentObj?.name }}</div>
</div>
</div>
<!-- children -->
<div v-if="children?.length > 0">
<p>Children:</p>
<div v-for="(childObj, index) in children" :key="index" class="flex flex-col">
<div>{{ childObj?.code }} | {{ childObj?.name }}</div>
</div>
</div>
</div>
<!-- footer -->
<div class="modal-footer">
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ItemRelationshipsModal',
created() {
window.itemRelationshipsModal = this;
},
data() {
return {
isLoading: false,
rowCode: null,
parents: [],
children: []
};
},
methods: {
show(relationshipsUrl) {
$(this.$refs.itemRelationshipsModal).modal('show');
this.fetchItemRelationshipsData(relationshipsUrl);
},
fetchItemRelationshipsData(relationshipsUrl) {
this.isLoading = true;
$.ajax({
method: 'GET',
url: relationshipsUrl,
dataType: 'json',
success: (result) => {
this.isLoading = false;
this.parents = result.parents;
this.children = result.children;
this.rowCode = result.repository_row.code;
}
});
},
cancel() {
$(this.$refs.itemRelationshipsModal).modal('hide');
}
}
};
</script>

View file

@ -0,0 +1,280 @@
<template>
<div
v-if="canConnectRows"
ref="repositoryItemRelationshipsModal"
@keydown.esc="close"
id="repositoryItemRelationshipsModal"
tabindex="-1"
role="dialog"
class="modal ">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content w-[400px] m-auto">
<!-- header -->
<div class="modal-header h-[76px] flex !flex-col gap-[6px]">
<!-- header title with close icon -->
<div class="h-[30px] w-full flex flex-row-reverse">
<i id="close-icon" class="sn-icon sn-icon-close ml-auto cursor-pointer my-auto mx-0" data-dismiss="modal"
aria-label="Close" @click="close"></i>
<h4 class="modal-title" id="modal-destroy-team-label">
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.header_title') }}
</h4>
</div>
<!-- header subtitle -->
<div class="h-10 overflow-hidden break-words text-sn-dark-grey text-sm font-normal leading-5 self-start"
style="-webkit-box-orient: vertical; -webkit-line-clamp: 2; display: -webkit-box;">
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.header_subtitle') }}
</div>
</div>
<div class="modal-body flex flex-col gap-6" :class="{ '!pb-3': notification }">
<!-- inventory -->
<div class="flex flex-col gap-[7px]">
<div class="h-5 whitespace-nowrap overflow-auto">
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.inventory_section_title') }}</div>
<div class="h-11">
<select-search ref="ChangeSelectedInventoryDropdownSelector" @change="changeSelectedInventory" :value="selectedInventoryValue"
:options="inventoryOptions"
:isLoading="isLoadingInventories"
:lazyLoadEnabled="true"
:optionsUrl="inventoriesUrl"
optionsClassName="inventory-options"
:placeholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_inventory_placeholder')"
:noOptionsPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_inventory_no_options_placeholder')"
:searchPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_inventory_placeholder')"
@update-options="updateInventories"
@reached-end="fetchInventories"
></select-search>
</div>
</div>
<!-- item -->
<div class="flex flex-col gap-[7px]">
<div class="h-5 whitespace-nowrap overflow-auto">
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.item_section_title') }}</div>
<div class="h-11">
<ChecklistSearch
ref="ChangeSelectedItemChecklistSelector"
:shouldUpdateWithoutValues="true"
:lazyLoadEnabled="true"
:withButtons="false"
:withEditCursor="false"
optionsClassName="item-options"
:optionsUrl="inventoryItemsUrl"
:options="itemOptions"
:placeholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_item_placeholder')"
:noOptionsPlaceholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_item_no_options_placeholder')"
:initialSelectedValues="selectedItemValues"
:shouldUpdateOnToggleClose="true"
:params="itemParams"
@update-options="updateInventoryItems"
@update="selectedItemValues = $event"
@reached-end="() => fetchInventoryItems(selectedInventoryValue)"
></ChecklistSearch>
</div>
</div>
<!-- relationship -->
<div class="flex flex-col gap-[7px]">
<div class="h-5 whitespace-nowrap overflow-auto">
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.relationship_section_title') }}
</div>
<div class="h-11">
<Select
ref="ChangeSelectedRelationshipDropdownSelector"
class="hover:border-sn-sleepy-grey"
@change="selectedRelationshipValue = $event"
:value="selectedRelationshipValue"
:options="[['parent', 'Parent'], ['child', 'Child']]"
:placeholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_relationship_placeholder')"
></Select>
</div>
</div>
</div>
<!-- Notification -->
<template v-if="notification">
<hr class="bg-sn-light-grey mb-6 mt-3" />
<div class="w-full mb-6">
<div class="flex align-center gap-2.5">
<img class="w-6 h-6" :src="notificationIconPath" alt="warning" />
<span class="my-auto">{{ notification.message }}</span>
</div>
<div v-html="notification.support_html" class="pl-2.5"></div>
</div>
</template>
<!-- footer -->
<div class="modal-footer">
<div class="flex justify-end gap-4">
<button class="btn btn-secondary w-[78px] h-10 whitespace-nowrap" @click="close">
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.cancel_button') }}
</button>
<button class="btn btn-primary w-[59px] h-10 whitespace-nowrap"
:class="{ 'disabled': !shouldEnableAddButton }" @click="() => addRelation(selectedRelationshipValue)">
{{ i18n.t('repositories.item_card.repository_item_relationships_modal.add_button') }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import SelectSearch from '../shared/select_search.vue';
import ChecklistSearch from '../shared/checklist_search.vue';
import Select from '../shared/select.vue';
import ChecklistSelect from '../shared/checklist_select.vue';
export default {
name: 'RepositoryItemRelationshipsModal',
components: {
'select-search': SelectSearch,
ChecklistSearch,
Select,
'checklist-select': ChecklistSelect
},
created() {
window.repositoryItemRelationshipsModal = this;
},
data() {
return {
isLoadingInventories: false,
inventoriesUrl: '',
inventoryItemsUrl: '',
createConnectionUrl: '',
createConnectionUrlValue: '',
selectedInventoryValue: null,
selectedItemValues: [],
selectedRelationshipValue: null,
inventoryOptions: [],
itemOptions: [],
nextInventoriesPage: 1,
nextItemsPage: 1,
itemParams: [],
notification: null,
notificationIconPath: null,
canConnectRows: null
};
},
computed: {
shouldEnableAddButton() {
return ((this.selectedInventoryValue !== null) && (this.selectedRelationshipValue !== null) && (this.selectedItemValues.length > 0));
}
},
methods: {
fetchInventories() {
if (!this.nextInventoriesPage) return;
this.loadingInventories = true;
$.ajax({
url: `${this.inventoriesUrl}?page=${this.nextInventoriesPage}`,
success: (result) => {
this.inventoryOptions = this.inventoryOptions.concat(result.data.map((val) => [val.id, val.name]));
this.loadingInventories = false;
this.nextInventoriesPage = result.next_page;
}
});
},
fetchInventoryItems(inventoryValue = null) {
if (!inventoryValue || !this.nextItemsPage) return;
this.loadingItems = true;
$.ajax({
url: `${this.inventoryItemsUrl}/?page=${this.nextItemsPage}&repository_id=${inventoryValue}`,
success: (result) => {
this.itemOptions = this.itemOptions.concat(result.data.map((val) => ({ id: val.id, label: val.name })));
this.loadingItems = false;
this.nextItemsPage = result.next_page;
}
});
},
updateInventories(currentOptions, result) {
this.inventoryOptions = currentOptions.concat(result.data?.map((val) => [val.id, val.name]));
},
updateInventoryItems(currentOptions, result) {
this.itemOptions = currentOptions.concat(result.data.map(({ id, name }) => ({ id, label: name })));
},
show(params = {}) {
const {
relation,
optionUrls,
addRelationCallback,
notificationIconPath,
notification,
canConnectRows
} = params;
this.inventoriesUrl = optionUrls.inventories_url;
this.inventoryItemsUrl = optionUrls.inventory_items_url;
this.createConnectionUrl = optionUrls.create_url;
this.addRelationCallback = addRelationCallback;
this.notificationIconPath = notificationIconPath;
this.notification = notification;
this.canConnectRows = canConnectRows;
this.$nextTick(() => {
$(this.$refs.repositoryItemRelationshipsModal).modal('show');
});
if (['parent', 'child'].includes(relation)) {
this.selectedRelationshipValue = relation;
}
this.$nextTick(() => {
this.fetchInventories();
});
},
changeSelectedInventory(value) {
this.selectedInventoryValue = value;
this.itemOptions = [];
this.nextItemsPage = 1;
if (value) {
this.loadingItems = true;
this.itemParams = [`repository_id=${value}`];
}
this.$nextTick(() => {
this.fetchInventoryItems(value);
});
},
close() {
Object.assign(this.$data, {
selectedInventoryValue: null,
selectedItemValues: [],
selectedRelationshipValue: null,
nextInventoriesPage: 1,
nextItemsPage: 1,
inventoriesUrl: '',
inventoryItemsUrl: '',
createConnectionUrl: '',
itemOptions: [],
inventoryOptions: [],
itemParams: [],
canConnectRows: null
});
$(this.$refs.repositoryItemRelationshipsModal).modal('hide');
},
addRelation(relation) {
const $this = this;
$.ajax({
url: this.createConnectionUrl,
method: 'POST',
dataType: 'json',
data: {
repository_row_connection: {
relation: this.selectedRelationshipValue,
relation_ids: this.selectedItemValues,
connection_repository_id: this.selectedInventoryValue
}
},
success: (result) => {
$this.addRelationCallback(result, relation);
if ($('.dataTable.repository-dataTable')[0]) $('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
}
});
this.close();
}
}
};
</script>

View file

@ -75,137 +75,129 @@
</div>
</template>
<script>
const DPI_RESOLUTION_OPTIONS = [
{ value: 6, label: '152 dpi' },
{ value: 8, label: '203 dpi' },
{ value: 12, label: '300 dpi'},
{ value: 24, label: '600 dpi' }
]
<script>
const DPMM_RESOLUTION_OPTIONS = [
{ value: 6, label: '6 dpmm (152 dpi)' },
{ value: 8, label: '8 dpmm (203 dpi)' },
{ value: 12, label: '12 dpmm (300 dpi)' },
{ value: 24, label: '24 dpmm (600 dpi)' }
]
const DPI_RESOLUTION_OPTIONS = [
{ value: 6, label: '152 dpi' },
{ value: 8, label: '203 dpi' },
{ value: 12, label: '300 dpi' },
{ value: 24, label: '600 dpi' }
];
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'LabelPreview',
components: { DropdownSelector },
props: {
template: { type: Object, required: true},
zpl: { type: String, required: true },
previewUrl: { type: String, required: true },
viewOnly: {
type: Boolean,
default: false
}
export default {
name: 'LabelPreview',
components: { DropdownSelector },
props: {
template: { type: Object, required: true },
zpl: { type: String, required: true },
previewUrl: { type: String, required: true },
viewOnly: {
type: Boolean,
default: false
}
},
data() {
return {
DPMM_RESOLUTION_OPTIONS,
DPI_RESOLUTION_OPTIONS,
optionsOpen: false,
width: this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm,
height: this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm,
unit: this.template.attributes.unit,
density: this.template.attributes.density,
base64Image: null,
imageStyle: ''
};
},
mounted() {
this.refreshPreview();
},
computed: {
widthMm() {
return this.unit === 'in' ? this.width * 25.4 : this.width;
},
data() {
return {
DPMM_RESOLUTION_OPTIONS,
DPI_RESOLUTION_OPTIONS,
optionsOpen: false,
width: this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm,
height: this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm,
unit: this.template.attributes.unit,
density: this.template.attributes.density,
base64Image: null,
imageStyle: ''
}
heightMm() {
return this.unit === 'in' ? this.height * 25.4 : this.height;
},
mounted() {
canManage() {
return this.template.attributes.urls.update;
}
},
watch: {
unit() {
this.setDefaults();
},
zpl() {
this.refreshPreview();
},
computed: {
widthMm() {
return this.unit === 'in' ? this.width * 25.4 : this.width;
},
heightMm() {
return this.unit === 'in' ? this.height * 25.4 : this.height;
},
canManage() {
return this.template.attributes.urls.update;
template() {
this.unit = this.template.attributes.unit;
this.width = this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm;
this.height = this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm;
this.density = this.template.attributes.density;
}
},
methods: {
setDefaults() {
!this.unit && (this.unit = 'in');
!this.density && (this.density = 12);
!this.width && (this.width = this.unit === 'in' ? 2 : 50.8);
!this.height && (this.height = this.unit === 'in' ? 1 : 25.4);
},
recalculateUnits() {
if (this.unit === 'in') {
this.width /= 25.4;
this.height /= 25.4;
} else {
this.width *= 25.4;
this.height *= 25.4;
}
},
watch: {
unit() {
this.setDefaults();
},
zpl() {
this.refreshPreview();
},
template() {
this.unit = this.template.attributes.unit
this.width = this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm
this.height = this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm
this.density = this.template.attributes.density
}
},
methods: {
setDefaults() {
!this.unit && (this.unit = 'in');
!this.density && (this.density = 12);
!this.width && (this.width = this.unit === 'in' ? 2 : 50.8);
!this.height && (this.height = this.unit === 'in' ? 1 : 25.4);
},
recalculateUnits() {
if (this.unit === 'in') {
this.width /= 25.4;
this.height /= 25.4;
} else {
this.width *= 25.4;
this.height *= 25.4;
}
},
refreshPreview() {
if (this.zpl.length === 0) return;
refreshPreview() {
if (this.zpl.length === 0) return;
this.base64Image = null;
this.base64Image = null;
$.ajax({
url: this.previewUrl,
type: 'GET',
data: {
zpl: this.zpl,
width: this.widthMm,
height: this.heightMm,
density: this.density
},
success: (result) => {
this.base64Image = result.base64_preview;
if (this.base64Image.length > 0) {
this.$emit('preview:valid');
} else {
this.$emit('preview:invalid');
}
},
error: (result) => {
this.base64Image = '';
$.ajax({
url: this.previewUrl,
type: 'GET',
data: {
zpl: this.zpl,
width: this.widthMm,
height: this.heightMm,
density: this.density
},
success: (result) => {
this.base64Image = result.base64_preview;
if (this.base64Image.length > 0) {
this.$emit('preview:valid');
} else {
this.$emit('preview:invalid');
}
},
error: (result) => {
this.base64Image = '';
this.$emit('preview:invalid');
}
});
},
updateUnit(unit) {
if (this.unit === unit) return;
this.unit = unit;
this.recalculateUnits();
this.$emit('unit:update', this.unit);
},
updateDensity(density) {
this.density = density;
this.$emit('density:update', this.density);
},
densityLabel() {
let resolutions = this.unit === 'in' ? DPI_RESOLUTION_OPTIONS : DPMM_RESOLUTION_OPTIONS;
return resolutions.find(element => {
return element.value === this.density;
}).label;
}
});
},
updateUnit(unit) {
if (this.unit === unit) return;
this.unit = unit;
this.recalculateUnits();
this.$emit('unit:update', this.unit);
},
updateDensity(density) {
this.density = density;
this.$emit('density:update', this.density);
},
densityLabel() {
const resolutions = this.unit === 'in' ? DPI_RESOLUTION_OPTIONS : DPMM_RESOLUTION_OPTIONS;
return resolutions.find((element) => element.value === this.density).label;
}
}
</script>
};
</script>

View file

@ -30,51 +30,51 @@
</div>
</div>
</template>
<script>
export default {
name: 'logoInsertModal',
props: {
unit: { type: String, required: true },
density: { type: Number, required: true },
dimension: { type: Array, required: true }
},
data() {
return {
width: 0,
height: 0,
ratio: 1
}
},
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
this.width = this.dimension[0] / this.density
this.height = this.dimension[1] / this.density
if (this.unit == 'in') {
this.width = this.width / 25.4
this.height = this.height / 25.4
}
<script>
export default {
name: 'logoInsertModal',
props: {
unit: { type: String, required: true },
density: { type: Number, required: true },
dimension: { type: Array, required: true }
},
data() {
return {
width: 0,
height: 0,
ratio: 1
};
},
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
this.width = this.dimension[0] / this.density;
this.height = this.dimension[1] / this.density;
if (this.unit == 'in') {
this.width /= 25.4;
this.height /= 25.4;
}
this.width = Math.round(this.width * 100) / 100
this.height = Math.round(this.height * 100) / 100
this.ratio = this.dimension[0] / this.dimension[1]
this.width = Math.round(this.width * 100) / 100;
this.height = Math.round(this.height * 100) / 100;
this.ratio = this.dimension[0] / this.dimension[1];
},
methods: {
updateHeight() {
this.height = Math.round(this.width * 10 / this.ratio) / 10;
},
methods: {
updateHeight() {
this.height = Math.round(this.width * 10 / this.ratio) / 10
},
updateWidth() {
this.width = Math.round(this.height * this.ratio * 10) / 10
},
confirm() {
this.$emit('insert:tag', {tag: `{{LOGO, ${this.unit}, ${this.width}, ${this.height}}}`});
$(this.$refs.modal).modal('hide');
},
cancel() {
$(this.$refs.modal).modal('hide');
}
updateWidth() {
this.width = Math.round(this.height * this.ratio * 10) / 10;
},
confirm() {
this.$emit('insert:tag', { tag: `{{LOGO, ${this.unit}, ${this.width}, ${this.height}}}` });
$(this.$refs.modal).modal('hide');
},
cancel() {
$(this.$refs.modal).modal('hide');
}
}
};
</script>

View file

@ -107,214 +107,217 @@
</div>
</template>
<script>
<script>
import InlineEdit from '../shared/inline_edit.vue'
import InsertFieldDropdown from './insert_field_dropdown.vue'
import LabelPreview from './components/label_preview.vue'
import InlineEdit from '../shared/inline_edit.vue';
import InsertFieldDropdown from './insert_field_dropdown.vue';
import LabelPreview from './components/label_preview.vue';
export default {
name: 'LabelTemplateContainer',
props: {
labelTemplateUrl: String,
labelTemplatesUrl: String,
previewUrl: String,
newLabel: Boolean
export default {
name: 'LabelTemplateContainer',
props: {
labelTemplateUrl: String,
labelTemplatesUrl: String,
previewUrl: String,
newLabel: Boolean
},
data() {
return {
labelTemplate: {
attributes: {}
},
editingName: false,
editingDescription: false,
editingContent: false,
newContent: '',
newLabelWidth: null,
newLabelHeight: null,
newLabelDensity: null,
newLabelUnit: null,
previewContent: '',
previewValid: false,
skipSave: false,
codeErrorMessage: '',
cursorPos: 0
};
},
watch: {
newContent() {
this.showErrors();
},
data() {
return {
labelTemplate: {
attributes: {}
},
editingName: false,
editingDescription: false,
editingContent: false,
newContent: '',
newLabelWidth: null,
newLabelHeight: null,
newLabelDensity: null,
newLabelUnit: null,
previewContent: '',
previewValid: false,
skipSave: false,
codeErrorMessage: '',
cursorPos: 0
}
previewValid() {
this.showErrors();
}
},
computed: {
hasError() {
return this.codeErrorMessage.length > 0;
},
watch: {
newContent() {
this.showErrors();
},
previewValid() {
this.showErrors();
}
canManage() {
return this.labelTemplate.attributes.urls.update && this.notFluics;
},
computed: {
hasError() {
return this.codeErrorMessage.length > 0
},
canManage() {
return this.labelTemplate.attributes.urls.update && this.notFluics
},
notFluics() {
return this.labelTemplate.attributes.type !== "FluicsLabelTemplate"
}
notFluics() {
return this.labelTemplate.attributes.type !== 'FluicsLabelTemplate';
}
},
components: { InlineEdit, InsertFieldDropdown, LabelPreview },
created() {
$.get(this.labelTemplateUrl, (result) => {
this.labelTemplate = result.data;
this.newContent = this.labelTemplate.attributes.content;
this.previewContent = this.labelTemplate.attributes.content;
this.newLabelWidth = this.labelTemplate.attributes.width_mm;
this.newLabelHeight = this.labelTemplate.attributes.height_mm;
this.newLabelDensity = this.labelTemplate.attributes.density;
this.newLabelUnit = this.labelTemplate.attributes.unit;
});
},
methods: {
setNewHeight(val) {
this.newLabelHeight = val;
},
components: {InlineEdit, InsertFieldDropdown, LabelPreview},
created() {
$.get(this.labelTemplateUrl, (result) => {
this.labelTemplate = result.data
this.newContent = this.labelTemplate.attributes.content
this.previewContent = this.labelTemplate.attributes.content
this.newLabelWidth = this.labelTemplate.attributes.width_mm
this.newLabelHeight = this.labelTemplate.attributes.height_mm
this.newLabelDensity = this.labelTemplate.attributes.density
this.newLabelUnit = this.labelTemplate.attributes.unit
})
setNewWidth(val) {
this.newLabelWidth = val;
},
methods: {
setNewHeight(val) {
this.newLabelHeight = val;
},
setNewWidth(val) {
this.newLabelWidth = val;
},
setNewDensity(val) {
this.newLabelDensity = val;
},
setNewUnit(val) {
this.newLabelUnit = val;
},
enableContentEdit() {
this.editingContent = true;
this.$nextTick(() => {
this.$refs.contentInput.focus();
const contentInput = this.$refs.contentInput;
const contentLength = contentInput.value.length;
contentInput.setSelectionRange(contentLength, contentLength);
});
},
disableContentEdit() {
this.editingContent = false;
this.newContent = this.labelTemplate.attributes.content;
this.previewContent = this.labelTemplate.attributes.content;
},
updateName(newName) {
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {label_template: {name: newName}},
success: (result) => {
this.labelTemplate.attributes.name = result.data.attributes.name
}
});
},
updateDescription(newDescription) {
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {label_template: {description: newDescription}},
success: (result) => {
this.labelTemplate.attributes.description = result.data.attributes.description
}
});
},
updateContent() {
this.previewValid = true;
this.saveLabelDimensions();
if (!this.editingContent) return;
if (this.skipSave) {
this.skipSave = false;
return;
setNewDensity(val) {
this.newLabelDensity = val;
},
setNewUnit(val) {
this.newLabelUnit = val;
},
enableContentEdit() {
this.editingContent = true;
this.$nextTick(() => {
this.$refs.contentInput.focus();
const { contentInput } = this.$refs;
const contentLength = contentInput.value.length;
contentInput.setSelectionRange(contentLength, contentLength);
});
},
disableContentEdit() {
this.editingContent = false;
this.newContent = this.labelTemplate.attributes.content;
this.previewContent = this.labelTemplate.attributes.content;
},
updateName(newName) {
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: { label_template: { name: newName } },
success: (result) => {
this.labelTemplate.attributes.name = result.data.attributes.name;
}
});
},
updateDescription(newDescription) {
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: { label_template: { description: newDescription } },
success: (result) => {
this.labelTemplate.attributes.description = result.data.attributes.description;
}
});
},
updateContent() {
this.previewValid = true;
this.$nextTick(() => {
if (this.hasError) return;
this.saveLabelDimensions();
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {label_template: {
content: this.newContent,
}},
success: (result) => {
this.labelTemplate.attributes.content = result.data.attributes.content;
this.editingContent = false;
if (!this.editingContent) return;
if (this.skipSave) {
this.skipSave = false;
return;
}
this.$nextTick(() => {
if (this.hasError) return;
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {
label_template: {
content: this.newContent
}
});
},
success: (result) => {
this.labelTemplate.attributes.content = result.data.attributes.content;
this.editingContent = false;
}
});
},
saveLabelDimensions() {
if (this.newLabelWidth == this.labelTemplate.attributes.width_mm &&
this.newLabelHeight == this.labelTemplate.attributes.height_mm &&
this.newLabelDensity == this.labelTemplate.attributes.density &&
this.newLabelUnit == this.labelTemplate.attributes.unit) {
return
}
});
},
saveLabelDimensions() {
if (this.newLabelWidth == this.labelTemplate.attributes.width_mm
&& this.newLabelHeight == this.labelTemplate.attributes.height_mm
&& this.newLabelDensity == this.labelTemplate.attributes.density
&& this.newLabelUnit == this.labelTemplate.attributes.unit) {
return;
}
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {label_template: {
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {
label_template: {
width_mm: this.newLabelWidth,
height_mm: this.newLabelHeight,
density: this.newLabelDensity,
unit: this.newLabelUnit
}},
success: (result) => {
this.labelTemplate.attributes.width_mm = result.data.attributes.width_mm;
this.labelTemplate.attributes.height_mm = result.data.attributes.height_mm;
this.labelTemplate.attributes.density = result.data.attributes.density;
this.labelTemplate.attributes.unit = result.data.attributes.unit;
}
});
},
generatePreview(skipSave = false) {
this.skipSave = skipSave;
if (!skipSave && this.previewContent === this.newContent && this.previewValid) {
this.updateContent();
} else {
this.previewContent = this.newContent;
},
success: (result) => {
this.labelTemplate.attributes.width_mm = result.data.attributes.width_mm;
this.labelTemplate.attributes.height_mm = result.data.attributes.height_mm;
this.labelTemplate.attributes.density = result.data.attributes.density;
this.labelTemplate.attributes.unit = result.data.attributes.unit;
}
});
},
generatePreview(skipSave = false) {
this.skipSave = skipSave;
if (!skipSave && this.previewContent === this.newContent && this.previewValid) {
this.updateContent();
} else {
this.previewContent = this.newContent;
}
},
invalidPreview() {
this.previewValid = false;
this.skipSave = false;
},
saveCursorPosition() {
this.cursorPos = $(this.$refs.contentInput).prop('selectionStart');
},
insertTag(tag) {
this.enableContentEdit();
const textBefore = this.newContent.substring(0, this.cursorPos);
const textAfter = this.newContent.substring(this.cursorPos, this.newContent.length);
this.newContent = textBefore + tag + textAfter;
this.cursorPos += tag.length;
},
invalidPreview() {
this.previewValid = false;
this.skipSave = false;
},
saveCursorPosition() {
this.cursorPos = $(this.$refs.contentInput).prop('selectionStart');
},
insertTag(tag) {
this.enableContentEdit();
let textBefore = this.newContent.substring(0, this.cursorPos);
let textAfter = this.newContent.substring(this.cursorPos, this.newContent.length);
this.newContent = textBefore + tag + textAfter;
this.cursorPos = this.cursorPos + tag.length;
this.$nextTick(() => {
$(this.$refs.contentInput).prop('selectionStart', this.cursorPos);
$(this.$refs.contentInput).prop('selectionEnd', this.cursorPos);
});
},
showErrors() {
if (this.editingContent) {
if (this.newContent.length === 0) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.empty')
} else if (this.newContent.length > 10000) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.too_long')
} else if (!this.previewValid) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.invalid')
} else {
this.codeErrorMessage = ''
}
this.$nextTick(() => {
$(this.$refs.contentInput).prop('selectionStart', this.cursorPos);
$(this.$refs.contentInput).prop('selectionEnd', this.cursorPos);
});
},
showErrors() {
if (this.editingContent) {
if (this.newContent.length === 0) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.empty');
} else if (this.newContent.length > 10000) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.too_long');
} else if (!this.previewValid) {
this.codeErrorMessage = this.i18n.t('label_templates.show.code_errors.invalid');
} else {
this.codeErrorMessage = ''
this.codeErrorMessage = '';
}
} else {
this.codeErrorMessage = '';
}
}
}
</script>
};
</script>

View file

@ -82,30 +82,30 @@ export default {
props: {
labelTemplate: {
type: Object,
required: true,
},
required: true
}
},
data() {
return {
fields: {
default: [],
common: [],
repositories: [],
repositories: []
},
openLogoModal: false,
logoDimension: null,
searchValue: '',
searchValue: ''
};
},
components: { LogoInsertModal },
computed: {
tooltipTemplate() {
return `<div class="tooltip" role="tooltip">
<div class="tooltip-arrow"></div>
<div class="tooltip-body">
<div class="tooltip-inner"></div>
</div>
</div>`;
<div class="tooltip-arrow"></div>
<div class="tooltip-body">
<div class="tooltip-inner"></div>
</div>
</div>`;
},
filteredFields() {
this.$nextTick(() => {
@ -113,28 +113,24 @@ export default {
$('[data-toggle="tooltip"]').tooltip();
});
if (this.searchValue.length === 0) {
if (this.searchValue.length == 0) {
return this.fields;
}
return {
default: this.filterArray(this.fields.default, 'key'),
common: this.filterArray(this.fields.common, 'key'),
repositories: this.filterArray(this.fields.repositories, 'repository_name').map((repo) => (
{ ...repo, tags: this.filterArray(repo.tags, 'key') }
)),
repositories: this.filterArray(this.fields.repositories, 'repository_name').map((repo) => ({ ...repo, tags: this.filterArray(repo.tags, 'key') }))
};
},
noResults() {
const defaultField = this.filteredFields.default;
return defaultField.concat(this.filteredFields.common, this.filteredFields.repositories).length === 0;
},
return this.filteredFields.default.concat(this.filteredFields.common, this.filteredFields.repositories).length === 0;
}
},
mounted() {
$.get(this.labelTemplate.attributes.urls.fields, (result) => {
result.default.map((value) => {
const newValue = value;
newValue.key = this.i18n.t(`label_templates.default_columns.${value.key}`);
return newValue;
value.key = this.i18n.t(`label_templates.default_columns.${value.key}`);
return value;
});
this.fields = result;
@ -153,7 +149,7 @@ export default {
},
methods: {
insertTag(field) {
if (field.id === 'logo') {
if (field.id == 'logo') {
this.logoDimension = field.dimension;
this.openLogoModal = true;
return;
@ -163,9 +159,9 @@ export default {
filterArray(array, key) {
return array.filter((field) => (
field[key].toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1
|| field.tags
|| field.tags
));
},
},
}
}
};
</script>

View file

@ -61,24 +61,24 @@
</template>
<script>
import BreadcrumbsDropdown from "./breadcrumbs_dropdown.vue";
import BreadcrumbsDropdown from './breadcrumbs_dropdown.vue';
const State = Object.freeze({
INITIAL: "initial",
EXPENDED: "expended",
SHORTENED: "shortened",
SHORTENED_COLLAPSED: "shortened_collapsed"
INITIAL: 'initial',
EXPENDED: 'expended',
SHORTENED: 'shortened',
SHORTENED_COLLAPSED: 'shortened_collapsed'
});
const dropdownWidth = 60;
export default {
name: "Breadcrumbs",
name: 'Breadcrumbs',
props: {
breadcrumbsItems: String,
delimiterUrl: String
},
data() {
return {
dropdownWidth: dropdownWidth,
dropdownWidth,
State,
state: State.INITIAL,
items: [],
@ -136,10 +136,9 @@ export default {
} else if (this.state === this.State.SHORTENED) {
if (width < scrollWidth) {
this.state = this.State.SHORTENED_COLLAPSED;
let visibleWidth =
this.dropdownWidth +
this.$refs.firstItem.offsetWidth +
this.$refs.lastItem.offsetWidth;
let visibleWidth = this.dropdownWidth
+ this.$refs.firstItem.offsetWidth
+ this.$refs.lastItem.offsetWidth;
let index = this.middleItems.length - 1;
while (visibleWidth <= width && index >= 0) {

View file

@ -41,7 +41,7 @@
import { PerfectScrollbar } from 'vue3-perfect-scrollbar';
export default {
name: "BreadcrumbsDropdown",
name: 'BreadcrumbsDropdown',
props: {
items: Array,
delimiterUrl: String

View file

@ -38,12 +38,12 @@
<script>
import NavigatorItem from './navigator_item.vue'
import Vue3DraggableResizable from 'vue3-draggable-resizable';
import NavigatorItem from './navigator_item.vue';
import axios from '../../packs/custom_axios.js';
import Vue3DraggableResizable from 'vue3-draggable-resizable'
export default {
name : 'NavigatorContainer',
name: 'NavigatorContainer',
components: {
NavigatorItem,
Vue3DraggableResizable
@ -58,7 +58,7 @@ export default {
currentItemId: null,
archived: null,
width: null
}
};
},
props: {
reloadCurrentLevel: Boolean,
@ -90,13 +90,13 @@ export default {
});
},
mounted() {
this.$refs.vueResizable.style.width = this.getNavigatorWidth()
this.$refs.vueResizable.style.width = this.getNavigatorWidth();
},
watch: {
archived() {
this.loadTree();
},
reloadCurrentLevel: function() {
reloadCurrentLevel() {
if (this.reloadCurrentLevel && (
this.currentItemId?.length === 0
|| this.menuItems.filter((item) => item.id === this.currentItemId)
@ -123,10 +123,10 @@ export default {
console.error('An error occurred while fetching the data', error);
}
},
onScrollY({target}) {
onScrollY({ target }) {
this.navigatorYScroll = target.scrollTop;
},
onScrollX({target}) {
onScrollX({ target }) {
this.navigatorXScroll = target.scrollLeft;
},
getNavigatorWidth() {
@ -135,7 +135,7 @@ export default {
},
onResizeMove(event) {
if (event.w > 400) event.w = 400;
document.documentElement.style.setProperty('--navigator-navigation-width', event.w + 'px');
document.documentElement.style.setProperty('--navigator-navigation-width', `${event.w}px`);
},
onResizeStart() {
document.body.style.cursor = 'url(/images/icon_small/Resize.svg) 0 0, auto';
@ -150,7 +150,7 @@ export default {
document.body.style.cursor = 'default';
$('.sci--layout-navigation-navigator').removeClass('!transition-none');
$('.sci--layout').removeClass('!transition-none');
this.changeNavigatorState(event.w)
this.changeNavigatorState(event.w);
},
async changeNavigatorState(newWidth) {
try {
@ -161,6 +161,6 @@ export default {
console.error('An error occurred while sending the request', error);
}
}
},
}
}
};
</script>

View file

@ -54,7 +54,7 @@ export default {
reloadCurrentLevel: Boolean,
reloadChildrenLevel: Boolean
},
data: function() {
data() {
return {
childrenExpanded: false,
childrenLoaded: false,
@ -62,13 +62,13 @@ export default {
};
},
computed: {
hasChildren: function() {
hasChildren() {
if (this.item.disabled) return false;
if (this.item.has_children) return true;
if (this.children && this.children.length > 0) return true;
return false
return false;
},
sortedMenuItems: function() {
sortedMenuItems() {
if (!this.children) return [];
return this.children.sort((a, b) => {
@ -81,44 +81,43 @@ export default {
return 0;
});
},
activeItem: function() {
activeItem() {
return this.item.id == this.currentItemId;
},
itemIcon: function() {
switch(this.item.type) {
itemIcon() {
switch (this.item.type) {
case 'folder':
return 'sn-icon mini sn-icon-mini-folder-left';
default:
return null;
}
},
itemToolTip: function() {
if (this.item.type == 'folder')
return this.item.name;
return this.i18n.t('sidebar.elements_tooltip', { type: this.i18n.t(`activerecord.models.${this.item.type}`), name: this.item.name});
itemToolTip() {
if (this.item.type == 'folder') return this.item.name;
return this.i18n.t('sidebar.elements_tooltip', { type: this.i18n.t(`activerecord.models.${this.item.type}`), name: this.item.name });
}
},
created: function() {
created() {
if (this.item.children) this.children = this.item.children;
},
mounted: function() {
mounted() {
this.selectItem();
},
watch: {
currentItemId: function() {
currentItemId() {
this.selectItem();
},
reloadChildrenLevel: function() {
reloadChildrenLevel() {
if (this.reloadChildrenLevel && this.item.id == this.currentItemId) {
this.loadChildren();
}
},
reloadCurrentLevel: function() {
reloadCurrentLevel() {
if (this.reloadCurrentLevel && this.children.find((item) => item.id == this.currentItemId)) {
this.loadChildren();
}
},
children: function() {
children() {
if (this.children && this.children.length > 0) {
this.childrenExpanded = true;
} else if (this.childrenLoaded) {
@ -127,21 +126,21 @@ export default {
}
},
methods: {
toggleChildren: function() {
toggleChildren() {
this.childrenExpanded = !this.childrenExpanded;
if (this.childrenExpanded) this.loadChildren();
},
loadChildren: function() {
$.get(this.item.children_url, {archived: this.archived}, (data) => {
loadChildren() {
$.get(this.item.children_url, { archived: this.archived }, (data) => {
this.children = data.items;
this.childrenLoaded = true;
});
},
treeExpand: function() {
treeExpand() {
this.childrenExpanded = true;
this.$emit('item:expand');
},
selectItem: function() {
selectItem() {
if (this.activeItem && !this.childrenExpanded) {
this.$emit('item:expand');
if (this.hasChildren) {
@ -150,6 +149,6 @@ export default {
}
}
}
},
}
}
};
</script>

View file

@ -26,7 +26,7 @@ export default {
},
computed: {
icon() {
switch(this.notification.attributes.type_of) {
switch (this.notification.attributes.type_of) {
case 'deliver':
return 'fas fa-truck';
case 'assignment':
@ -38,5 +38,5 @@ export default {
}
}
}
}
};
</script>

View file

@ -25,7 +25,7 @@
<script>
import NotificationItem from './notification_item.vue'
import NotificationItem from './notification_item.vue';
import axios from '../../../packs/custom_axios.js';
export default {
@ -43,20 +43,20 @@ export default {
nextPageUrl: null,
scrollBar: null,
loadingPage: false
}
};
},
created() {
this.nextPageUrl = this.notificationsUrl;
this.loadNotifications();
},
mounted() {
let container = this.$refs.scrollContainer.$el
const container = this.$refs.scrollContainer.$el;
container.addEventListener('ps-scroll-y', (e) => {
if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - 20) {
this.loadNotifications();
}
})
});
},
beforeUnmount() {
document.body.style.overflow = 'auto';
@ -66,10 +66,10 @@ export default {
this.loadNotifications();
},
todayNotifications() {
return this.notifications.filter(n => n.attributes.today);
return this.notifications.filter((n) => n.attributes.today);
},
olderNotifications() {
return this.notifications.filter(n => !n.attributes.today);
return this.notifications.filter((n) => !n.attributes.today);
}
},
methods: {
@ -79,18 +79,16 @@ export default {
this.loadingPage = true;
axios.get(this.nextPageUrl)
.then(response => {
.then((response) => {
this.notifications = this.notifications.concat(response.data.data);
this.nextPageUrl = response.data.links.next;
this.loadingPage = false;
this.$emit('update:unseenNotificationsCount');
})
.catch(error => {
.catch((error) => {
this.loadingPage = false;
});
}
}
}
};
</script>

View file

@ -58,123 +58,122 @@
</template>
<script>
import NotificationsFlyout from './notifications/notifications_flyout.vue';
import DropdownSelector from '../shared/legacy/dropdown_selector.vue';
import SelectDropdown from "../shared/select_dropdown.vue";
import MenuDropdown from '../shared/menu_dropdown.vue';
import NotificationsFlyout from './notifications/notifications_flyout.vue';
import DropdownSelector from '../shared/legacy/dropdown_selector.vue';
import Select from '../shared/select.vue';
import MenuDropdown from '../shared/menu_dropdown.vue';
export default {
name: 'TopMenuContainer',
components: {
DropdownSelector,
NotificationsFlyout,
MenuDropdown,
SelectDropdown
},
props: {
url: String,
notificationsUrl: String,
unseenNotificationsUrl: String
},
data() {
return {
rootUrl: null,
teamSwitchUrl: null,
currentTeam: null,
teams: null,
searchUrl: null,
user: null,
helpMenu: null,
settingsMenu: null,
userMenu: null,
notificationsOpened: false,
unseenNotificationsCount: 0
}
},
created() {
this.fetchData();
export default {
name: 'TopMenuContainer',
components: {
DropdownSelector,
NotificationsFlyout,
Select,
MenuDropdown
},
props: {
url: String,
notificationsUrl: String,
unseenNotificationsUrl: String
},
data() {
return {
rootUrl: null,
teamSwitchUrl: null,
currentTeam: null,
teams: null,
searchUrl: null,
user: null,
helpMenu: null,
settingsMenu: null,
userMenu: null,
notificationsOpened: false,
unseenNotificationsCount: 0
};
},
created() {
this.fetchData();
this.checkUnseenNotifications();
$(document).on('turbolinks:load', () => {
this.notificationsOpened = false;
this.checkUnseenNotifications();
this.refreshCurrentTeam();
});
$(document).on('turbolinks:load', () => {
this.notificationsOpened = false;
this.checkUnseenNotifications();
this.refreshCurrentTeam();
})
// Track name update in user profile settings
$(document).on('inlineEditing::updated', '.inline-editing-container[data-field-to-update="full_name"]', this.fetchData);
},
beforeUnmount: function(){
clearTimeout(this.unseenNotificationsTimeout);
},
computed: {
settingsMenuItems() {
return this.settingsMenu.map((item) => {
return { text: item.name, url: item.url } }
).concat(
{
text: this.i18n.t('left_menu_bar.support_links.core_version'),
modalTarget: '#aboutModal', url: ''
}
)
}
},
watch: {
notificationsOpened(newVal) {
if (newVal === true) {
document.body.style.overflow = 'hidden';
} else if (newVal === false) {
document.body.style.overflow = 'scroll';
}
}
},
methods: {
fetchData() {
$.get(this.url, (result) => {
this.rootUrl = result.root_url;
this.teamSwitchUrl = result.team_switch_url;
this.currentTeam = result.current_team;
this.teams = result.teams;
this.searchUrl = result.search_url;
this.helpMenu = result.help_menu;
this.settingsMenu = result.settings_menu;
this.userMenu = result.user_menu;
this.user = result.user;
})
},
switchTeam(team) {
if (this.currentTeam == team) return;
let newTeam = this.teams.find(e => e[0] == team);
if (!newTeam) return;
$.post(this.teamSwitchUrl, {team_id: team}, (result) => {
this.currentTeam = result.current_team
dropdownSelector.selectValues('#sciNavigationTeamSelector', this.currentTeam);
$('body').attr('data-current-team-id', this.currentTeam);
window.open(this.rootUrl, '_self')
}).fail((msg) => {
HelperModule.flashAlertMsg(msg.responseJSON.message, 'danger');
});
},
searchValue(e) {
window.open(`${this.searchUrl}?q=${e.target.value}`, '_self')
},
checkUnseenNotifications() {
clearTimeout(this.unseenNotificationsTimeout);
$.get(this.unseenNotificationsUrl, (result) => {
this.unseenNotificationsCount = result.unseen;
this.unseenNotificationsTimeout = setTimeout(this.checkUnseenNotifications, 30000);
})
},
refreshCurrentTeam() {
let newTeam = parseInt($('body').attr('data-current-team-id'));
if (newTeam !== this.currentTeam) {
this.currentTeam = newTeam;
dropdownSelector.selectValues('#sciNavigationTeamSelector', this.currentTeam);
// Track name update in user profile settings
$(document).on('inlineEditing::updated', '.inline-editing-container[data-field-to-update="full_name"]', this.fetchData);
},
beforeUnmount() {
clearTimeout(this.unseenNotificationsTimeout);
},
computed: {
settingsMenuItems() {
return this.settingsMenu.map((item) => ({ text: item.name, url: item.url })).concat(
{
text: this.i18n.t('left_menu_bar.support_links.core_version'),
modalTarget: '#aboutModal',
url: ''
}
);
}
},
watch: {
notificationsOpened(newVal) {
if (newVal === true) {
document.body.style.overflow = 'hidden';
} else if (newVal === false) {
document.body.style.overflow = 'scroll';
}
}
},
methods: {
fetchData() {
$.get(this.url, (result) => {
this.rootUrl = result.root_url;
this.teamSwitchUrl = result.team_switch_url;
this.currentTeam = result.current_team;
this.teams = result.teams;
this.searchUrl = result.search_url;
this.helpMenu = result.help_menu;
this.settingsMenu = result.settings_menu;
this.userMenu = result.user_menu;
this.user = result.user;
});
},
switchTeam(team) {
if (this.currentTeam == team) return;
const newTeam = this.teams.find((e) => e[0] == team);
if (!newTeam) return;
$.post(this.teamSwitchUrl, { team_id: team }, (result) => {
this.currentTeam = result.current_team;
dropdownSelector.selectValues('#sciNavigationTeamSelector', this.currentTeam);
$('body').attr('data-current-team-id', this.currentTeam);
window.open(this.rootUrl, '_self');
}).fail((msg) => {
HelperModule.flashAlertMsg(msg.responseJSON.message, 'danger');
});
},
searchValue(e) {
window.open(`${this.searchUrl}?q=${e.target.value}`, '_self');
},
checkUnseenNotifications() {
clearTimeout(this.unseenNotificationsTimeout);
$.get(this.unseenNotificationsUrl, (result) => {
this.unseenNotificationsCount = result.unseen;
this.unseenNotificationsTimeout = setTimeout(this.checkUnseenNotifications, 30000);
});
},
refreshCurrentTeam() {
const newTeam = parseInt($('body').attr('data-current-team-id'));
if (newTeam !== this.currentTeam) {
this.currentTeam = newTeam;
dropdownSelector.selectValues('#sciNavigationTeamSelector', this.currentTeam);
}
}
}
};
</script>

View file

@ -29,135 +29,135 @@
</div>
</template>
<script>
import axios from 'axios';
import { blobToBase64 } from '../shared/blobToBase64.js'
<script>
import axios from 'axios';
import { blobToBase64 } from '../shared/blobToBase64.js';
export default {
name: 'OpenVectorEditor',
props: {
fileUrl: { type: String },
fileName: { type: String },
updateUrl: { type: String },
canEditFile: { type: String },
oveWarning: { type: String }
},
data() {
return {
editor: null,
sequenceName: null,
closeAfterSave: false,
readOnly: this.canEditFile !== 'true'
export default {
name: 'OpenVectorEditor',
props: {
fileUrl: { type: String },
fileName: { type: String },
updateUrl: { type: String },
canEditFile: { type: String },
oveWarning: { type: String }
},
data() {
return {
editor: null,
sequenceName: null,
closeAfterSave: false,
readOnly: this.canEditFile !== 'true'
};
},
mounted() {
let editorConfig = {
onSave: this.saveFile,
generatePng: true,
showMenuBar: true,
alwaysAllowSave: true,
menuFilter: this.menuFilter,
beforeReadOnlyChange: this.readOnlyHandler,
showCircularity: true,
ToolBarProps: {
toolList: [
'saveTool',
'downloadTool',
{ name: 'importTool', tooltip: I18n.t('open_vector_editor.editor.tooltips.importTool'), disabled: this.readOnly },
'undoTool',
'redoTool',
'cutsiteTool',
'featureTool',
'partTool',
'oligoTool',
'orfTool',
// Hide allignment tool
// 'alignmentTool',
'editTool',
'findTool',
'visibilityTool'
]
}
},
mounted() {
let editorConfig = {
onSave: this.saveFile,
generatePng: true,
showMenuBar: true,
alwaysAllowSave: true,
menuFilter: this.menuFilter,
beforeReadOnlyChange: this.readOnlyHandler,
showCircularity: true,
};
if (this.readOnly) {
editorConfig = {
...editorConfig,
showReadOnly: false,
ToolBarProps: {
toolList: [
'saveTool',
'downloadTool',
{ name: 'importTool', tooltip: I18n.t('open_vector_editor.editor.tooltips.importTool'), disabled: this.readOnly },
'undoTool',
'redoTool',
'cutsiteTool',
'featureTool',
'partTool',
'oligoTool',
'orfTool',
// Hide allignment tool
// 'alignmentTool',
'editTool',
'findTool',
'visibilityTool'
]
toolList: []
}
}
if (this.readOnly) {
editorConfig = {
...editorConfig,
showReadOnly: false,
ToolBarProps: {
toolList: []
}
}
}
this.editor = window.createVectorEditor(this.$refs.container, editorConfig);
this.sequenceName = this.fileName;
if (this.fileUrl) {
this.loadFile();
} else {
this.editor.updateEditor(
{
sequenceData: { circular: true, name: this.sequenceName },
readOnly: this.readOnly
}
);
}
this.$nextTick(this.attachEditorHelpCallback);
},
methods: {
loadFile() {
fetch(this.fileUrl).then((response) => response.json()).then(
(json) => this.editor.updateEditor(
{ sequenceData: json, readOnly: this.readOnly }
)
);
},
saveAndClose() {
this.closeAfterSave = true;
document.querySelector('[data-test=saveTool]').click()
},
saveFile(opts, sequenceDataToSave) {
if (this.readOnly) return;
blobToBase64(opts.pngFile).then((base64image) => {
(this.fileUrl ? axios.patch : axios.post)(
this.updateUrl,
{
sequence_data: sequenceDataToSave,
sequence_name: this.sequenceName || this.i18n.t('open_vector_editor.default_sequence_name'),
base64_image: base64image
}
).then(() => {
parent.document.getElementById('iFrameModal').dispatchEvent(new Event('sequenceSaved'));
if (this.closeAfterSave) this.closeModal();
});
});
},
closeModal() {
if (parent !== window) {
parent.document.getElementById('iFrameModal').dispatchEvent(new Event('hide'));
}
},
menuFilter(menus) {
return menus.map(menu => {
if(menu.text !== 'Help') return menu;
const menuOverride = {
...menu,
submenu: menu.submenu.filter(item => item.cmd !== 'versionNumber')
}
return menuOverride;
});
},
readOnlyHandler(val) { this.readOnly = val; return true },
// override click event for github issue link in editor help -> about modal
attachEditorHelpCallback() {
$(window.document).on('click', '.bp3-dialog-container .bp3-dialog .bp3-alert-contents a', function(event) {
event.preventDefault();
$('.bp3-dialog .bp3-dialog-footer button[type*=submit]').trigger('click');
window.open($(event.target).attr('href'), '_blank');
})
}
};
}
}
</script>
this.editor = window.createVectorEditor(this.$refs.container, editorConfig);
this.sequenceName = this.fileName;
if (this.fileUrl) {
this.loadFile();
} else {
this.editor.updateEditor(
{
sequenceData: { circular: true, name: this.sequenceName },
readOnly: this.readOnly
}
);
}
this.$nextTick(this.attachEditorHelpCallback);
},
methods: {
loadFile() {
fetch(this.fileUrl).then((response) => response.json()).then(
(json) => this.editor.updateEditor(
{ sequenceData: json, readOnly: this.readOnly }
)
);
},
saveAndClose() {
this.closeAfterSave = true;
document.querySelector('[data-test=saveTool]').click();
},
saveFile(opts, sequenceDataToSave) {
if (this.readOnly) return;
blobToBase64(opts.pngFile).then((base64image) => {
(this.fileUrl ? axios.patch : axios.post)(
this.updateUrl,
{
sequence_data: sequenceDataToSave,
sequence_name: this.sequenceName || this.i18n.t('open_vector_editor.default_sequence_name'),
base64_image: base64image
}
).then(() => {
parent.document.getElementById('iFrameModal').dispatchEvent(new Event('sequenceSaved'));
if (this.closeAfterSave) this.closeModal();
});
});
},
closeModal() {
if (parent !== window) {
parent.document.getElementById('iFrameModal').dispatchEvent(new Event('hide'));
}
},
menuFilter(menus) {
return menus.map((menu) => {
if (menu.text !== 'Help') return menu;
const menuOverride = {
...menu,
submenu: menu.submenu.filter((item) => item.cmd !== 'versionNumber')
};
return menuOverride;
});
},
readOnlyHandler(val) { this.readOnly = val; return true; },
// override click event for github issue link in editor help -> about modal
attachEditorHelpCallback() {
$(window.document).on('click', '.bp3-dialog-container .bp3-dialog .bp3-alert-contents a', (event) => {
event.preventDefault();
$('.bp3-dialog .bp3-dialog-footer button[type*=submit]').trigger('click');
window.open($(event.target).attr('href'), '_blank');
});
}
}
};
</script>

View file

@ -224,243 +224,244 @@
</div>
</template>
<script>
import InlineEdit from '../shared/inline_edit.vue'
import Step from './step'
import ProtocolMetadata from './protocolMetadata'
import ProtocolOptions from './protocolOptions'
import Tinymce from '../shared/tinymce.vue'
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
import PublishProtocol from './modals/publish_protocol.vue'
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue'
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js'
<script>
import InlineEdit from '../shared/inline_edit.vue';
import Step from './step';
import ProtocolMetadata from './protocolMetadata';
import ProtocolOptions from './protocolOptions';
import Tinymce from '../shared/tinymce.vue';
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue';
import PublishProtocol from './modals/publish_protocol.vue';
import clipboardPasteModal from '../shared/content/attachments/clipboard_paste_modal.vue';
import AssetPasteMixin from '../shared/content/attachments/mixins/paste.js';
import UtilsMixin from '../mixins/utils.js'
import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
import moduleNameObserver from '../mixins/moduleNameObserver';
import UtilsMixin from '../mixins/utils.js';
import stackableHeadersMixin from '../mixins/stackableHeadersMixin';
import moduleNameObserver from '../mixins/moduleNameObserver';
export default {
name: 'ProtocolContainer',
props: {
protocolUrl: {
type: String,
required: true
}
export default {
name: 'ProtocolContainer',
props: {
protocolUrl: {
type: String,
required: true
}
},
components: {
Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol, clipboardPasteModal
},
mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
computed: {
inRepository() {
return this.protocol.attributes.in_repository;
},
components: { Step, InlineEdit, ProtocolOptions, Tinymce, ReorderableItemsModal, ProtocolMetadata, PublishProtocol, clipboardPasteModal},
mixins: [UtilsMixin, stackableHeadersMixin, moduleNameObserver, AssetPasteMixin],
computed: {
inRepository() {
return this.protocol.attributes.in_repository
},
linked() {
return this.protocol.attributes.linked;
},
urls() {
return this.protocol.attributes.urls || {}
},
linked() {
return this.protocol.attributes.linked;
},
data() {
return {
protocol: {
attributes: {}
},
steps: [],
reordering: false,
publishing: false,
stepToReload: null,
activeDragStep: null
}
urls() {
return this.protocol.attributes.urls || {};
}
},
data() {
return {
protocol: {
attributes: {}
},
steps: [],
reordering: false,
publishing: false,
stepToReload: null,
activeDragStep: null
};
},
mounted() {
$.get(this.protocolUrl, (result) => {
this.protocol = result.data;
this.$nextTick(() => {
this.refreshProtocolStatus();
if (!this.inRepository) {
window.addEventListener('scroll', this.initStackableHeaders, false);
this.initStackableHeaders();
}
});
$.get(this.urls.steps_url, (result) => {
this.steps = result.data;
});
});
},
beforeUnmount() {
if (!this.inRepository) {
window.removeEventListener('scroll', this.initStackableHeaders, false);
}
},
methods: {
getHeader() {
return this.$refs.header;
},
mounted() {
$.get(this.protocolUrl, (result) => {
this.protocol = result.data;
this.$nextTick(() => {
this.refreshProtocolStatus();
if (!this.inRepository) {
window.addEventListener('scroll', this.initStackableHeaders, false);
this.initStackableHeaders();
}
});
$.get(this.urls.steps_url, (result) => {
this.steps = result.data
})
reloadStep(step) {
this.stepToReload = step;
},
collapseSteps() {
$('.step-container .collapse').collapse('hide');
},
expandSteps() {
$('.step-container .collapse').collapse('show');
},
deleteSteps() {
$.post(this.urls.delete_steps_url, () => {
this.steps = [];
}).fail(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
});
},
beforeUnmount() {
if (!this.inRepository) {
window.removeEventListener('scroll', this.initStackableHeaders, false);
}
refreshProtocolStatus() {
if (this.inRepository) return;
// legacy method from app/assets/javascripts/my_modules/protocols.js
refreshProtocolStatusBar();
// Update protocol options drowpdown for linked tasks
this.refreshProtocolDropdownOptions();
},
methods: {
getHeader() {
return this.$refs.header;
},
reloadStep(step) {
this.stepToReload = step;
},
collapseSteps() {
$('.step-container .collapse').collapse('hide')
},
expandSteps() {
$('.step-container .collapse').collapse('show')
},
deleteSteps() {
$.post(this.urls.delete_steps_url, () => {
this.steps = []
}).fail(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger')
})
},
refreshProtocolStatus() {
if (this.inRepository) return
// legacy method from app/assets/javascripts/my_modules/protocols.js
refreshProtocolStatusBar();
refreshProtocolDropdownOptions() {
if (!this.linked && this.inRepository) return;
// Update protocol options drowpdown for linked tasks
this.refreshProtocolDropdownOptions();
},
refreshProtocolDropdownOptions() {
if (!this.linked && this.inRepository) return
$.get(this.protocolUrl, (result) => {
this.protocol.attributes.urls = result.data.attributes.urls;
});
},
updateProtocol(attributes) {
this.protocol.attributes = attributes
},
updateName(newName) {
this.protocol.attributes.name = newName;
$.ajax({
type: 'PATCH',
url: this.urls.update_protocol_name_url,
data: { protocol: { name: newName } },
success: () => {
this.refreshProtocolStatus();
}
});
},
updateDescription(protocol) {
this.protocol.attributes = protocol.attributes
this.refreshProtocolStatus();
},
addStep(position) {
$.post(this.urls.add_step_url, {position: position}, (result) => {
result.data.newStep = true
this.updateStepsPosition(result.data);
// scroll to bottom if step was appended at the end
if(position === this.steps.length - 1) {
this.$nextTick(() => this.scrollToBottom());
}
$.get(this.protocolUrl, (result) => {
this.protocol.attributes.urls = result.data.attributes.urls;
});
},
updateProtocol(attributes) {
this.protocol.attributes = attributes;
},
updateName(newName) {
this.protocol.attributes.name = newName;
$.ajax({
type: 'PATCH',
url: this.urls.update_protocol_name_url,
data: { protocol: { name: newName } },
success: () => {
this.refreshProtocolStatus();
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.error ? Object.values(data.responseJSON.error).join(', ') : I18n.t('errors.general'), 'danger');
})
},
updateStepsPosition(step, action = 'add') {
let position = step.attributes.position;
if (action === 'delete') {
this.steps.splice(position, 1)
}
let unordered_steps = this.steps.map( s => {
if (s.attributes.position >= position) {
if (action === 'add') {
s.attributes.position += 1;
} else {
s.attributes.position -= 1;
}
}
return s;
})
if (action === 'add') {
unordered_steps.push(step);
});
},
updateDescription(protocol) {
this.protocol.attributes = protocol.attributes;
this.refreshProtocolStatus();
},
addStep(position) {
$.post(this.urls.add_step_url, { position }, (result) => {
result.data.newStep = true;
this.updateStepsPosition(result.data);
// scroll to bottom if step was appended at the end
if (position === this.steps.length - 1) {
this.$nextTick(() => this.scrollToBottom());
}
this.reorderSteps(unordered_steps)
},
updateStep(attributes) {
this.steps[attributes.position].attributes = {
...this.steps[attributes.position].attributes,
...attributes
};
this.refreshProtocolStatus();
},
reorderSteps(steps) {
this.steps = steps.sort((a, b) => a.attributes.position - b.attributes.position);
this.refreshProtocolStatus();
},
updateStepOrder(orderedSteps) {
orderedSteps.forEach((step, position) => {
let index = this.steps.findIndex((e) => e.id === step.id);
this.steps[index].attributes.position = position;
});
let stepPositions =
{
step_positions: this.steps.map(
(step) => [step.id, step.attributes.position]
)
};
$.ajax({
type: "POST",
url: this.protocol.attributes.urls.reorder_steps_url,
data: JSON.stringify(stepPositions),
contentType: "application/json",
dataType: "json",
error: (() => HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger')),
success: (() => this.reorderSteps(this.steps))
});
},
startStepReorder() {
this.reordering = true;
},
closeStepReorderModal() {
this.reordering = false;
},
startPublish() {
$.ajax({
type: "GET",
url: this.urls.version_comment_url,
contentType: "application/json",
dataType: "json",
success: (result) => {
this.protocol.attributes.version_comment = result.version_comment;
this.publishing = true;
}
});
},
closePublishModal() {
this.publishing = false;
},
scrollToBottom() {
window.scrollTo(0, document.body.scrollHeight);
},
publishProtocol(comment) {
this.protocol.attributes.version_comment = comment;
$.post(this.urls.publish_url, {version_comment: comment, view: 'show'})
},
scrollTop() {
window.scrollTo(0, 0);
setTimeout(() => {
$('.my_module-name .view-mode').trigger('click');
$('.my_module-name .input-field').focus();
}, 300)
},
dragEnter(id) {
this.activeDragStep = id;
},
uploadFilesToStep(file, stepId) {
this.$refs.steps.find(child => child.step?.id == stepId).uploadFiles(file);
},
firstObjectInViewport() {
let step = $('.step-container:not(.locked)').toArray().find(element => {
const { top, bottom } = element.getBoundingClientRect()
return bottom > 0 && top < window.innerHeight
})
return step ? step.dataset.id : null
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.error ? Object.values(data.responseJSON.error).join(', ') : I18n.t('errors.general'), 'danger');
});
},
updateStepsPosition(step, action = 'add') {
const { position } = step.attributes;
if (action === 'delete') {
this.steps.splice(position, 1);
}
const unordered_steps = this.steps.map((s) => {
if (s.attributes.position >= position) {
if (action === 'add') {
s.attributes.position += 1;
} else {
s.attributes.position -= 1;
}
}
return s;
});
if (action === 'add') {
unordered_steps.push(step);
}
this.reorderSteps(unordered_steps);
},
updateStep(attributes) {
this.steps[attributes.position].attributes = {
...this.steps[attributes.position].attributes,
...attributes
};
this.refreshProtocolStatus();
},
reorderSteps(steps) {
this.steps = steps.sort((a, b) => a.attributes.position - b.attributes.position);
this.refreshProtocolStatus();
},
updateStepOrder(orderedSteps) {
orderedSteps.forEach((step, position) => {
const index = this.steps.findIndex((e) => e.id === step.id);
this.steps[index].attributes.position = position;
});
const stepPositions = {
step_positions: this.steps.map(
(step) => [step.id, step.attributes.position]
)
};
$.ajax({
type: 'POST',
url: this.protocol.attributes.urls.reorder_steps_url,
data: JSON.stringify(stepPositions),
contentType: 'application/json',
dataType: 'json',
error: (() => HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger')),
success: (() => this.reorderSteps(this.steps))
});
},
startStepReorder() {
this.reordering = true;
},
closeStepReorderModal() {
this.reordering = false;
},
startPublish() {
$.ajax({
type: 'GET',
url: this.urls.version_comment_url,
contentType: 'application/json',
dataType: 'json',
success: (result) => {
this.protocol.attributes.version_comment = result.version_comment;
this.publishing = true;
}
});
},
closePublishModal() {
this.publishing = false;
},
scrollToBottom() {
window.scrollTo(0, document.body.scrollHeight);
},
publishProtocol(comment) {
this.protocol.attributes.version_comment = comment;
$.post(this.urls.publish_url, { version_comment: comment, view: 'show' });
},
scrollTop() {
window.scrollTo(0, 0);
setTimeout(() => {
$('.my_module-name .view-mode').trigger('click');
$('.my_module-name .input-field').focus();
}, 300);
},
dragEnter(id) {
this.activeDragStep = id;
},
uploadFilesToStep(file, stepId) {
this.$refs.steps.find((child) => child.step?.id == stepId).uploadFiles(file);
},
firstObjectInViewport() {
const step = $('.step-container:not(.locked)').toArray().find((element) => {
const { top, bottom } = element.getBoundingClientRect();
return bottom > 0 && top < window.innerHeight;
});
return step ? step.dataset.id : null;
}
}
</script>
};
</script>

View file

@ -21,23 +21,23 @@
</div>
</div>
</template>
<script>
export default {
name: 'deleteStepModal',
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
<script>
export default {
name: 'deleteStepModal',
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
},
methods: {
confirm() {
$(this.$refs.modal).modal('hide');
this.$emit('confirm');
},
methods: {
confirm() {
$(this.$refs.modal).modal('hide');
this.$emit('confirm');
},
cancel() {
$(this.$refs.modal).modal('hide');
}
cancel() {
$(this.$refs.modal).modal('hide');
}
}
};
</script>

View file

@ -20,26 +20,26 @@
</div>
</div>
</template>
<script>
export default {
name: 'deleteStepsModal',
mounted() {
// move modal to body to avoid z-index issues
$('body').append($(this.$refs.modal));
<script>
export default {
name: 'deleteStepsModal',
mounted() {
// move modal to body to avoid z-index issues
$('body').append($(this.$refs.modal));
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('close');
});
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('close');
});
},
methods: {
confirm() {
$(this.$refs.modal).modal('hide');
this.$emit('confirm');
},
methods: {
confirm() {
$(this.$refs.modal).modal('hide');
this.$emit('confirm');
},
cancel() {
$(this.$refs.modal).modal('hide');
}
cancel() {
$(this.$refs.modal).modal('hide');
}
}
};
</script>

View file

@ -30,30 +30,30 @@
</div>
</div>
</template>
<script>
export default {
name: 'publishProtocol',
props: {
protocol: {
type: Object,
required: true
}
<script>
export default {
name: 'publishProtocol',
props: {
protocol: {
type: Object,
required: true
}
},
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
$(this.$refs.textarea).focus();
},
methods: {
confirm() {
$(this.$refs.modal).modal('hide');
this.$emit('publish', this.protocol.attributes.version_comment);
},
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
$(this.$refs.textarea).focus();
},
methods: {
confirm() {
$(this.$refs.modal).modal('hide');
this.$emit('publish', this.protocol.attributes.version_comment);
},
cancel() {
$(this.$refs.modal).modal('hide');
}
cancel() {
$(this.$refs.modal).modal('hide');
}
}
};
</script>

View file

@ -98,22 +98,22 @@
</div>
</template>
<script>
import DeleteStepsModals from './modals/delete_steps'
<script>
import DeleteStepsModals from './modals/delete_steps';
export default {
name: "ProtocolOptions",
name: 'ProtocolOptions',
components: { DeleteStepsModals },
data() {
return {
stepsDeleting: false
}
};
},
props: {
protocol: {
type: Object,
required: true,
required: true
},
canDeleteSteps: {
type: Boolean,
@ -135,29 +135,29 @@ export default {
},
loadProtocol() {
$.get(
this.protocol.attributes.urls.load_from_repo_url + "?type=recent"
`${this.protocol.attributes.urls.load_from_repo_url}?type=recent`
).done((data) => {
$(this.$refs.loadProtocol).trigger("ajax:success", data);
$(this.$refs.loadProtocol).trigger('ajax:success', data);
});
},
unlinkProtocol() {
$.get(this.protocol.attributes.urls.unlink_url).done((data) => {
$(this.$refs.unlinkProtocol).trigger("ajax:success", data);
$(this.$refs.unlinkProtocol).trigger('ajax:success', data);
});
},
updateProtocol() {
$.get(this.protocol.attributes.urls.update_protocol_url).done((data) => {
$(this.$refs.updateProtocol).trigger("ajax:success", data);
$(this.$refs.updateProtocol).trigger('ajax:success', data);
});
},
revertProtocol() {
$.get(this.protocol.attributes.urls.revert_protocol_url).done((data) => {
$(this.$refs.revertProtocol).trigger("ajax:success", data);
$(this.$refs.revertProtocol).trigger('ajax:success', data);
});
},
deleteSteps() {
this.$emit('protocol:delete_steps')
this.$emit('protocol:delete_steps');
}
},
}
};
</script>

View file

@ -77,88 +77,88 @@
</div>
</div>
</template>
<script>
import StorageUsage from '../storage_usage.vue'
<script>
import StorageUsage from '../storage_usage.vue';
import WopiFileModal from './mixins/wopi_file_modal.js'
import WopiFileModal from './mixins/wopi_file_modal.js';
export default {
name: 'fileModal',
props: {
step: Object
},
data() {
return {
dragingFile: false,
attachmentsChanged: false
export default {
name: 'fileModal',
props: {
step: Object
},
data() {
return {
dragingFile: false,
attachmentsChanged: false
};
},
components: { StorageUsage },
mixins: [WopiFileModal],
mounted() {
$(this.$refs.modal).modal('show');
MarvinJsEditor.initNewButton('.add-file-modal .new-marvinjs-upload-button', () => {
this.attachmentsChanged = true;
$(this.$refs.modal).modal('hide');
});
$(this.$refs.modal).on('hidden.bs.modal', () => {
global.removeEventListener('paste', this.onImageFilePaste, false);
if (this.attachmentsChanged) {
this.$emit('attachmentsChanged');
} else {
this.cancel();
}
});
global.addEventListener('paste', this.onImageFilePaste, false);
},
methods: {
cancel() {
$(this.$refs.modal).modal('hide');
this.$nextTick(() => this.$emit('cancel'));
},
components: {StorageUsage},
mixins: [WopiFileModal],
mounted() {
$(this.$refs.modal).modal('show');
MarvinJsEditor.initNewButton('.add-file-modal .new-marvinjs-upload-button', () => {
this.attachmentsChanged = true;
$(this.$refs.modal).modal('hide');
});
$(this.$refs.modal).on('hidden.bs.modal', () => {
global.removeEventListener('paste', this.onImageFilePaste, false);
if (this.attachmentsChanged) {
this.$emit('attachmentsChanged');
} else {
this.cancel();
}
});
global.addEventListener('paste', this.onImageFilePaste, false);
},
methods: {
cancel() {
$(this.$refs.modal).modal('hide');
this.$nextTick(() => this.$emit('cancel'));
},
onImageFilePaste (pasteEvent) {
if (pasteEvent.clipboardData !== false) {
let items = pasteEvent.clipboardData.items
for (let i = 0; i < items.length; i += 1) {
if (items[i].type.indexOf('image') !== -1) {
this.$emit('copyPasteImageModal', items[i]);
$(this.$refs.modal).modal('hide');
return
}
}
}
},
dropFile(e) {
e.stopPropagation();
if (e.dataTransfer && e.dataTransfer.files.length) {
this.$emit('files', e.dataTransfer.files);
$(this.$refs.modal).modal('hide');
}
},
uploadFiles() {
this.$emit('files', this.$refs.fileSelector.files);
$(this.$refs.modal).modal('hide');
this.$parent.$parent.$nextTick(() => {
this.$parent.$el.scrollIntoView(false)
});
},
openMarvinJsModal() {
},
openWopiFileModal() {
this.initWopiFileModal(this.step, (_e, data, status) => {
if (status === 'success') {
onImageFilePaste(pasteEvent) {
if (pasteEvent.clipboardData !== false) {
const { items } = pasteEvent.clipboardData;
for (let i = 0; i < items.length; i += 1) {
if (items[i].type.indexOf('image') !== -1) {
this.$emit('copyPasteImageModal', items[i]);
$(this.$refs.modal).modal('hide');
this.$emit('attachmentUploaded', data);
} else {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
return;
}
});
},
openOVEditor() {
$(this.$refs.modal).modal('hide');
window.showIFrameModal(this.step.attributes.open_vector_editor_context.new_sequence_asset_url);
}
}
},
dropFile(e) {
e.stopPropagation();
if (e.dataTransfer && e.dataTransfer.files.length) {
this.$emit('files', e.dataTransfer.files);
$(this.$refs.modal).modal('hide');
}
},
uploadFiles() {
this.$emit('files', this.$refs.fileSelector.files);
$(this.$refs.modal).modal('hide');
this.$parent.$parent.$nextTick(() => {
this.$parent.$el.scrollIntoView(false);
});
},
openMarvinJsModal() {
},
openWopiFileModal() {
this.initWopiFileModal(this.step, (_e, data, status) => {
if (status === 'success') {
$(this.$refs.modal).modal('hide');
this.$emit('attachmentUploaded', data);
} else {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
}
});
},
openOVEditor() {
$(this.$refs.modal).modal('hide');
window.showIFrameModal(this.step.attributes.open_vector_editor_context.new_sequence_asset_url);
}
}
};
</script>

View file

@ -44,21 +44,22 @@
</template>
<script>
import ContextMenuMixin from './mixins/context_menu.js'
import ContextMenu from './context_menu.vue'
export default {
name: 'thumbnailAttachment',
mixins: [ContextMenuMixin],
components: { ContextMenu },
props: {
attachment: {
type: Object,
required: true
},
stepId: {
type: Number,
required: true
}
import ContextMenuMixin from './mixins/context_menu.js';
import ContextMenu from './context_menu.vue';
export default {
name: 'thumbnailAttachment',
mixins: [ContextMenuMixin],
components: { ContextMenu },
props: {
attachment: {
type: Object,
required: true
},
stepId: {
type: Number,
required: true
}
}
};
</script>

View file

@ -28,82 +28,84 @@
</template>
<script>
export default {
name: 'ProtocolFileImportModal',
props: {
importUrl: { type: String, required: true },
protocolTemplateTableUrl: { type: String }
export default {
name: 'ProtocolFileImportModal',
props: {
importUrl: { type: String, required: true },
protocolTemplateTableUrl: { type: String }
},
data() {
return {
state: 'confirm',
files: null,
jobPollInterval: null,
pollCount: 0,
jobId: null,
refreshCallback: null
};
},
mounted() {
window.protocolFileImportModal = this;
},
methods: {
open() {
$('.protocol-import-dropdown-button').dropdown('toggle');
$(this.$refs.modal).modal('show');
},
data() {
return {
state: "confirm",
files: null,
jobPollInterval: null,
pollCount: 0,
jobId: null,
refreshCallback: null
close() {
if (this.state === 'done' && this.refreshCallback) {
this.refreshCallback();
}
$(this.$refs.modal).modal('hide');
},
mounted() {
window.protocolFileImportModal = this;
init(files, refreshCallback) {
this.refreshCallback = refreshCallback;
this.pollCount = 0;
this.jobId = null;
this.files = files;
this.state = 'confirm';
this.open();
},
methods: {
open() {
$('.protocol-import-dropdown-button').dropdown('toggle');
$(this.$refs.modal).modal('show');
},
close() {
if (this.state === "done" && this.refreshCallback) {
this.refreshCallback();
}
$(this.$refs.modal).modal('hide');
},
init(files, refreshCallback) {
this.refreshCallback = refreshCallback;
this.pollCount = 0;
this.jobId = null;
this.files = files;
this.state = "confirm";
this.open();
},
confirm() {
let formData = new FormData();
Array.from(this.files).forEach(file => formData.append('files[]', file, file.name));
confirm() {
const formData = new FormData();
Array.from(this.files).forEach((file) => formData.append('files[]', file, file.name));
$.post({ url: this.importUrl, data: formData, processData: false, contentType: false }, (data) => {
this.state = 'in_progress';
this.jobId = data.job_id
this.jobPollInterval = setInterval(this.fetchJobStatus, 1000);
});
},
fetchJobStatus() {
this.pollCount += 1;
$.post({
url: this.importUrl, data: formData, processData: false, contentType: false
}, (data) => {
this.state = 'in_progress';
this.jobId = data.job_id;
this.jobPollInterval = setInterval(this.fetchJobStatus, 1000);
});
},
fetchJobStatus() {
this.pollCount += 1;
if (this.pollCount > 4) {
this.state = 'not_yet_done';
clearInterval(this.jobPollInterval);
return;
}
$.get(`/jobs/${this.jobId}/status`, (data) => {
let status = data.status;
switch (status) {
case 'pending':
break;
case 'running':
break;
case 'done':
this.state = 'done';
clearInterval(this.jobPollInterval);
break;
case 'failed':
this.state = 'failed';
clearInterval(this.jobPollInterval);
break;
}
});
if (this.pollCount > 4) {
this.state = 'not_yet_done';
clearInterval(this.jobPollInterval);
return;
}
$.get(`/jobs/${this.jobId}/status`, (data) => {
const { status } = data;
switch (status) {
case 'pending':
break;
case 'running':
break;
case 'done':
this.state = 'done';
clearInterval(this.jobPollInterval);
break;
case 'failed':
this.state = 'failed';
clearInterval(this.jobPollInterval);
break;
}
});
}
}
};
</script>

View file

@ -5,16 +5,15 @@
</template>
<script>
export default {
name: 'ColumnElement',
props: {
column: Object
},
methods: {
addFilter() {
this.$emit('columns:addFilter', this.column)
},
export default {
name: 'ColumnElement',
props: {
column: Object
},
methods: {
addFilter() {
this.$emit('columns:addFilter', this.column);
}
}
};
</script>

View file

@ -55,106 +55,109 @@
</div>
</template>
<script>
import ColumnElement from './column.vue'
import FiltersList from './filters_list.vue'
import SavedFilterElement from './saved_filter.vue'
<script>
import ColumnElement from './column.vue';
import FiltersList from './filters_list.vue';
import SavedFilterElement from './saved_filter.vue';
export default {
name: 'FilterContainer',
props: {
defaultFilters: Array,
my_modules: Array,
container: Object,
columns: Array,
savedFilters: Array,
canManageFilters: Boolean,
filterName: String, default: () => null
export default {
name: 'FilterContainer',
props: {
defaultFilters: Array,
my_modules: Array,
container: Object,
columns: Array,
savedFilters: Array,
canManageFilters: Boolean,
filterName: String,
default: () => null
},
data() {
return {
filters: this.defaultFilters,
savedFilterScrollbar: null,
filterListKey: true
};
},
components: { ColumnElement, FiltersList, SavedFilterElement },
methods: {
addFilter(column) {
const id = this.filters.length ? this.filters[this.filters.length - 1].id + 1 : 1;
this.filters.push({
id, column, isBlank: true, data: {}
});
},
data() {
return {
filters: this.defaultFilters,
savedFilterScrollbar: null,
filterListKey: true
}
updateFilter(filter) {
const index = this.filters.findIndex((f) => f.id === filter.id);
this.filters[index].data = filter.data;
this.filters[index].isBlank = filter.isBlank;
this.$emit('filters:update', this.filters);
},
components: { ColumnElement, FiltersList, SavedFilterElement },
methods: {
addFilter(column) {
const id = this.filters.length ? this.filters[this.filters.length - 1].id + 1 : 1
this.filters.push({ id: id, column: column, isBlank: true, data: {} });
},
updateFilter(filter) {
const index = this.filters.findIndex((f) => f.id === filter.id);
this.filters[index].data = filter.data;
this.filters[index].isBlank = filter.isBlank;
this.$emit("filters:update", this.filters);
},
clearFilters() {
this.$emit('filters:clear');
this.filterListKey = !this.filterListKey;
},
deleteFilter(index) {
this.filters.splice(index, 1);
},
closeDropdowns() {
this.closeColumnsFilters();
this.closeSavedFilters();
},
closeColumnsFilters() {
$('#filtersColumnsDropdown').removeClass('open');
},
toggleColumnsFilters(e) {
e.stopPropagation();
$('.filters-columns-list').scrollTop(0);
this.closeSavedFilters();
$('#filtersColumnsDropdown').toggleClass('open');
},
loadFilters(filterUrl) {
this.filters = [];
$.get(filterUrl, (data) => {
let filters = [];
let rawFilters = data.data.attributes.default_columns.concat((data.included || []).map(f => f.attributes))
let id = 0;
$.each(rawFilters, (i, f) => {
filters.push({
id: id,
column: this.columns.find(c => c.id == f.repository_column_id),
isBlank: false,
data: {
operator: f.operator,
parameters: f.parameters
}
});
id++;
})
this.$emit('filters:update-current-name', data.data.attributes.name);
this.filters = filters;
// set up save modal
let $saveFiltersModal = $('#modalSaveRepositoryTableFilter');
let $overwriteLink = $('#overwriteFilterLink');
$overwriteLink.removeClass('hidden');
$saveFiltersModal.data('repositoryTableFilterId', data.data.id);
$('#currentFilterName').html(data.data.attributes.name);
$saveFiltersModal.data('repositoryTableFilterName', data.data.attributes.name);
clearFilters() {
this.$emit('filters:clear');
this.filterListKey = !this.filterListKey;
},
deleteFilter(index) {
this.filters.splice(index, 1);
},
closeDropdowns() {
this.closeColumnsFilters();
this.closeSavedFilters();
},
closeColumnsFilters() {
$('#filtersColumnsDropdown').removeClass('open');
},
toggleColumnsFilters(e) {
e.stopPropagation();
$('.filters-columns-list').scrollTop(0);
this.closeSavedFilters();
$('#filtersColumnsDropdown').toggleClass('open');
},
loadFilters(filterUrl) {
this.filters = [];
$.get(filterUrl, (data) => {
const filters = [];
const rawFilters = data.data.attributes.default_columns.concat((data.included || []).map((f) => f.attributes));
let id = 0;
$.each(rawFilters, (i, f) => {
filters.push({
id,
column: this.columns.find((c) => c.id == f.repository_column_id),
isBlank: false,
data: {
operator: f.operator,
parameters: f.parameters
}
});
id++;
});
},
closeSavedFilters() {
$('#savedFiltersContainer').removeClass('open');
return true;
},
toggleSavedFilters(e) {
e.stopPropagation();
$('.saved-filters-list').scrollTop(0);
if (this.savedFilterScrollbar) {
this.savedFilterScrollbar.update();
} else {
this.savedFilterScrollbar = new PerfectScrollbar($('.saved-filters-list')[0]);
}
this.closeColumnsFilters();
$('#savedFiltersContainer').toggleClass('open');
},
this.$emit('filters:update-current-name', data.data.attributes.name);
this.filters = filters;
// set up save modal
const $saveFiltersModal = $('#modalSaveRepositoryTableFilter');
const $overwriteLink = $('#overwriteFilterLink');
$overwriteLink.removeClass('hidden');
$saveFiltersModal.data('repositoryTableFilterId', data.data.id);
$('#currentFilterName').html(data.data.attributes.name);
$saveFiltersModal.data('repositoryTableFilterName', data.data.attributes.name);
});
},
closeSavedFilters() {
$('#savedFiltersContainer').removeClass('open');
return true;
},
toggleSavedFilters(e) {
e.stopPropagation();
$('.saved-filters-list').scrollTop(0);
if (this.savedFilterScrollbar) {
this.savedFilterScrollbar.update();
} else {
this.savedFilterScrollbar = new PerfectScrollbar($('.saved-filters-list')[0]);
}
this.closeColumnsFilters();
$('#savedFiltersContainer').toggleClass('open');
}
}
</script>
};
</script>

View file

@ -19,6 +19,7 @@
</template>
<script>
<<<<<<< HEAD
// filter types
import RepositoryNonEmptyTextValue from './filters/repositoryNonEmptyTextValue.vue'
import RepositoryAssetValue from './filters/repositoryAssetValue.vue'
@ -37,38 +38,56 @@
import RepositoryUserValue from './filters/repositoryUserValue.vue'
import RepositoryStockValue from './filters/repositoryStockValue.vue'
import DropdownSelector from '../shared/legacy/dropdown_selector.vue'
=======
// filter types
import RepositoryNonEmptyTextValue from './filters/repositoryNonEmptyTextValue.vue';
import RepositoryAssetValue from './filters/repositoryAssetValue.vue';
import RepositoryTextValue from './filters/repositoryTextValue.vue';
import RepositoryNumberValue from './filters/repositoryNumberValue.vue';
import RepositoryMyModuleValue from './filters/repositoryMyModuleValue.vue';
import RepositoryDateValue from './filters/repositoryDateValue.vue';
import RepositoryDateRangeValue from './filters/repositoryDateRangeValue.vue';
import RepositoryDateTimeValue from './filters/repositoryDateTimeValue.vue';
import RepositoryDateTimeRangeValue from './filters/repositoryDateTimeRangeValue.vue';
import RepositoryTimeValue from './filters/repositoryTimeValue.vue';
import RepositoryTimeRangeValue from './filters/repositoryTimeRangeValue.vue';
import RepositoryListValue from './filters/repositoryListValue.vue';
import RepositoryStatusValue from './filters/repositoryStatusValue.vue';
import RepositoryChecklistValue from './filters/repositoryChecklistValue.vue';
import RepositoryUserValue from './filters/repositoryUserValue.vue';
import RepositoryStockValue from './filters/repositoryStockValue.vue';
import DropdownSelector from '../shared/dropdown_selector.vue';
>>>>>>> develop
export default {
name: "FilterElement",
props: {
filter: Object,
my_modules: Array
},
components: {
DropdownSelector,
RepositoryNonEmptyTextValue,
RepositoryAssetValue,
RepositoryTextValue,
RepositoryNumberValue,
RepositoryMyModuleValue,
RepositoryDateValue,
RepositoryDateRangeValue,
RepositoryTimeValue,
RepositoryTimeRangeValue,
RepositoryDateTimeValue,
RepositoryDateTimeRangeValue,
RepositoryListValue,
RepositoryStatusValue,
RepositoryChecklistValue,
RepositoryListValue,
RepositoryUserValue,
RepositoryStockValue
},
methods: {
updateFilter(value) {
this.$emit('filter:update', value)
}
export default {
name: 'FilterElement',
props: {
filter: Object,
my_modules: Array
},
components: {
DropdownSelector,
RepositoryNonEmptyTextValue,
RepositoryAssetValue,
RepositoryTextValue,
RepositoryNumberValue,
RepositoryMyModuleValue,
RepositoryDateValue,
RepositoryDateRangeValue,
RepositoryTimeValue,
RepositoryTimeRangeValue,
RepositoryDateTimeValue,
RepositoryDateTimeRangeValue,
RepositoryListValue,
RepositoryStatusValue,
RepositoryChecklistValue,
RepositoryUserValue,
RepositoryStockValue
},
methods: {
updateFilter(value) {
this.$emit('filter:update', value);
}
}
};
</script>

View file

@ -22,42 +22,42 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryAssetValue',
mixins: [FilterMixin],
props: {
filter: Object
export default {
name: 'RepositoryAssetValue',
mixins: [FilterMixin],
props: {
filter: Object
},
data() {
return {
operators: [
{ value: 'file_contains', label: this.i18n.t('repositories.show.repository_filter.filters.operators.file_contains') },
{ value: 'file_attached', label: this.i18n.t('repositories.show.repository_filter.filters.operators.file_attached') },
{ value: 'file_not_attached', label: this.i18n.t('repositories.show.repository_filter.filters.operators.file_not_attached') }
],
operator: 'file_contains',
value: ''
};
},
watch: {
operator() {
if (this.operator !== 'file_contains') this.value = '';
},
data() {
return {
operators: [
{ value: 'file_contains', label: this.i18n.t('repositories.show.repository_filter.filters.operators.file_contains') },
{ value: 'file_attached', label: this.i18n.t('repositories.show.repository_filter.filters.operators.file_attached') },
{ value: 'file_not_attached', label: this.i18n.t('repositories.show.repository_filter.filters.operators.file_not_attached') }
],
operator: 'file_contains',
value: ''
}
},
watch: {
operator() {
if(this.operator !== 'file_contains') this.value = '';
},
value() {
this.parameters = this.operator === 'file_contains' ? { text: this.value } : {}
this.updateFilter();
}
},
components: {
DropdownSelector
},
computed: {
isBlank(){
return this.operator == 'file_contains' && !this.value;
}
value() {
this.parameters = this.operator === 'file_contains' ? { text: this.value } : {};
this.updateFilter();
}
},
components: {
DropdownSelector
},
computed: {
isBlank() {
return this.operator == 'file_contains' && !this.value;
}
}
};
</script>

View file

@ -27,40 +27,41 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryChecklistValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'all_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.all_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
}
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { item_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value
}
},
computed: {
isBlank(){
return this.operator == 'any_of' && this.value.length == 0;
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryChecklistValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'all_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.all_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
};
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { item_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value;
}
},
computed: {
isBlank() {
return this.operator === 'any_of' && this.value.length === 0;
}
}
};
</script>

View file

@ -19,45 +19,45 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import RangeDateTimeFilterMixin from '../mixins/filters/range_date_time_filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import DateTimePicker from '../../shared/date_time_picker.vue'
import FilterMixin from '../mixins/filter.js';
import RangeDateTimeFilterMixin from '../mixins/filters/range_date_time_filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
import DateTimePicker from '../../shared/date_time_picker.vue';
export default {
name: 'RepositoryDateRangeValue',
mixins: [FilterMixin, RangeDateTimeFilterMixin],
data() {
return {
timeType: 'date',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
}
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
}
export default {
name: 'RepositoryDateRangeValue',
mixins: [FilterMixin, RangeDateTimeFilterMixin],
data() {
return {
timeType: 'date',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
};
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null;
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
}
}
};
</script>

View file

@ -21,45 +21,45 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import RangeDateTimeFilterMixin from '../mixins/filters/range_date_time_filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import DateTimePicker from '../../shared/date_time_picker.vue'
import FilterMixin from '../mixins/filter.js';
import RangeDateTimeFilterMixin from '../mixins/filters/range_date_time_filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
import DateTimePicker from '../../shared/date_time_picker.vue';
export default {
name: 'RepositoryDateRangeValue',
mixins: [FilterMixin, RangeDateTimeFilterMixin],
data() {
return {
timeType: 'datetime',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
}
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
export default {
name: 'RepositoryDateRangeValue',
mixins: [FilterMixin, RangeDateTimeFilterMixin],
data() {
return {
timeType: 'datetime',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
};
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null;
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
}
};
</script>

View file

@ -23,64 +23,88 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DateTimeFilterMixin from '../mixins/filters/date_time_filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import DateTimePicker from '../../shared/date_time_picker.vue'
import FilterMixin from '../mixins/filter.js';
import DateTimeFilterMixin from '../mixins/filters/date_time_filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
import DateTimePicker from '../../shared/date_time_picker.vue';
export default {
name: 'RepositoryDateValue',
mixins: [FilterMixin, DateTimeFilterMixin],
data() {
return {
timeType: 'datetime',
operators: [
{ value: 'today', label: this.i18n.t('repositories.show.repository_filter.filters.operators.today'), params: {
export default {
name: 'RepositoryDateValue',
mixins: [FilterMixin, DateTimeFilterMixin],
data() {
return {
timeType: 'datetime',
operators: [
{
value: 'today',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.today'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.today')
} },
{ value: 'yesterday', label: this.i18n.t('repositories.show.repository_filter.filters.operators.yesterday'), params: {
}
},
{
value: 'yesterday',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.yesterday'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.yesterday')
} },
{ value: 'last_week', label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_week'), params: {
}
},
{
value: 'last_week',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_week'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.last_week')
} },
{ value: 'this_month', label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_month'), params: {
}
},
{
value: 'this_month',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_month'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.this_month')
} },
{ value: 'this_year', label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_year'), params: {
}
},
{
value: 'this_year',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_year'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.this_year')
} },
{ value: 'last_year', label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_year'), params: {
}
},
{
value: 'last_year',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_year'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.last_year')
} },
{ value: '', label: '', params: { delimiter: true } },
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
}
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
}
},
{ value: '', label: '', params: { delimiter: true } },
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
};
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null;
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
}
};
</script>

View file

@ -21,64 +21,88 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DateTimeFilterMixin from '../mixins/filters/date_time_filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import DateTimePicker from '../../shared/date_time_picker.vue'
import FilterMixin from '../mixins/filter.js';
import DateTimeFilterMixin from '../mixins/filters/date_time_filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
import DateTimePicker from '../../shared/date_time_picker.vue';
export default {
name: 'RepositoryDateValue',
mixins: [FilterMixin, DateTimeFilterMixin],
data() {
return {
timeType: 'date',
operators: [
{ value: 'today', label: this.i18n.t('repositories.show.repository_filter.filters.operators.today'), params: {
export default {
name: 'RepositoryDateValue',
mixins: [FilterMixin, DateTimeFilterMixin],
data() {
return {
timeType: 'date',
operators: [
{
value: 'today',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.today'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.today')
} },
{ value: 'yesterday', label: this.i18n.t('repositories.show.repository_filter.filters.operators.yesterday'), params: {
}
},
{
value: 'yesterday',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.yesterday'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.yesterday')
} },
{ value: 'last_week', label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_week'), params: {
}
},
{
value: 'last_week',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_week'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.last_week')
} },
{ value: 'this_month', label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_month'), params: {
}
},
{
value: 'this_month',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_month'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.this_month')
} },
{ value: 'this_year', label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_year'), params: {
}
},
{
value: 'this_year',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.this_year'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.this_year')
} },
{ value: 'last_year', label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_year'), params: {
}
},
{
value: 'last_year',
label: this.i18n.t('repositories.show.repository_filter.filters.operators.last_year'),
params: {
tooltip: this.i18n.t('repositories.show.repository_filter.filters.operators.tooltips.last_year')
} },
{ value: '', label: '', params: { delimiter: true } },
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
}
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
}
}
},
{ value: '', label: '', params: { delimiter: true } },
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.on') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.after') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.before') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.date.not_on') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
};
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null;
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
}
}
};
</script>

View file

@ -27,39 +27,40 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryListValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
}
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { item_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value;
}
},
computed: {
isBlank(){
return this.operator == 'any_of' && this.value.length == 0;
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryListValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
};
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { item_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value;
}
},
computed: {
isBlank() {
return this.operator == 'any_of' && this.value.length == 0;
}
}
};
</script>

View file

@ -29,45 +29,46 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryMyModuleValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'all_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.all_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryMyModuleValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'all_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.all_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
};
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { my_module_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value;
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { my_module_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value;
},
renderOption(data) {
return `<span class="task-option">
renderOption(data) {
return `<span class="task-option">
${data.label}
</span>`;
}
},
computed: {
isBlank(){
return this.operator == 'any_of' && this.value.length == 0;
}
}
},
computed: {
isBlank() {
return this.operator == 'any_of' && this.value.length == 0;
}
}
};
</script>

View file

@ -22,34 +22,35 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryNonEmptyTextValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'contains', label: this.i18n.t('repositories.show.repository_filter.filters.operators.contains') },
{ value: 'doesnt_contain', label: this.i18n.t('repositories.show.repository_filter.filters.operators.does_not_contain') }
],
operator: 'contains',
value: ''
}
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { text: this.value };
this.updateFilter();
}
},
computed: {
isBlank(){
return this.operator == 'contains' && !this.value;
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryNonEmptyTextValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'contains', label: this.i18n.t('repositories.show.repository_filter.filters.operators.contains') },
{ value: 'doesnt_contain', label: this.i18n.t('repositories.show.repository_filter.filters.operators.does_not_contain') }
],
operator: 'contains',
value: ''
};
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { text: this.value };
this.updateFilter();
}
},
computed: {
isBlank() {
return this.operator == 'contains' && !this.value;
}
}
};
</script>

View file

@ -43,58 +43,59 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryNumberValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to')},
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
value: '',
from: '',
to: ''
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryNumberValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
value: '',
from: '',
to: ''
};
},
components: {
DropdownSelector
},
methods: {
validateNumber(number) {
return number.replace(/[^0-9.]/g, '').match(/^\d*(\.\d{0,10})?/)[0];
}
},
watch: {
value() {
this.value = this.validateNumber(this.value);
this.parameters = { number: this.value };
this.updateFilter();
},
components: {
DropdownSelector
to() {
this.to = this.validateNumber(this.to);
this.parameters = { from: this.from, to: this.to };
this.updateFilter();
},
methods: {
validateNumber(number) {
return number.replace(/[^0-9.]/g, '').match(/^\d*(\.\d{0,10})?/)[0]
}
},
watch: {
value() {
this.value = this.validateNumber(this.value)
this.parameters = { number: this.value }
this.updateFilter();
},
to() {
this.to = this.validateNumber(this.to)
this.parameters = {from: this.from, to: this.to}
this.updateFilter();
},
from() {
this.from = this.validateNumber(this.from)
this.parameters = {from: this.from, to: this.to}
this.updateFilter();
}
},
computed: {
isBlank(){
return (!this.value && this.operator != 'between') ||
((!this.to || !this.from) && this.operator == 'between');
}
from() {
this.from = this.validateNumber(this.from);
this.parameters = { from: this.from, to: this.to };
this.updateFilter();
}
},
computed: {
isBlank() {
return (!this.value && this.operator !== 'between')
|| ((!this.to || !this.from) && this.operator === 'between');
}
}
};
</script>

View file

@ -27,39 +27,40 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryStatusValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
}
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { item_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value
}
},
computed: {
isBlank(){
return this.operator == 'any_of' && this.value.length == 0;
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryStatusValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: []
};
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { item_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value;
}
},
computed: {
isBlank() {
return this.operator === 'any_of' && this.value.length === 0;
}
}
};
</script>

View file

@ -53,83 +53,84 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryStockValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to')},
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
value: '',
from: '',
to: '',
stock_unit: 'all'
}
},
components: {
DropdownSelector
},
methods: {
validateNumber(number) {
return number.replace(/[^0-9.]/g, '').match(/^\d*(\.\d{0,10})?/)[0]
},
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
prepareUnitOptions() {
return [
{ label: this.i18n.t('repositories.show.repository_filter.filters.types.RepositoryStockValue.all_units'), value: 'all'},
{ label: this.i18n.t('repositories.show.repository_filter.filters.types.RepositoryStockValue.no_unit'), value: 'none'}
].concat(this.filter.column.items);
},
export default {
name: 'RepositoryStockValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
value: '',
from: '',
to: '',
stock_unit: 'all'
};
},
components: {
DropdownSelector
},
methods: {
validateNumber(number) {
return number.replace(/[^0-9.]/g, '').match(/^\d*(\.\d{0,10})?/)[0];
},
updateStockUnit(value) {
this.stock_unit = value
}
prepareUnitOptions() {
return [
{ label: this.i18n.t('repositories.show.repository_filter.filters.types.RepositoryStockValue.all_units'), value: 'all' },
{ label: this.i18n.t('repositories.show.repository_filter.filters.types.RepositoryStockValue.no_unit'), value: 'none' }
].concat(this.filter.column.items);
},
updateStockUnit(value) {
this.stock_unit = value;
}
},
created() {
if (this.parameters) {
this.value = this.parameters.value || '';
this.from = this.parameters.from || '';
this.to = this.parameters.to || '';
this.stock_unit = this.parameters.stock_unit || 'all';
}
},
watch: {
stock_unit() {
this.parameters.stock_unit = this.stock_unit;
this.updateFilter();
},
created() {
if (this.parameters) {
this.value = this.parameters.value || ''
this.from = this.parameters.from || ''
this.to = this.parameters.to || ''
this.stock_unit = this.parameters.stock_unit || 'all'
}
value() {
this.value = this.validateNumber(this.value);
this.parameters = { value: this.value, stock_unit: this.stock_unit };
this.updateFilter();
},
watch: {
stock_unit() {
this.parameters.stock_unit = this.stock_unit
this.updateFilter();
},
value() {
this.value = this.validateNumber(this.value)
this.parameters = { value: this.value, stock_unit: this.stock_unit }
this.updateFilter();
},
to() {
this.to = this.validateNumber(this.to)
this.parameters = {from: this.from, to: this.to, stock_unit: this.stock_unit}
this.updateFilter();
},
from() {
this.from = this.validateNumber(this.from)
this.parameters = {from: this.from, to: this.to, stock_unit: this.stock_unit}
this.updateFilter();
}
to() {
this.to = this.validateNumber(this.to);
this.parameters = { from: this.from, to: this.to, stock_unit: this.stock_unit };
this.updateFilter();
},
computed: {
isBlank(){
return (!this.value && this.operator != 'between') ||
((!this.to || !this.from) && this.operator == 'between');
}
from() {
this.from = this.validateNumber(this.from);
this.parameters = { from: this.from, to: this.to, stock_unit: this.stock_unit };
this.updateFilter();
}
},
computed: {
isBlank() {
return (!this.value && this.operator != 'between')
|| ((!this.to || !this.from) && this.operator == 'between');
}
}
};
</script>

View file

@ -22,35 +22,36 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryTextValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'contains', label: this.i18n.t('repositories.show.repository_filter.filters.operators.contains') },
{ value: 'doesnt_contain', label: this.i18n.t('repositories.show.repository_filter.filters.operators.does_not_contain') },
{ value: 'empty', label: this.i18n.t('repositories.show.repository_filter.filters.operators.empty') }
],
operator: 'contains',
value: ''
}
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { text: this.value };
this.updateFilter();
}
},
computed: {
isBlank(){
return this.operator == 'contains' && !this.value;
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryTextValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'contains', label: this.i18n.t('repositories.show.repository_filter.filters.operators.contains') },
{ value: 'doesnt_contain', label: this.i18n.t('repositories.show.repository_filter.filters.operators.does_not_contain') },
{ value: 'empty', label: this.i18n.t('repositories.show.repository_filter.filters.operators.empty') }
],
operator: 'contains',
value: ''
};
},
components: {
DropdownSelector
},
watch: {
value() {
this.parameters = { text: this.value };
this.updateFilter();
}
},
computed: {
isBlank() {
return this.operator === 'contains' && !this.value;
}
}
};
</script>

View file

@ -19,47 +19,47 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import RangeDateTimeFilterMixin from '../mixins/filters/range_date_time_filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import DateTimePicker from '../../shared/date_time_picker.vue'
import FilterMixin from '../mixins/filter.js';
import RangeDateTimeFilterMixin from '../mixins/filters/range_date_time_filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
import DateTimePicker from '../../shared/date_time_picker.vue';
export default {
name: 'RepositoryTimeRangeValue',
mixins: [FilterMixin, RangeDateTimeFilterMixin],
data() {
return {
timeType: 'time',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to')},
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
}
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
export default {
name: 'RepositoryTimeRangeValue',
mixins: [FilterMixin, RangeDateTimeFilterMixin],
data() {
return {
timeType: 'time',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
};
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null;
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
}
};
</script>

View file

@ -21,47 +21,47 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DateTimeFilterMixin from '../mixins/filters/date_time_filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
import DateTimePicker from '../../shared/date_time_picker.vue'
import FilterMixin from '../mixins/filter.js';
import DateTimeFilterMixin from '../mixins/filters/date_time_filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
import DateTimePicker from '../../shared/date_time_picker.vue';
export default {
name: 'RepositoryTimeValue',
mixins: [FilterMixin, DateTimeFilterMixin],
data() {
return {
timeType: 'time',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to')},
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
}
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
export default {
name: 'RepositoryTimeValue',
mixins: [FilterMixin, DateTimeFilterMixin],
data() {
return {
timeType: 'time',
operators: [
{ value: 'equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.equal_to') },
{ value: 'unequal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.unequal_to') },
{ value: 'greater_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than') },
{ value: 'greater_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.greater_than_or_equal_to') },
{ value: 'less_than', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than') },
{ value: 'less_than_or_equal_to', label: this.i18n.t('repositories.show.repository_filter.filters.operators.less_than_or_equal_to') },
{ value: 'between', label: this.i18n.t('repositories.show.repository_filter.filters.operators.between') }
],
operator: 'equal_to',
date: null,
dateTo: null,
value: null
};
},
components: {
DropdownSelector,
DateTimePicker
},
watch: {
value() {
this.parameters = this.value;
this.updateFilter();
}
},
methods: {
formattedDate(date) {
if (!date) return null;
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
}
};
</script>

View file

@ -30,54 +30,55 @@
</template>
<script>
import FilterMixin from '../mixins/filter.js'
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue'
export default {
name: 'RepositoryUserValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: [],
users: null
}
import FilterMixin from '../mixins/filter.js';
import DropdownSelector from '../../shared/legacy/dropdown_selector.vue';
export default {
name: 'RepositoryUserValue',
mixins: [FilterMixin],
data() {
return {
operators: [
{ value: 'any_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.any_of') },
{ value: 'none_of', label: this.i18n.t('repositories.show.repository_filter.filters.operators.none_of') }
],
operator: 'any_of',
value: [],
users: null
};
},
components: {
DropdownSelector
},
mounted() {
const params = {};
if (this.filter.column.id === 'archived_by') params.archived_by = true;
$.get($('#filterContainer').data('users-url'), params, (data) => {
this.users = data.users;
});
},
watch: {
value() {
this.parameters = { user_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value;
},
components: {
DropdownSelector
},
mounted() {
let params = {}
if (this.filter.column.id === 'archived_by') params.archived_by = true
$.get($('#filterContainer').data('users-url'), params, (data) => {
this.users = data.users;
});
},
watch: {
value() {
this.parameters = { user_ids: this.value };
this.updateFilter();
}
},
methods: {
updateValue(value) {
this.value = value
},
renderOption(data) {
return `<span class="user-filter-option" title="${data.label.trim()} | ${data.params.email}">
renderOption(data) {
return `<span class="user-filter-option" title="${data.label.trim()} | ${data.params.email}">
<img class="item-avatar" src="${data.params.avatar_url}"/>
${data.label}
</span>`;
}
},
computed: {
isBlank(){
return (this.operator === 'any_of' && this.value.length === 0) ||
(this.filter.column.id === 'archived_by' && $('.repository-show').hasClass('active') ) ;
}
}
},
computed: {
isBlank() {
return (this.operator === 'any_of' && this.value.length === 0)
|| (this.filter.column.id === 'archived_by' && $('.repository-show').hasClass('active'));
}
}
};
</script>

View file

@ -12,19 +12,19 @@
</template>
<script>
import FilterElement from './filter.vue'
import FilterElement from './filter.vue';
export default {
name: 'FiltersList',
props: {
filters: Array,
my_modules: Array,
},
components: {FilterElement},
methods: {
updateFilter(value) {
this.$emit('filter:update', value)
}
export default {
name: 'FiltersList',
props: {
filters: Array,
my_modules: Array
},
components: { FilterElement },
methods: {
updateFilter(value) {
this.$emit('filter:update', value);
}
}
};
</script>

View file

@ -8,25 +8,25 @@
</template>
<script>
export default {
name: 'SavedFilterElement',
props: {
savedFilter: Object,
canManageFilters: Boolean
export default {
name: 'SavedFilterElement',
props: {
savedFilter: Object,
canManageFilters: Boolean
},
methods: {
loadFilters() {
this.$emit('savedFilter:load', this.savedFilter.attributes.show_url);
},
methods: {
loadFilters() {
this.$emit('savedFilter:load', this.savedFilter.attributes.show_url)
},
deleteFilter() {
$.ajax({
url: this.savedFilter.attributes.delete_url,
type: 'DELETE',
success: ()=> {
this.$emit('savedFilter:delete')
}
});
}
deleteFilter() {
$.ajax({
url: this.savedFilter.attributes.delete_url,
type: 'DELETE',
success: () => {
this.$emit('savedFilter:delete');
}
});
}
}
};
</script>

View file

@ -5,7 +5,7 @@
leave-to-class="translate-x-full w-0"
v-click-outside="handleOutsideClick">
<div ref="wrapper" v-show="isShowing" id="repository-item-sidebar-wrapper"
class='items-sidebar-wrapper bg-white gap-2.5 self-stretch rounded-tl-4 rounded-bl-4 shadow-lg h-full w-[565px]'>
class='items-sidebar-wrapper bg-white gap-2.5 self-stretch rounded-tl-4 rounded-bl-4 sn-shadow-menu-lg h-full w-[565px]'>
<div id="repository-item-sidebar" class="w-full h-full pl-6 bg-white flex flex-col">
@ -13,7 +13,7 @@
class="sticky top-0 right-0 bg-white flex z-50 flex-col h-[78px] pt-6">
<div class="header flex w-full h-[30px] pr-6">
<repository-item-sidebar-title v-if="defaultColumns"
:editable="permissions?.can_manage && !defaultColumns?.archived"
:editable="permissions.can_manage && !defaultColumns?.archived"
:name="defaultColumns.name"
:archived="defaultColumns.archived"
@update="update">
@ -31,12 +31,12 @@
<div v-else class="flex flex-1 flex-grow-1 justify-between" ref="scrollSpyContent" id="scrollSpyContent">
<div id="left-col" class="flex flex-col gap-4 max-w-[350px]">
<div id="left-col" class="flex flex-col gap-6 max-w-[350px]">
<!-- INFORMATION -->
<section id="information-section">
<div ref="information-label" id="information-label"
class="font-inter text-lg font-semibold leading-7 mb-4 transition-colors duration-300">{{
class="font-inter text-lg font-semibold leading-7 mb-6 transition-colors duration-300">{{
i18n.t('repositories.item_card.section.information') }}
</div>
<div v-if="defaultColumns">
@ -113,13 +113,13 @@
<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, RELATIONSHIPS, ASSIGNED, QR CODE -->
<div id="custom-col-assigned-qr-wrapper" class="flex flex-col gap-6">
<!-- CUSTOM COLUMNS -->
<section id="custom-columns-section" class="flex flex-col min-h-[64px] h-auto">
<div ref="custom-columns-label" id="custom-columns-label"
class="font-inter text-lg font-semibold leading-7 pb-4 transition-colors duration-300">
class="font-inter text-lg font-semibold leading-7 mb-6 transition-colors duration-300">
{{ i18n.t('repositories.item_card.custom_columns_label') }}
</div>
<CustomColumns :customColumns="customColumns" :repositoryRowId="repositoryRowId"
@ -129,10 +129,109 @@
<div id="divider" class="w-500 bg-sn-light-grey flex px-8 items-center self-stretch h-px"></div>
<!-- RELATIONSHIPS -->
<section id="relationships-section" class="flex flex-col" ref="relationshipsSectionRef">
<div ref="relationships-label" id="relationships-label"
class="font-inter text-lg font-semibold leading-7 mb-6 transition-colors duration-300">
{{ i18n.t('repositories.item_card.section.relationships') }}
</div>
<div class="font-inter text-sm leading-5 w-full">
<div class="flex flex-row justify-between mb-4">
<div class="font-semibold">
{{ i18n.t('repositories.item_card.relationships.parents.count', { count: parentsCount || 0 }) }}
</div>
<a
v-if="permissions.can_connect_rows"
class="relationships-add-link btn-text-link font-normal"
@click="handleOpenAddRelationshipsModal($event, 'parent')">
{{ i18n.t('repositories.item_card.add_relationship_button_text') }}
</a>
</div>
<div v-if="parentsCount">
<details v-for="(parent) in parents" @toggle="updateOpenState(parent.code, $event.target.open)" :key="parent.code" class="flex flex-col font-normal gap-4 group cursor-default">
<summary class="flex flex-row gap-3 mb-4 relative">
<img :src="icons.delimiter_path" class="w-3 h-3 cursor-pointer flex-shrink-0 relative top-1"
:class="{ 'rotate-90': relationshipDetailsState[parent.code] }" />
<span>
<span>{{ i18n.t('repositories.item_card.relationships.item') }}</span>
<a :href="parent.path" class="record-info-link btn-text-link !text-sn-science-blue">{{ parent.name }}</a>
<button v-if="permissions.can_connect_rows" @click="openUnlinkModal(parent)"
class=" ml-2 bg-transparent border-none opacity-0 group-hover:opacity-100 cursor-pointer">
<img :src="icons.unlink_path" />
</button>
</span>
</summary>
<p class="flex flex-col gap-3 ml-6 mb-4">
<span>
{{ i18n.t('repositories.item_card.relationships.id', { code: parent.code }) }}
</span>
<span>
<span>{{ i18n.t('repositories.item_card.relationships.inventory') }}</span>
<a :href="parent.repository_path" class="btn-text-link !text-sn-science-blue">
{{ parent.repository_name }}
</a>
</span>
</p>
</details>
</div>
<div v-else class="text-sn-dark-grey max-h-5">
{{ i18n.t('repositories.item_card.relationships.parents.empty') }}
</div>
</div>
<div class="sci-divider pb-4"></div>
<div class="font-inter text-sm leading-5 w-full">
<div class="flex flex-row justify-between" :class="{ 'mb-4': childrenCount }">
<div class="font-semibold">
{{ i18n.t('repositories.item_card.relationships.children.count', { count: childrenCount || 0 }) }}
</div>
<a
v-if="permissions.can_connect_rows"
class="relationships-add-link btn-text-link font-normal"
@click="handleOpenAddRelationshipsModal($event, 'child')">
{{ i18n.t('repositories.item_card.add_relationship_button_text') }}
</a>
</div>
<div v-if="childrenCount">
<details v-for="(child) in children" :key="child.code" @toggle="updateOpenState(child.code, $event.target.open)"
class="flex flex-col font-normal gap-4 group last-of-type:[&>p:last-child]:mb-0">
<summary class="flex flex-row gap-3 mb-4 relative"
:class="{ 'group-last-of-type:mb-0': !relationshipDetailsState[child.code] }">
<img :src="icons.delimiter_path" class="w-3 h-3 flex-shrink-0 cursor-pointer relative top-1"
:class="{ 'rotate-90': relationshipDetailsState[child.code] }"/>
<span class="group/child">
<span>{{ i18n.t('repositories.item_card.relationships.item') }}</span>
<a :href="child.path" class="record-info-link btn-text-link !text-sn-science-blue">{{ child.name }}</a>
<button v-if="permissions.can_connect_rows" @click="openUnlinkModal(child)"
class="ml-2 bg-transparent border-none opacity-0 group-hover:opacity-100 cursor-pointer">
<img :src="icons.unlink_path" />
</button>
</span>
</summary>
<p class="flex flex-col gap-3 ml-6 mb-4">
<span>{{ i18n.t('repositories.item_card.relationships.id', { code: child.code }) }}</span>
<span>
<span>{{ i18n.t('repositories.item_card.relationships.inventory') }}</span>
<a :href="child.repository_path" class="btn-text-link !text-sn-science-blue">
{{ child.repository_name }}
</a>
</span>
</p>
</details>
</div>
<div v-else class="text-sn-dark-grey max-h-5">
{{ i18n.t('repositories.item_card.relationships.children.empty') }}
</div>
</div>
</section>
<div id="divider" class="w-500 bg-sn-light-grey flex px-8 items-center self-stretch h-px"></div>
<!-- ASSIGNED -->
<section id="assigned-section" class="flex flex-col" ref="assignedSectionRef">
<div
class="flex flex-row text-lg font-semibold w-[350px] pb-4 leading-7 items-center justify-between transition-colors duration-300"
class="flex flex-row text-lg font-semibold w-[350px] mb-6 leading-7 items-center justify-between transition-colors duration-300"
ref="assigned-label"
id="assigned-label"
>
@ -163,7 +262,10 @@
<div class="flex flex-col gap-2">
<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 hover:text-sn-science-blue hover:no-underline">
<a v-if="defaultColumns.archived" :href="item.url" class="text-sn-science-blue hover:text-sn-science-blue hover:no-underline">
{{ i18n.t('labels.archived')}} {{ item.value }}
</a>
<a v-else :href="item.url" class="text-sn-science-blue hover:text-sn-science-blue hover:no-underline">
{{ item.archived ? i18n.t('labels.archived') : '' }} {{ item.value }}
</a>
</div>
@ -181,7 +283,7 @@
<!-- QR -->
<section id="qr-section" ref="QR-label">
<div id="QR-label" class="font-inter text-lg font-semibold leading-7 mb-4 mt-0 transition-colors duration-300">
<div id="QR-label" class="font-inter text-lg font-semibold leading-7 mb-6 mt-0 transition-colors duration-300">
{{ i18n.t('repositories.item_card.section.qr') }}
</div>
<div class="bar-code-container">
@ -195,37 +297,8 @@
<!-- NAVIGATION -->
<div v-if="isShowing && !dataLoading" ref="navigationRef" id="navigation"
class="flex item-end gap-x-4 min-w-[130px] min-h-[130px] h-fit sticky top-0 pr-6 [scrollbar-gutter:stable_both-edges] ">
<scroll-spy :itemsToCreate="[
{
id: 'highlight-item-1',
textId: 'text-item-1',
labelAlias: 'information_label',
label: 'information-label',
sectionId: 'information-section'
},
{
id: 'highlight-item-2',
textId: 'text-item-2',
labelAlias: 'custom_columns_label',
label: 'custom-columns-label',
sectionId: 'custom-columns-section'
},
{
id: 'highlight-item-3',
textId: 'text-item-3',
labelAlias: 'assigned_label',
label: 'assigned-label',
sectionId: 'assigned-section'
},
{
id: 'highlight-item-4',
textId: 'text-item-4',
labelAlias: 'QR_label',
label: 'QR-label',
sectionId: 'qr-section'
}
]" v-show="isShowing">
</scroll-spy>
<scroll-spy v-show="isShowing" :initialSectionId="initialSectionId" />
</div>
</div>
@ -235,7 +308,8 @@
<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-e2e="e2e-BT-invInventoryItemSB-print"
:data-rows="JSON.stringify([repositoryRowId])">
:data-rows="JSON.stringify([repositoryRowId])"
:data-repository-id="repository?.id">
{{ i18n.t('repositories.item_card.print_label') }}
</button>
</div>
@ -245,6 +319,10 @@
</div>
</div>
</transition>
<Teleport to="body">
<unlink-modal v-if="selectedToUnlink" @cancel="closeUnlinkModal" @unlink="unlinkItem" />
</Teleport>
</template>
<script>
@ -252,7 +330,9 @@ import { vOnClickOutside } from '@vueuse/components';
import InlineEdit from '../shared/inline_edit.vue';
import ScrollSpy from './repository_values/ScrollSpy.vue';
import CustomColumns from './customColumns.vue';
import RepositoryItemSidebarTitle from './Title.vue'
import RepositoryItemSidebarTitle from './Title.vue';
import UnlinkModal from './unlink_modal.vue';
import axios from '../../packs/custom_axios.js';
export default {
name: 'RepositoryItemSidebar',
@ -261,6 +341,7 @@ export default {
'repository-item-sidebar-title': RepositoryItemSidebarTitle,
'inline-edit': InlineEdit,
'scroll-spy': ScrollSpy,
'unlink-modal': UnlinkModal
},
directives: {
'click-outside': vOnClickOutside
@ -274,20 +355,30 @@ export default {
repository: null,
defaultColumns: null,
customColumns: null,
parentsCount: 0,
childrenCount: 0,
parents: null,
children: null,
assignedModules: null,
isShowing: false,
barCodeSrc: null,
permissions: null,
permissions: {},
repositoryRowUrl: null,
actions: null,
myModuleId: null,
inRepository: false
}
inRepository: false,
icons: null,
notification: null,
relationshipDetailsState: {},
relationshipsEnabled: false,
selectedToUnlink: null,
initialSectionId: null
};
},
provide() {
return {
reloadRepoItemSidebar: this.reload,
}
reloadRepoItemSidebar: this.reload
};
},
created() {
window.repositoryItemSidebarComponent = this;
@ -297,6 +388,18 @@ export default {
return this.defaultColumns?.archived ? `${I18n.t('labels.archived')} ${this.defaultColumns?.name}` : this.defaultColumns?.name;
}
},
watch: {
parents(newParents) {
newParents.forEach((parent) => {
this.relationshipDetailsState[parent.code] = false;
});
},
children(newChildren) {
newChildren.forEach((child) => {
this.relationshipDetailsState[child.code] = false;
});
}
},
mounted() {
// Add a click event listener to the document
this.inRepository = $('.assign-items-to-task-modal-container').length > 0;
@ -305,6 +408,30 @@ export default {
delete window.repositoryItemSidebarComponent;
},
methods: {
handleOpenAddRelationshipsModal(event, relation) {
event.stopPropagation();
event.preventDefault();
const addRelationCallback = (data, connectionRelation) => {
if (connectionRelation === 'parent') {
this.parentsCount = data.parents.length;
this.parents = data.parents;
}
if (connectionRelation === 'child') {
this.childrenCount = data.children.length;
this.children = data.children;
}
};
window.repositoryItemRelationshipsModal.show(
{
relation,
addRelationCallback,
optionUrls: { ...this.actions.row_connections },
notificationIconPath: this.icons.notification_path,
notification: this.notification,
canConnectRows: this.permissions.can_connect_rows
}
);
},
handleOutsideClick(event) {
if (!this.isShowing) return;
@ -326,7 +453,11 @@ export default {
this.toggleShowHideSidebar(null);
}
},
toggleShowHideSidebar(repositoryRowUrl, myModuleId = null) {
toggleShowHideSidebar(repositoryRowUrl, myModuleId = null, initialSectionId = null) {
if (initialSectionId) {
this.initialSectionId = initialSectionId;
} else this.initialSectionId = null;
// initial click
if (this.currentItemUrl === null) {
this.myModuleId = myModuleId;
@ -368,11 +499,18 @@ export default {
this.optionsPath = result.options_path;
this.updatePath = result.update_path;
this.defaultColumns = result.default_columns;
this.relationshipsEnabled = result.relationships.enabled;
this.parentsCount = result.relationships.parents_count;
this.childrenCount = result.relationships.children_count;
this.parents = result.relationships.parents;
this.children = result.relationships.children;
this.customColumns = result.custom_columns;
this.assignedModules = result.assigned_modules;
this.permissions = result.permissions;
this.actions = result.actions;
this.icons = result.icons;
this.dataLoading = false;
this.notification = result.notification;
this.$nextTick(() => {
this.generateBarCode(this.defaultColumns.code);
@ -412,15 +550,30 @@ export default {
dataType: 'json',
data: {
id: this.id,
...params,
},
...params
}
}).done((response) => {
if (response) {
this.customColumns = this.customColumns.map(col => col.id === response.id ? { ...col, ...response } : col)
if ($('.dataTable')[0]) $('.dataTable').DataTable().ajax.reload(null, false);
this.customColumns = this.customColumns.map((col) => (col.id === response.id ? { ...col, ...response } : col));
if ($('.dataTable.repository-dataTable')[0]) $('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
}
});
},
updateOpenState(code, isOpen) {
this.relationshipDetailsState[code] = isOpen;
},
openUnlinkModal(item) {
this.selectedToUnlink = item;
},
closeUnlinkModal() {
this.selectedToUnlink = null;
},
async unlinkItem() {
await axios.delete(this.selectedToUnlink.unlink_path);
this.loadRepositoryRow(this.currentItemUrl);
if ($('.dataTable.repository-dataTable')[0]) $('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
this.selectedToUnlink = null;
}
}
}
};
</script>

View file

@ -10,28 +10,28 @@
</template>
<script>
import InlineEdit from "../shared/inline_edit.vue";
import InlineEdit from '../shared/inline_edit.vue';
export default {
name: "RepositoryItemSidebarTitle",
name: 'RepositoryItemSidebarTitle',
components: {
"inline-edit": InlineEdit
'inline-edit': InlineEdit
},
emits: ['update'],
props: {
editable: Boolean,
name: String,
archived: Boolean,
archived: Boolean
},
computed: {
computedName() {
return this.archived ? `(A) ${this.name}` : this.name;
},
}
},
methods: {
updateName(name) {
this.$emit('update', { 'repository_row': { name: name } });
},
},
this.$emit('update', { repository_row: { name } });
}
}
};
</script>

View file

@ -30,55 +30,55 @@
</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 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';
export default {
name: 'CustomColumns',
components: {
RepositoryStockValue,
RepositoryTextValue,
RepositoryNumberValue,
RepositoryAssetValue,
RepositoryListValue,
RepositoryChecklistValue,
RepositoryStatusValue,
RepositoryDateTimeValue,
RepositoryDateTimeRangeValue,
RepositoryDateValue,
RepositoryDateRangeValue,
RepositoryTimeRangeValue,
RepositoryTimeValue
},
props: {
customColumns: { type: Array, default: () => [] },
permissions: { type: Object, default: () => {} },
updatePath: { type: String, default: '' },
repositoryRowId: { type: Number, default: null },
repositoryId: { type: Number, default: null },
inArchivedRepositoryRow: { type: Boolean, default: false },
actions: {type: Object, default: () => {}}
},
data() {
return {
editingField: null
}
},
methods: {
update(params) {
this.$emit('update', params);
}
export default {
name: 'CustomColumns',
components: {
RepositoryStockValue,
RepositoryTextValue,
RepositoryNumberValue,
RepositoryAssetValue,
RepositoryListValue,
RepositoryChecklistValue,
RepositoryStatusValue,
RepositoryDateTimeValue,
RepositoryDateTimeRangeValue,
RepositoryDateValue,
RepositoryDateRangeValue,
RepositoryTimeRangeValue,
RepositoryTimeValue
},
props: {
customColumns: { type: Array, default: () => [] },
permissions: { type: Object, default: () => {} },
updatePath: { type: String, default: '' },
repositoryRowId: { type: Number, default: null },
repositoryId: { type: Number, default: null },
inArchivedRepositoryRow: { type: Boolean, default: false },
actions: { type: Object, default: () => {} }
},
data() {
return {
editingField: null
};
},
methods: {
update(params) {
this.$emit('update', params);
}
}
};
</script>

View file

@ -74,7 +74,7 @@ export default {
break;
}
Object.assign($this.$data, { isEditing: false, isSaving: false, values: result?.value });
if ($('.dataTable')[0]) $('.dataTable').DataTable().ajax.reload(null, false);
if ($('.dataTable.repository-dataTable')[0]) $('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
}
});
},

View file

@ -7,24 +7,24 @@
</template>
<script>
export default {
name: 'Reminder',
props: {
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';
},
reminderTitle() {
let title = this.value?.reminder_text
if (this.value?.reminder_message) title = `${title}\n${this.value?.reminder_message}`;
return title;
export default {
name: 'Reminder',
props: {
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';
},
reminderTitle() {
let title = this.value?.reminder_text;
if (this.value?.reminder_message) title = `${title}\n${this.value?.reminder_message}`;
return title;
}
}
};
</script>

View file

@ -48,12 +48,12 @@
</template>
<script>
import TooltipPreview from './TooltipPreview.vue'
import TooltipPreview from './TooltipPreview.vue';
export default {
name: 'RepositoryAssetvalue',
components: {
"tooltip-preview": TooltipPreview
'tooltip-preview': TooltipPreview
},
data() {
return {
@ -67,7 +67,7 @@ export default {
uploading: false,
progress: 0,
error: false
}
};
},
props: {
data_type: String,
@ -80,18 +80,18 @@ export default {
canEdit: { type: Boolean, default: false }
},
created() {
if (!this.colVal) return
if (!this.colVal) return;
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
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}`
return `modal_link${this.id}`;
}
},
methods: {
@ -114,21 +114,25 @@ export default {
this.error = '';
if (file.size > GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB * 1024 * 1024) {
this.error = I18n.t('repositories.item_card.repository_asset_value.errors.file_too_big',
{ file_size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB });
this.error = I18n.t(
'repositories.item_card.repository_asset_value.errors.file_too_big',
{ file_size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB }
);
this.uploading = false;
return;
}
const upload = new ActiveStorage.DirectUpload(file,
this.actions.direct_file_upload_path,
{
directUploadWillStoreFileWithXHR: (request) => {
request.upload.addEventListener('progress', (e) => {
this.progress = parseInt((e.loaded / e.total) * 100, 10);
});
}
});
const upload = new ActiveStorage.DirectUpload(
file,
this.actions.direct_file_upload_path,
{
directUploadWillStoreFileWithXHR: (request) => {
request.upload.addEventListener('progress', (e) => {
this.progress = parseInt((e.loaded / e.total) * 100, 10);
});
}
}
);
upload.create((error, blob) => {
if (error) {
@ -149,7 +153,7 @@ export default {
}
},
success: (result) => {
let assetRepositoryCell = result?.value;
const assetRepositoryCell = result?.value;
this.uploading = false;
if (assetRepositoryCell) {
@ -162,7 +166,7 @@ export default {
} else {
this.file_name = '';
}
if ($('.dataTable')[0]) $('.dataTable').DataTable().ajax.reload(null, false);
if ($('.dataTable.repository-dataTable')[0]) $('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
},
error: () => {
this.error = I18n.t('repositories.item_card.repository_asset_value.errors.upload_failed_general');
@ -171,5 +175,5 @@ export default {
});
}
}
}
};
</script>

View file

@ -14,7 +14,7 @@
:options="checklistItems"
:placeholder="i18n.t('repositories.item_card.dropdown_placeholder')"
:no-options-placeholder="i18n.t('repositories.item_card.dropdown_placeholder')"
className="h-[38px] pl-3"
className="h-[38px] !pl-3"
optionsClassName="max-h-[300px]"
></checklist-select>
</div>
@ -39,14 +39,14 @@
</template>
<script>
import ChecklistSelect from "../../shared/legacy/checklist_select.vue";
import repositoryValueMixin from "./mixins/repository_value.js";
import ChecklistSelect from '../../shared/legacy/checklist_select.vue';
import repositoryValueMixin from './mixins/repository_value.js';
export default {
name: "RepositoryChecklistValue",
name: 'RepositoryChecklistValue',
mixins: [repositoryValueMixin],
components: {
"checklist-select": ChecklistSelect
'checklist-select': ChecklistSelect
},
props: {
data_type: String,
@ -67,20 +67,20 @@ export default {
},
mounted() {
this.fetchChecklistItems();
if(this.colVal) {
if (this.colVal) {
this.selectedChecklistItems = Array.isArray(this.colVal) ? this.colVal : [this.colVal];
this.selectedValues = this.selectedChecklistItems.map(item => item?.value);
this.selectedValues = this.selectedChecklistItems.map((item) => item?.value);
}
},
methods: {
fetchChecklistItems() {
this.isLoading = true;
$.get(this.optionsPath, data => {
$.get(this.optionsPath, (data) => {
if (Array.isArray(data)) {
this.checklistItems = data.map(option => {
this.checklistItems = data.map((option) => {
const { value, label } = option;
return { id: value, label: label };
return { id: value, label };
});
return false;
}

View file

@ -16,16 +16,16 @@
<script>
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryDateRangeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: String,
canEdit: { type: Boolean, default: false },
}
export default {
name: 'RepositoryDateRangeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: String,
canEdit: { type: Boolean, default: false }
}
};
</script>

View file

@ -13,18 +13,18 @@
</template>
<script>
import DateTimeComponent from './date_time_component.vue';
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryDateTimeRangeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: String,
canEdit: { type: Boolean, default: false }
}
export default {
name: 'RepositoryDateTimeRangeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: String,
canEdit: { type: Boolean, default: false }
}
};
</script>

View file

@ -12,18 +12,18 @@
</template>
<script>
import DateTimeComponent from './date_time_component.vue';
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryDateTimeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: String,
canEdit: { type: Boolean, default: false }
}
export default {
name: 'RepositoryDateTimeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: String,
canEdit: { type: Boolean, default: false }
}
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<div class="flex flex-col gap2">
<div class="flex flex-col gap-2">
<DateTimeComponent
mode="date"
:colVal="colVal"
@ -12,7 +12,7 @@
</template>
<script>
import DateTimeComponent from './date_time_component.vue';
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryDateValue',
@ -26,5 +26,5 @@ export default {
editingField: null,
canEdit: { type: Boolean, default: false }
}
}
};
</script>

View file

@ -35,13 +35,13 @@
</template>
<script>
import SelectSearch from "../../shared/legacy/select_search.vue";
import repositoryValueMixin from "./mixins/repository_value.js";
import SelectSearch from '../../shared/legacy/select_search.vue';
import repositoryValueMixin from './mixins/repository_value.js';
export default {
name: "RepositoryListValue",
name: 'RepositoryListValue',
components: {
"select-search": SelectSearch
'select-search': SelectSearch
},
mixins: [repositoryValueMixin],
props: {
@ -51,7 +51,7 @@ export default {
colVal: Object,
optionsPath: String,
permissions: null,
inArchivedRepositoryRow: Boolean,
inArchivedRepositoryRow: Boolean
},
data() {
return {
@ -69,9 +69,9 @@ export default {
mounted() {
this.isLoading = true;
$.get(this.optionsPath, data => {
$.get(this.optionsPath, (data) => {
if (Array.isArray(data)) {
this.options = data.map(option => {
this.options = data.map((option) => {
const { value, label } = option;
return [value, label];
});

View file

@ -43,20 +43,20 @@
</template>
<script>
import repositoryValueMixin from "./mixins/repository_value.js";
import Textarea from "../../shared/legacy/Textarea.vue";
import repositoryValueMixin from './mixins/repository_value.js';
import Textarea from '../../shared/legacy/Textarea.vue';
export default {
name: "RepositoryNumberValue",
name: 'RepositoryNumberValue',
mixins: [repositoryValueMixin],
components: {
'text-area': Textarea,
'text-area': Textarea
},
data() {
return {
expandable: false,
collapsed: true,
numberValue: '',
numberValue: ''
};
},
props: {
@ -66,7 +66,16 @@ export default {
colVal: Number,
permissions: null,
decimals: { type: Number, default: 0 },
canEdit: { type: Boolean, default: false },
canEdit: { type: Boolean, default: false }
},
mounted() {
const maxCollapsedHeight = 60;
const numberRefEl = this.$refs.numberRef;
this.$nextTick(() => {
if (!numberRefEl) return;
const isExpandable = numberRefEl.scrollHeight > maxCollapsedHeight;
this.expandable = isExpandable;
});
},
methods: {
toggleCollapse() {
@ -76,7 +85,7 @@ export default {
},
toggleExpandableState(expandable) {
this.expandable = expandable;
},
}
}
};
</script>

View file

@ -36,14 +36,14 @@
</template>
<script>
import SelectSearch from "../../shared/legacy/select_search.vue";
import repositoryValueMixin from "./mixins/repository_value.js";
import twemoji from "twemoji";
import twemoji from 'twemoji';
import SelectSearch from '../../shared/legacy/select_search.vue';
import repositoryValueMixin from './mixins/repository_value.js';
export default {
name: "RepositoryStatusValue",
name: 'RepositoryStatusValue',
components: {
"select-search": SelectSearch
'select-search': SelectSearch
},
mixins: [repositoryValueMixin],
data() {
@ -63,7 +63,7 @@ export default {
colVal: Object,
optionsPath: String,
permissions: null,
inArchivedRepositoryRow: Boolean,
inArchivedRepositoryRow: Boolean
},
created() {
if (!this.colVal) return;
@ -75,9 +75,9 @@ export default {
mounted() {
this.isLoading = true;
$.get(this.optionsPath, data => {
$.get(this.optionsPath, (data) => {
if (Array.isArray(data)) {
this.options = data.map(option => {
this.options = data.map((option) => {
const { value, label } = option;
return [value, label];
});
@ -103,7 +103,7 @@ export default {
},
replaceEmojiesInDropdown() {
setTimeout(() => {
twemoji.size = "24x24";
twemoji.size = '24x24';
twemoji.base = '/images/twemoji/';
twemoji.parse(this.$refs.container);
}, 300);

View file

@ -1,7 +1,10 @@
<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 h-[20px]">
<span class="truncate w-full inline-block pr-[50px]" :title="colName">{{ colName }}</span>
<div class="font-inter text-sm font-semibold leading-5 relative h-[20px] flex flex-row">
<div class="flex flex-row gap-1">
<span class="truncate w-fit inline-block" :title="colName">{{ colName }}</span>
<div v-if="values?.reminder" class="bg-sn-alert-passion w-1.5 h-1.5 min-w-[0.375rem] min-h-[0.375rem] rounded hover:cursor-pointer" :title="values.reminder_text"></div>
</div>
<a style="text-decoration: none;" class="absolute right-0 btn-text-link font-normal export-consumption-button"
v-if="permissions?.can_export_repository_stock === true && values?.stock_formatted" :data-rows="JSON.stringify([repositoryRowId])"
:data-object-id="repositoryId">
@ -22,68 +25,66 @@
<div v-else class="font-inter text-sm font-normal leading-5" :class="{ 'text-sn-dark-grey': !canEdit, 'text-sn-grey': canEdit }">
{{ i18n.t(`repositories.item_card.repository_stock_value.${canEdit ? 'placeholder' : 'no_stock'}`) }}
</div>
<span class="absolute right-2 reminder" :class="{ 'top-1.5': canEdit, 'top-0': !canEdit, hidden: !values?.reminder }">
<Reminder :value="values" />
</span>
</a>
</div>
</template>
<script>
import Reminder from '../reminder.vue';
export default {
name: 'RepositoryStockValue',
components: {
Reminder
import Reminder from '../reminder.vue';
export default {
name: 'RepositoryStockValue',
components: {
Reminder
},
computed: {
editableClassName() {
const className = 'border-solid border-[1px] p-2 pl-3 manage-repository-stock-value-link sci-cursor-edit';
if (this.canEdit && this.isEditing) return `${className} border-sn-science-blue`;
if (this.canEdit) return `${className} border-sn-light-grey hover:border-sn-sleepy-grey`;
return '';
}
},
data() {
return {
stock_formatted: null,
stock_amount: null,
low_stock_threshold: null,
isEditing: false,
values: null
};
},
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
repositoryId: Number,
repositoryRowId: null,
permissions: null,
canEdit: { type: Boolean, default: false },
actions: null
},
mounted() {
this.values = this.colVal || {};
this.values.stock_url = this.actions?.stock_value_url;
window.manageStockCallback = this.submitCallback;
},
unmounted() {
delete window.manageStockCallback;
},
methods: {
enableEditing() {
this.isEditing = true;
const $this = this;
// disable edit
$('#manageStockValueModal').on('hide.bs.modal', () => {
$this.isEditing = false;
});
},
computed: {
editableClassName() {
const className = 'border-solid border-[1px] p-2 pl-3 manage-repository-stock-value-link sci-cursor-edit'
if (this.canEdit && this.isEditing) return `${className} border-sn-science-blue`;
if (this.canEdit) return `${className} border-sn-light-grey hover:border-sn-sleepy-grey`;
return ''
}
},
data() {
return {
stock_formatted: null,
stock_amount: null,
low_stock_threshold: null,
isEditing: false,
values: null
}
},
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
repositoryId: Number,
repositoryRowId: null,
permissions: null,
canEdit: { type: Boolean, default: false },
actions: null
},
mounted() {
this.values = this.colVal || {};
this.values.stock_url = this.actions?.stock_value_url
window.manageStockCallback = this.submitCallback;
},
unmounted(){
delete window.manageStockCallback
},
methods: {
enableEditing(){
this.isEditing = true
const $this = this;
// disable edit
$('#manageStockValueModal').on('hide.bs.modal', function() {
$this.isEditing = false;
})
},
submitCallback(values) {
if (values) this.values = values;
}
submitCallback(values) {
if (values) this.values = values;
}
}
};
</script>

View file

@ -45,20 +45,20 @@
</template>
<script>
import repositoryValueMixin from "./mixins/repository_value.js";
import Textarea from "../../shared/legacy/Textarea.vue";
import repositoryValueMixin from './mixins/repository_value.js';
import Textarea from '../../shared/legacy/Textarea.vue';
export default {
name: "RepositoryTextValue",
name: 'RepositoryTextValue',
mixins: [repositoryValueMixin],
components: {
'text-area': Textarea,
'text-area': Textarea
},
data() {
return {
expandable: false,
collapsed: true,
textValue: '',
textValue: ''
};
},
props: {
@ -70,7 +70,16 @@ export default {
},
created() {
// constants
this.noContentPlaceholder = this.i18n.t("repositories.item_card.repository_text_value.no_text");
this.noContentPlaceholder = this.i18n.t('repositories.item_card.repository_text_value.no_text');
},
mounted() {
const maxCollapsedHeight = 60;
const textRefEl = this.$refs.textRef;
this.$nextTick(() => {
if (!textRefEl) return;
const isExpandable = textRefEl.scrollHeight > maxCollapsedHeight;
this.expandable = isExpandable;
});
},
methods: {
toggleCollapse() {
@ -80,7 +89,7 @@ export default {
},
toggleExpandableState(expandable) {
this.expandable = expandable;
},
},
}
}
};
</script>

View file

@ -13,18 +13,19 @@
</template>
<script>
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryTimeRangeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: null,
editingField: null,
canEdit: { type: Boolean, default: false }
}
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryTimeRangeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: null,
editingField: null,
canEdit: { type: Boolean, default: false }
}
};
</script>

View file

@ -12,18 +12,19 @@
</template>
<script>
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryTimeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: null,
editingField: null,
canEdit: { type: Boolean, default: false }
}
}
import DateTimeComponent from './date_time_component.vue';
export default {
name: 'RepositoryTimeValue',
components: { DateTimeComponent },
props: {
data_type: String,
colId: Number,
colName: String,
colVal: Object,
updatePath: null,
editingField: null,
canEdit: { type: Boolean, default: false }
}
};
</script>

View file

@ -21,11 +21,49 @@
</template>
<script>
const items = [
{
id: 'highlight-item-1',
textId: 'text-item-1',
labelAlias: 'information_label',
label: 'information-label',
sectionId: 'information-section'
},
{
id: 'highlight-item-2',
textId: 'text-item-2',
labelAlias: 'custom_columns_label',
label: 'custom-columns-label',
sectionId: 'custom-columns-section'
},
{
id: 'highlight-item-3',
textId: 'text-item-3',
labelAlias: 'relationships_label',
label: 'relationships-label',
sectionId: 'relationships-section'
},
{
id: 'highlight-item-4',
textId: 'text-item-4',
labelAlias: 'assigned_label',
label: 'assigned-label',
sectionId: 'assigned-section'
},
{
id: 'highlight-item-5',
textId: 'text-item-5',
labelAlias: 'QR_label',
label: 'QR-label',
sectionId: 'qr-section'
}
];
export default {
name: 'ScrollSpy',
props: {
itemsToCreate: Array,
itemsToCreate: { type: Array, default: () => items },
initialSectionId: String || null
},
data() {
@ -37,25 +75,23 @@ export default {
thresholds: [],
navigationItemsStatus: [], // highlighted or not
scrollPosition: null,
centerOfScrollThumb: null,
centerOfScrollThumb: null
};
},
mounted() {
window.addEventListener('resize', this.handleResize);
this.initializeComponent();
this.$nextTick(() => {
this.calculateAllSectionsCumulativeHeight()
this.calculateAllSectionsCumulativeHeight();
this.calculateSectionsHeight();
this.constructThresholds()
this.handleScroll()
this.constructThresholds();
this.handleScroll();
if (!this.initialSectionId) {
this.navigateToSection(this.itemsToCreate[0])
}
else {
const itemToNavigateTo = this.itemsToCreate.find((item) => item.sectionId === this.initialSectionId)
this.navigateToSection(itemToNavigateTo)
this.navigateToSection(this.itemsToCreate[0]);
} else {
const itemToNavigateTo = this.itemsToCreate.find((item) => item.sectionId === this.initialSectionId);
this.navigateToSection(itemToNavigateTo);
}
});
},
@ -64,12 +100,11 @@ export default {
window.removeEventListener('resize', this.handleResize);
this.removeScrollListener();
},
methods: {
initializeComponent() {
const bodyWrapperEl = document.getElementById('body-wrapper')
const scrollSpyContentEl = document.getElementById('scrollSpyContent')
this.bodyContainerEl = bodyWrapperEl
const bodyWrapperEl = document.getElementById('body-wrapper');
const scrollSpyContentEl = document.getElementById('scrollSpyContent');
this.bodyContainerEl = bodyWrapperEl;
this.sections = Array.from(scrollSpyContentEl.querySelectorAll('section[id]'));
this.navigationItemsStatus = Array(this.sections.length).fill(false);
this.navigationItemsStatus[0] = true;
@ -85,18 +120,18 @@ export default {
},
calculateAllSectionsCumulativeHeight() {
let totalHeight = 0
let totalHeight = 0;
this.itemsToCreate.forEach((item) => {
const sectionEl = document.getElementById(item.sectionId);
totalHeight += sectionEl.offsetHeight
})
this.allSectionsCumulativeHeight = totalHeight
totalHeight += sectionEl.offsetHeight;
});
this.allSectionsCumulativeHeight = totalHeight;
},
calculateSectionsHeight() {
// Initialize an array to store the height data for each section
this.sectionsWithHeight = this.itemsToCreate.map(item => {
this.sectionsWithHeight = this.itemsToCreate.map((item) => {
// Find the DOM element for the section
const sectionEl = document.getElementById(item.sectionId);
@ -107,8 +142,8 @@ export default {
// Return an object containing the section ID and its percentage height
return {
sectionId: item.sectionId,
heightPx: heightPx,
percentHeight: percentHeight
heightPx,
percentHeight
};
});
},
@ -118,57 +153,55 @@ export default {
// on the % of vertical space of scrollable content that they occupy
constructThresholds() {
const scrollableArea = this.bodyContainerEl;
const deltaTravel = scrollableArea.scrollHeight - scrollableArea.clientHeight
const deltaTravel = scrollableArea.scrollHeight - scrollableArea.clientHeight;
const viewportHeight = scrollableArea.clientHeight;
const scrollableAreaHeight = scrollableArea.scrollHeight;
const scrollThumbHeight = Math.round(viewportHeight / scrollableAreaHeight * viewportHeight);
const scrollThumbCenter = Math.round(scrollThumbHeight / 2)
this.centerOfScrollThumb = scrollThumbCenter
this.scrollPosition = scrollThumbCenter
const scrollThumbCenter = Math.round(scrollThumbHeight / 2);
this.centerOfScrollThumb = scrollThumbCenter;
this.scrollPosition = scrollThumbCenter;
let prevThreshold = scrollThumbCenter
let prevThreshold = scrollThumbCenter;
for (let i = 0; i < this.sectionsWithHeight.length; i++) {
// first section
if (i === 0) {
const from = prevThreshold
const to = Math.round(deltaTravel * this.sectionsWithHeight[i].percentHeight / 100) + prevThreshold
const id = this.sectionsWithHeight[i].sectionId
prevThreshold = to + 1
const from = prevThreshold;
const to = Math.round(deltaTravel * this.sectionsWithHeight[i].percentHeight / 100) + prevThreshold;
const id = this.sectionsWithHeight[i].sectionId;
prevThreshold = to + 1;
const threshold = {
id,
index: i,
from,
to
}
this.thresholds[i] = threshold
}
// last section
else if (i === this.sectionsWithHeight.length - 1) {
const from = prevThreshold
const to = scrollableArea.scrollHeight
const id = this.sectionsWithHeight[i].sectionId
};
this.thresholds[i] = threshold;
} else if (i === this.sectionsWithHeight.length - 1) {
// last section
const from = prevThreshold;
const to = scrollableArea.scrollHeight;
const id = this.sectionsWithHeight[i].sectionId;
const threshold = {
id,
index: i,
from,
to
}
this.thresholds[i] = threshold
}
else {
};
this.thresholds[i] = threshold;
} else {
// other sections
const from = prevThreshold
const to = Math.round(deltaTravel * this.sectionsWithHeight[i].percentHeight / 100) + prevThreshold - 1
const id = this.sectionsWithHeight[i].sectionId
prevThreshold = to + 1
const from = prevThreshold;
const to = Math.round(deltaTravel * this.sectionsWithHeight[i].percentHeight / 100) + prevThreshold - 1;
const id = this.sectionsWithHeight[i].sectionId;
prevThreshold = to + 1;
const threshold = {
id,
index: i,
from,
to
}
this.thresholds[i] = threshold
};
this.thresholds[i] = threshold;
}
}
},
@ -187,26 +220,24 @@ export default {
this.removeScrollListener();
const scrollableArea = this.bodyContainerEl;
const foundThreshold = this.thresholds.find((obj) => obj.id === navigationItem.sectionId)
const domElToScrollTo = document.getElementById(navigationItem.label)
const foundThreshold = this.thresholds.find((obj) => obj.id === navigationItem.sectionId);
const domElToScrollTo = document.getElementById(navigationItem.label);
if (foundThreshold.index === 0) {
// scroll to top
this.bodyContainerEl.scrollTo({
top: 0,
behavior: "auto"
behavior: 'auto'
});
}
else if (foundThreshold.index === this.thresholds.length - 1) {
} else if (foundThreshold.index === this.thresholds.length - 1) {
// scroll to bottom
this.bodyContainerEl.scrollTo({
top: 99999,
behavior: "auto"
behavior: 'auto'
});
}
else {
} else {
// scroll to the start of a section's threshold, adjusted for the center thumb value (true center)
scrollableArea.scrollTop = foundThreshold.from - this.centerOfScrollThumb
scrollableArea.scrollTop = foundThreshold.from - this.centerOfScrollThumb;
}
this.flashTitleColor(domElToScrollTo);
@ -215,7 +246,7 @@ export default {
},
flashTitleColor(domEl) {
if (!domEl) return
if (!domEl) return;
domEl.classList.add('text-sn-science-blue');
setTimeout(() => domEl.classList.remove('text-sn-science-blue'), 300);
@ -223,9 +254,9 @@ export default {
handleResize() {
this.$nextTick(() => {
this.calculateAllSectionsCumulativeHeight()
this.calculateAllSectionsCumulativeHeight();
this.calculateSectionsHeight();
this.constructThresholds()
this.constructThresholds();
});
},
@ -247,7 +278,7 @@ export default {
this.navigationItemsStatus[index] = true;
}
});
},
}
}
}
};
</script>

View file

@ -19,8 +19,8 @@ export default {
return {
showTop: false,
showImage: false,
imageLoading: false,
}
imageLoading: false
};
},
props: {
tooltipId: String,
@ -31,8 +31,8 @@ export default {
medium_preview_url: String || null,
defaultLoaderHeight: {
type: Number,
default: 254,
},
default: 254
}
},
methods: {
onImageLoaded(event) {
@ -53,11 +53,11 @@ export default {
const rect = el.parentElement.getBoundingClientRect();
return (
(rect.bottom + height) <=
(window.innerHeight || document.documentElement.clientHeight)
(rect.bottom + height)
<= (window.innerHeight || document.documentElement.clientHeight)
);
}
}
}
};
</script>

View file

@ -1,13 +1,16 @@
<template>
<div ref="dateTimeRangeOverlay"
class="fixed top-0 left-0 right-0 bottom-0 bg-transparent z-[99] hidden">
</div>
<div class="flex gap-1">
<div class="text-sm font-bold truncate" :title="colName">
{{ colName }}
</div>
<div v-if="colVal.reminder" class="bg-sn-alert-passion w-1.5 h-1.5 rounded" :title="colVal.reminder_text"></div>
<div v-if="colVal.reminder" class="bg-sn-alert-passion w-1.5 h-1.5 min-w-[0.375rem] min-h-[0.375rem] rounded hover:cursor-pointer" :title="colVal.reminder_text"></div>
</div>
<div class="flex flex-col gap-2">
<template v-if="!canEdit">
<span v-if="range">
<span v-if="range" class="text-sn-dark-grey">
<template v-if="colVal.start_time && colVal.end_time">
{{ colVal.start_time.formatted }} - {{ colVal.end_time.formatted }}
</template>
@ -15,7 +18,7 @@
{{ viewPlaceholder }}
</template>
</span>
<span v-else >
<span v-else class="text-sn-dark-grey">
<template v-if="colVal.formatted">
{{ colVal.formatted }}
</template>
@ -27,11 +30,11 @@
<template v-else>
<div>
<span class="text-xs capitalize" v-if="range">{{ i18n.t('general.from') }}</span>
<DateTimePicker :defaultValue="defaultStartDate" @closed="update" @change="updateStartDate" :mode="mode" :placeholder="placeholder" :clearable="true"/>
<DateTimePicker :defaultValue="defaultStartDate" @closed="update" @change="updateStartDate" :mode="mode" :placeholder="placeholder" :clearable="true" ref="dateTimePickerFrom"/>
</div>
<div>
<span class="text-xs capitalize" v-if="range">{{ i18n.t('general.to') }}</span>
<DateTimePicker :defaultValue="defaultEndDate" @closed="update" v-if="range" @change="updateEndDate" :placeholder="placeholder" :mode="mode" :clearable="true"/>
<DateTimePicker :defaultValue="defaultEndDate" @closed="update" v-if="range" @change="updateEndDate" :placeholder="placeholder" :mode="mode" :clearable="true" ref="dateTimePickerTo"/>
</div>
<div class="text-xs text-sn-delete-red" v-if="error">{{ error }}</div>
</template>
@ -39,156 +42,232 @@
</template>
<script>
import DateTimePicker from '../../shared/date_time_picker.vue';
import Reminder from '../reminder.vue';
import DateTimePicker from '../../shared/date_time_picker.vue';
import Reminder from '../reminder.vue';
export default {
name: 'DateTimeComponent',
components: {
DateTimePicker,
Reminder
},
data() {
return {
startDate: null,
endDate: null,
error: null,
defaultStartDate: null,
defaultEndDate: null,
}
},
inject: ['reloadRepoItemSidebar'],
props: {
mode: String,
range: { type: Boolean, default: false },
colVal: { type: Object, default: {} },
colId: Number,
updatePath: String,
canEdit: { type: Boolean, default: false },
colName: String,
},
created() {
if (this.range) {
if (this.colVal.start_time?.datetime) this.startDate = new Date(this.colVal.start_time.datetime)
if (this.colVal.end_time?.datetime) this.endDate = new Date(this.colVal.end_time.datetime)
} else {
if (this.colVal.datetime) this.startDate = new Date(this.colVal.datetime)
}
this.defaultStartDate = this.startDate;
this.defaultEndDate = this.endDate;
},
computed: {
value: {
get () {
if (this.range) {
if (!(this.startDate instanceof Date) && !(this.endDate instanceof Date)) return null;
return {
start_time: this.formatDate(this.startDate),
end_time: this.formatDate(this.endDate)
};
} else {
if (!(this.startDate instanceof Date)) return null;
return this.formatDate(this.startDate);
}
},
},
placeholder() {
switch (this.mode) {
case 'date':
return this.i18n.t('repositories.item_card.repository_date_value.placeholder');
case 'time':
return this.i18n.t('repositories.item_card.repository_time_value.placeholder');
case 'datetime':
return this.i18n.t('repositories.item_card.repository_date_time_value.placeholder');
}
},
viewPlaceholder() {
switch (this.mode) {
case 'date':
if (this.range) {
return this.i18n.t('repositories.item_card.repository_date_range_value.no_date_range');
}
return this.i18n.t('repositories.item_card.repository_date_value.no_date');
case 'time':
if (this.range) {
return this.i18n.t('repositories.item_card.repository_time_range_value.no_time_range');
}
return this.i18n.t('repositories.item_card.repository_time_value.no_time');
case 'datetime':
if (this.range) {
return this.i18n.t('repositories.item_card.repository_date_time_range_value.no_date_time_range');
}
return this.i18n.t('repositories.item_card.repository_date_time_value.no_date_time');
}
}
},
methods: {
updateStartDate(date) {
this.startDate = date;
if (!(this.startDate instanceof Date)) this.update();
},
updateEndDate(date) {
this.endDate = date;
if (!(this.endDate instanceof Date)) this.update();
},
validateValue() {
this.error = null;
// Date is not changed
if (this.defaultStartDate == this.startDate && this.defaultEndDate == this.endDate) return false;
export default {
name: 'DateTimeComponent',
components: {
DateTimePicker,
Reminder
},
data() {
return {
startDate: null,
endDate: null,
error: null,
defaultStartDate: null,
defaultEndDate: null,
fromPicker: null,
toPicker: null
};
},
inject: ['reloadRepoItemSidebar'],
props: {
mode: String,
range: { type: Boolean, default: false },
colVal: { type: Object, default: {} },
colId: Number,
updatePath: String,
canEdit: { type: Boolean, default: false },
colName: String
},
created() {
if (this.range) {
if (this.colVal.start_time?.datetime) this.startDate = new Date(this.colVal.start_time.datetime);
if (this.colVal.end_time?.datetime) this.endDate = new Date(this.colVal.end_time.datetime);
} else if (this.colVal.datetime) this.startDate = new Date(this.colVal.datetime);
this.defaultStartDate = this.startDate;
this.defaultEndDate = this.endDate;
},
mounted() {
this.fromPicker = this.$refs.dateTimePickerFrom;
this.toPicker = this.$refs.dateTimePickerTo;
document.addEventListener('click', this.logClick);
document.addEventListener('keydown', this.handleKeyDown);
},
beforeUnmount() {
this.fromPicker = null;
this.toPicker = null;
document.removeEventListener('click', this.logClick);
document.removeEventListener('keydown', this.handleKeyDown);
},
computed: {
value: {
get() {
if (this.range) {
// Both empty
if (!(this.startDate instanceof Date) && !(this.endDate instanceof Date)) return true;
if (!(this.startDate instanceof Date) && !(this.endDate instanceof Date)) return null;
// One empty
if (!(this.startDate instanceof Date) || !(this.endDate instanceof Date)) {
this.error = this.i18n.t('repositories.item_card.date_time.errors.not_valid_range')
return false;
}
return {
start_time: this.formatDate(this.startDate),
end_time: this.formatDate(this.endDate)
};
}
if (!(this.startDate instanceof Date)) return null;
// Start date is after end date
if (this.startDate > this.endDate) {
this.error = this.i18n.t('repositories.item_card.date_time.errors.not_valid_range')
return false;
return this.formatDate(this.startDate);
}
},
placeholder() {
switch (this.mode) {
case 'date':
return this.i18n.t('repositories.item_card.repository_date_value.placeholder');
case 'time':
return this.i18n.t('repositories.item_card.repository_time_value.placeholder');
case 'datetime':
return this.i18n.t('repositories.item_card.repository_date_time_value.placeholder');
}
},
viewPlaceholder() {
switch (this.mode) {
case 'date':
if (this.range) {
return this.i18n.t('repositories.item_card.repository_date_range_value.no_date_range');
}
return this.i18n.t('repositories.item_card.repository_date_value.no_date');
case 'time':
if (this.range) {
return this.i18n.t('repositories.item_card.repository_time_range_value.no_time_range');
}
return this.i18n.t('repositories.item_card.repository_time_value.no_time');
case 'datetime':
if (this.range) {
return this.i18n.t('repositories.item_card.repository_date_time_range_value.no_date_time_range');
}
return this.i18n.t('repositories.item_card.repository_date_time_value.no_date_time');
}
}
},
methods: {
updateStartDate(date) {
this.startDate = date;
if (!(this.startDate instanceof Date)) this.update();
},
updateEndDate(date) {
this.endDate = date;
if (!(this.endDate instanceof Date)) this.update();
},
validateValue() {
this.error = null;
// Date is not changed
if (this.defaultStartDate === this.startDate && this.defaultEndDate === this.endDate) return false;
if (this.range) {
// Both empty
if (!(this.startDate instanceof Date) && !(this.endDate instanceof Date)) return true;
// One empty
if (!(this.startDate instanceof Date) || !(this.endDate instanceof Date)) {
this.error = this.i18n.t('repositories.item_card.date_time.errors.not_valid_range');
return false;
}
return true
},
update() {
const params = {}
// Start date is after end date
if (this.startDate > this.endDate) {
this.error = this.i18n.t('repositories.item_card.date_time.errors.not_valid_range');
return false;
}
}
if (!this.validateValue()) return;
return true;
},
update() {
const params = {};
params[this.colId] = this.value
$.ajax({
method: 'PUT',
url: this.updatePath,
dataType: 'json',
data: { repository_cells: params },
success: () => {
this.defaultStartDate = this.startDate;
this.defaultEndDate = this.endDate;
if ($('.dataTable')[0]) {
$('.dataTable').DataTable().ajax.reload(null, false);
}
this.reloadRepoItemSidebar();
if (!this.validateValue()) return;
params[this.colId] = this.value;
$.ajax({
method: 'PUT',
url: this.updatePath,
dataType: 'json',
data: { repository_cells: params },
success: () => {
this.defaultStartDate = this.startDate;
this.defaultEndDate = this.endDate;
if ($('.dataTable.repository-dataTable')[0]) {
$('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
}
});
},
formatDate(date) {
if (!(date instanceof Date)) return null;
this.reloadRepoItemSidebar();
}
});
},
formatDate(date) {
if (!(date instanceof Date)) return null;
const y = date.getFullYear();
const m = date.getMonth() + 1;
const d = date.getDate();
const hours = date.getHours();
const mins = date.getMinutes();
return `${y}/${m}/${d} ${hours}:${mins}`;
},
const y = date.getFullYear();
const m = date.getMonth() + 1;
const d = date.getDate();
const hours = date.getHours();
const mins = date.getMinutes();
return `${y}/${m}/${d} ${hours}:${mins}`;
},
showOverlay() {
const overlay = this.$refs.dateTimeRangeOverlay;
overlay.classList.remove('hidden');
},
hideOverlay() {
const overlay = this.$refs.dateTimeRangeOverlay;
overlay.classList.add('hidden');
},
preventBodyScrolling() {
document.body.classList.add('overflow-hidden');
document.body.classList.remove('overflow-auto');
},
allowBodyScrolling() {
document.body.classList.remove('overflow-hidden');
document.body.classList.add('overflow-auto');
},
bringCalendarToFront() {
const calendarEl = document.querySelector('.dp__instance_calendar');
calendarEl.classList.add('z-[9999]');
},
focusClearedInput() {
if (!this.fromPicker.datetime) {
const fromInput = this.fromPicker.$el.querySelector('input');
fromInput.focus();
fromInput.click();
}
if (!this.toPicker.datetime) {
const toInput = this.toPicker.$el.querySelector('input');
toInput.focus();
toInput.click();
}
this.preventBodyScrolling();
this.showOverlay();
this.$nextTick(() => {
this.bringCalendarToFront();
});
},
logClick() {
if (this.error) this.focusClearedInput();
},
handleKeyDown(event) {
if (event.key === 'Escape' || (event.ctrlKey && event.key === 'z')) {
if (this.startDate === null && this.fromPicker) {
this.startDate = this.defaultStartDate;
this.fromPicker.datetime = this.defaultStartDate;
this.error = null;
}
if (this.endDate === null && this.toPicker) {
this.endDate = this.defaultEndDate;
this.toPicker.datetime = this.defaultEndDate;
this.error = null;
}
}
}
},
watch: {
error(newVal) {
if (newVal !== null) {
this.focusClearedInput();
} else {
this.hideOverlay();
this.allowBodyScrolling();
}
}
}
};
</script>

View file

@ -0,0 +1,48 @@
<template>
<div ref="modal" @keydown.esc="cancel" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" @click="cancel" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<h4 class="modal-title">
{{ i18n.t('repositories.item_card.relationships.unlink_modal.title') }}
</h4>
</div>
<div class="modal-body">
<p>{{ i18n.t('repositories.item_card.relationships.unlink_modal.description') }}</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" @click="confirm">{{ i18n.t('repositories.item_card.relationships.unlink_modal.unlink') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'UnlinkModal',
mounted() {
$(this.$refs.modal).modal('show');
},
methods: {
cancel() {
this.hide(() => {
this.$emit('cancel');
});
},
confirm() {
this.hide(() => {
this.$emit('unlink');
});
},
hide(callback) {
$(this.$refs.modal).one('hidden.bs.modal', () => {
callback();
});
$(this.$refs.modal).modal('hide');
}
}
};
</script>

View file

@ -5,17 +5,17 @@
<div v-if="availablePrinters.length > 0" class="printers-available">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<p class="modal-title">
<template v-if="rows.length == 1">
<b>{{ i18n.t('repository_row.modal_print_label.head_title', {repository_row: rows[0].attributes.name}) }}</b>
<div class="modal-title">
<div v-if="rows.length == 1" class="flex flex-row">
<div class="font-bold">{{ i18n.t('repository_row.modal_print_label.head_title', {repository_row: rows[0].attributes.name}) }}</div>
<span class="id-label">
{{ i18n.t('repository_row.modal_print_label.id_label', {repository_row_id: rows[0].attributes.code}) }}
</span>
</template>
<template v-else>
<b>{{ i18n.t('repository_row.modal_print_label.head_title_multiple', {repository_rows: rows.length}) }}</b>
</template>
</p>
</div>
<div v-else>
<div class="font-bold">{{ i18n.t('repository_row.modal_print_label.head_title_multiple', {repository_rows: rows.length}) }}</div>
</div>
</div>
</div>
<div class="modal-body">
<div class=printers-container>
@ -91,174 +91,178 @@
</template>
<script>
import DropdownSelector from '../shared/legacy/dropdown_selector.vue'
import LabelPreview from '../label_template/components/label_preview.vue'
import DropdownSelector from '../shared/legacy/dropdown_selector.vue';
import LabelPreview from '../label_template/components/label_preview.vue';
export default {
name: 'PrintModalContainer',
props: {
showModal: Boolean,
row_ids: Array,
urls: Object
export default {
name: 'PrintModalContainer',
props: {
showModal: Boolean,
row_ids: Array,
repository_id: Number,
urls: Object
},
data() {
return {
rows: [],
printers: [],
templates: [],
selectedPrinter: null,
selectedTemplate: null,
copies: 1,
zebraPrinters: null,
labelTemplateError: null,
labelTemplateCode: null
};
},
components: {
DropdownSelector,
LabelPreview
},
mounted() {
$.get(this.urls.labelTemplates, (result) => {
this.templates = result.data;
this.selectDefaultLabelTemplate();
});
$.get(this.urls.printers, (result) => {
this.printers = result.data;
});
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.copies = 1;
this.zebraPrinters = null;
this.$emit('close');
});
},
computed: {
availableTemplates() {
let { templates } = this;
if (this.selectedPrinter && this.selectedPrinter.attributes.type_of === 'zebra') {
templates = templates.filter((i) => i.attributes.type === 'ZebraLabelTemplate');
}
return templates.map((i) => ({
value: i.id,
label: i.attributes.name,
params: {
icon: i.attributes.icon_url,
description: i.attributes.description || ''
}
})).sort((temp1, temp2) => (temp1.label?.toLowerCase() > temp2.label?.toLowerCase() ? 1 : -1));
},
data() {
return {
rows: [],
printers: [],
templates: [],
selectedPrinter: null,
selectedTemplate: null,
copies: 1,
zebraPrinters: null,
labelTemplateError: null,
labelTemplateCode: null
availablePrinters() {
return this.printers.map((i) => ({
value: i.id,
label: i.attributes.display_name
}));
}
},
watch: {
showModal() {
if (this.showModal) {
this.initZebraPrinter();
$(this.$refs.modal).modal('show');
this.validateTemplate();
}
},
components: {
DropdownSelector,
LabelPreview
row_ids() {
$.get(this.urls.rows, { repository_id: this.repository_id, row_ids: this.row_ids }, (result) => {
this.rows = result.data;
});
}
},
methods: {
selectDefaultLabelTemplate() {
if (this.selectedPrinter && this.templates) {
const template = this.templates.find((i) => i.attributes.default
&& i.type.includes(this.selectedPrinter.attributes.type_of));
if (template) {
this.$nextTick(() => {
this.$refs.labelTemplateDropdown.selectValues(template.id);
});
}
}
},
mounted() {
$.get(this.urls.labelTemplates, (result) => {
this.templates = result.data
this.selectDefaultLabelTemplate();
})
selectPrinter(value) {
this.selectedPrinter = this.printers.find((i) => i.id === value);
this.selectDefaultLabelTemplate();
},
selectTemplate(value) {
this.selectedTemplate = this.templates.find((i) => i.id === value);
this.validateTemplate();
},
validateTemplate() {
if (!this.selectedTemplate || this.row_ids.length == 0) return;
$.get(this.urls.printers, (result) => {
this.printers = result.data
})
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.zebraPrinters = null;
this.$emit('close');
$.post(this.urls.printValidation, {
repository_id: this.repository_id,
label_template_id: this.selectedTemplate.id,
row_ids: this.row_ids
}, (result) => {
this.labelTemplateError = null;
this.labelTemplateCode = result.label_code;
}).fail((result) => {
this.labelTemplateError = result.responseJSON.error;
this.labelTemplateCode = result.responseJSON.label_code;
});
},
computed: {
availableTemplates() {
let templates = this.templates;
if (this.selectedPrinter && this.selectedPrinter.attributes.type_of === 'zebra') {
templates = templates.filter(i => i.attributes.type === 'ZebraLabelTemplate')
}
return templates.map(i => {
return {
value: i.id,
label: i.attributes.name,
params: {
icon: i.attributes.icon_url,
description: i.attributes.description || ''
}
}
}).sort((temp1, temp2) => (temp1.label?.toLowerCase() > temp2.label?.toLowerCase() ? 1 : -1));
},
availablePrinters() {
return this.printers.map(i => {
return {
value: i.id,
label: i.attributes.display_name
}
})
}
},
watch: {
showModal() {
if (this.showModal) {
this.initZebraPrinter();
$(this.$refs.modal).modal('show');
this.validateTemplate();
}
},
row_ids() {
$.get(this.urls.rows, {rows: this.row_ids}, (result) => {
this.rows = result.data
})
}
},
methods: {
selectDefaultLabelTemplate() {
if (this.selectedPrinter && this.templates) {
let template = this.templates.find(i => i.attributes.default
&& i.type.includes(this.selectedPrinter.attributes.type_of));
if (template) {
this.$nextTick(() => {
this.$refs.labelTemplateDropdown.selectValues(template.id);
});
}
}
},
selectPrinter(value) {
this.selectedPrinter = this.printers.find(i => i.id === value)
this.selectDefaultLabelTemplate();
},
selectTemplate(value) {
this.selectedTemplate = this.templates.find(i => i.id === value);
this.validateTemplate();
},
validateTemplate() {
if (!this.selectedTemplate || this.row_ids.length == 0) return;
$.post(this.urls.printValidation, {label_template_id: this.selectedTemplate.id, rows: this.row_ids}, (result) => {
this.labelTemplateError = null;
this.labelTemplateCode = result.label_code;
}).fail((result) => {
this.labelTemplateError = result.responseJSON.error;
this.labelTemplateCode = result.responseJSON.label_code;
})
},
submitPrint() {
this.$nextTick(() => {
if (this.selectedPrinter.attributes.type_of === 'zebra') {
this.zebraPrinters.print(
this.urls.zebraProgress,
'.label-printing-progress-modal',
'#modal-print-repository-row-label',
{
printer_name: this.selectedPrinter.attributes.name,
number_of_copies: this.copies,
label_template_id: this.selectedTemplate.id,
rows: this.row_ids
}
);
} else {
$.post(this.urls.print, {
rows: this.row_ids,
label_printer_id: this.selectedPrinter.id,
submitPrint() {
this.$nextTick(() => {
if (this.selectedPrinter.attributes.type_of === 'zebra') {
this.zebraPrinters.print(
this.urls.zebraProgress,
'.label-printing-progress-modal',
'#modal-print-repository-row-label',
{
printer_name: this.selectedPrinter.attributes.name,
number_of_copies: this.copies,
label_template_id: this.selectedTemplate.id,
copies: this.copies
}, (data) => {
$(this.$refs.modal).modal('hide');
this.$emit('close');
PrintProgressModal.init(data);
})
}
});
},
initZebraPrinter() {
this.zebraPrinters = zebraPrint.init($('#LabelPrinterSelector'), {
clearSelectorOnFirstDevice: false,
appendDevice: (device) => {
this.printers.push({
id: `zebra${this.printers.length}`,
attributes: {
name: device.name,
display_name: device.name,
type_of: 'zebra'
}
})
}
}, false);
},
templateOption(option) {
return `
row_ids: this.row_ids,
repository_id: this.repository_id
}
);
} else {
$.post(this.urls.print, {
row_ids: this.row_ids,
repository_id: this.repository_id,
label_printer_id: this.selectedPrinter.id,
label_template_id: this.selectedTemplate.id,
copies: this.copies
}, (data) => {
$(this.$refs.modal).modal('hide');
this.$emit('close');
PrintProgressModal.init(data);
});
}
});
},
initZebraPrinter() {
this.zebraPrinters = zebraPrint.init($('#LabelPrinterSelector'), {
clearSelectorOnFirstDevice: false,
appendDevice: (device) => {
this.printers.push({
id: `zebra${this.printers.length}`,
attributes: {
name: device.name,
display_name: device.name,
type_of: 'zebra'
}
});
}
}, false);
},
templateOption(option) {
return `
<div class="label-template-option" data-toggle="tooltip" data-placement="right" title="${option.params.description}">
<img src="${option.params.icon}" style=""></img>
${option.label}
</div>
`
},
initTooltip() {
$('[data-toggle="tooltip"]').tooltip();
}
`;
},
initTooltip() {
$('[data-toggle="tooltip"]').tooltip();
}
}
};
</script>

View file

@ -33,50 +33,50 @@
</template>
<script>
export default {
name: 'ExportStockConsumptionModal',
props: {
exportUrl: { type: String, required: true }
export default {
name: 'ExportStockConsumptionModal',
props: {
exportUrl: { type: String, required: true }
},
data() {
return {
repository: null,
selectedRows: []
};
},
created() {
window.exportStockConsumptionModalComponent = this;
},
beforeUnmount() {
delete window.exportStockConsumptionModalComponent;
},
methods: {
exportConsumption() {
$.post(this.repository.export_consumption_url, { row_ids: this.selectedRows })
.done((data) => {
HelperModule.flashAlertMsg(data.message, 'success');
})
.fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
})
.always(() => {
this.closeModal();
});
},
data() {
return {
repository: null,
selectedRows: []
}
},
created() {
window.exportStockConsumptionModalComponent = this;
},
beforeUnmount() {
delete window.exportStockConsumptionModalComponent;
},
methods: {
exportConsumption() {
$.post(this.repository.export_consumption_url, { row_ids: this.selectedRows })
.done((data) => {
HelperModule.flashAlertMsg(data.message, 'success');
})
.fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
})
.always(() => {
this.closeModal();
});
},
fetchRepositoryData(selectedRows, params) {
this.selectedRows = selectedRows;
$.get(this.exportUrl, params)
.done( (data) => {
fetchRepositoryData(selectedRows, params) {
this.selectedRows = selectedRows;
$.get(this.exportUrl, params)
.done((data) => {
this.repository = data;
this.showModal();
});
},
closeModal() {
$(this.$refs.modal).modal('hide');
},
showModal() {
$(this.$refs.modal).modal('show');
}
},
closeModal() {
$(this.$refs.modal).modal('hide');
},
showModal() {
$(this.$refs.modal).modal('show');
}
}
};
</script>

View file

@ -56,8 +56,13 @@
<label :class="`text-sm font-normal ${errors.unit ? 'text-sn-delete-red' : 'text-sn-grey'}`" for="stock-unit">
{{ i18n.t('repository_stock_values.manage_modal.unit') }}
</label>
<<<<<<< HEAD
<SelectDropdown
:disabled="[2, 3].includes(operation)"
=======
<Select
:disabled="['add', 'remove'].includes(operation)"
>>>>>>> develop
:value="unit"
:options="units"
:placeholder="i18n.t('repository_stock_values.manage_modal.unit_prompt')"
@ -86,8 +91,8 @@
</div>
</div>
</template>
<div class="repository-stock-reminder-selector">
<div class="sci-checkbox-container">
<div class="repository-stock-reminder-selector flex">
<div class="sci-checkbox-container my-auto">
<input type="checkbox" name="reminder-enabled" tabindex="4" class="sci-checkbox" id="reminder-selector-checkbox" :checked="reminderEnabled" @change="reminderEnabled = $event.target.checked"/>
<span class="sci-checkbox-label"></span>
</div>
@ -140,150 +145,153 @@
</template>
<script>
import SelectDropdown from './../shared/select_dropdown.vue';
import Input from '../shared/legacy/input.vue';
import Decimal from 'decimal.js';
import Decimal from 'decimal.js';
import Select from '../shared/legacy/select.vue';
import Input from '../shared/legacy/input.vue';
export default {
name: 'ManageStockValueModal',
components: {
SelectDropdown,
Input
export default {
name: 'ManageStockValueModal',
components: {
Select,
Input
},
data() {
return {
operation: null,
operations: [],
stockValue: null,
amount: '',
repositoryRowName: null,
stockUrl: null,
units: null,
unit: null,
reminderEnabled: false,
lowStockTreshold: null,
comment: null,
errors: {}
};
},
computed: {
unitLabel() {
const currentUnit = this.units?.find((option) => option[0] === this.unit);
return currentUnit ? currentUnit[1] : '';
},
data() {
return {
operation: null,
operations: [],
stockValue: null,
amount: '',
repositoryRowName: null,
stockUrl: null,
units: null,
unit: null,
reminderEnabled: false,
lowStockTreshold: null,
comment: null,
errors: {}
initUnitLabel() {
const unit = this.units?.find((option) => option[0] === this.stockValue?.unit);
return unit ? unit[1] : '';
},
newAmount() {
const currentAmount = new Decimal(this.stockValue?.amount || 0);
const amount = new Decimal(this.amount || 0);
let value;
switch (this.operation) {
case 'add':
value = currentAmount.plus(amount);
break;
case 'remove':
value = currentAmount.minus(amount);
break;
default:
value = amount;
break;
}
return Number(value);
}
},
created() {
window.manageStockModalComponent = this;
},
beforeDestroy() {
delete window.manageStockModalComponent;
},
mounted() {
// Focus stock amount input field
$(this.$refs.modal).on('show.bs.modal', () => {
setTimeout(() => {
$('#stock-amount')[0]?.focus();
}, 500);
});
},
methods: {
setOperation($event) {
if ($event !== this.operation) {
this.amount = null;
}
this.operation = $event;
if (['add', 'remove'].includes($event)) {
this.unit = this.stockValue.unit;
}
},
computed: {
unitLabel: function() {
const currentUnit = this.units?.find(option => option[0] === this.unit);
return currentUnit ? currentUnit[1] : ''
},
initUnitLabel: function() {
const unit = this.units?.find(option => option[0] === this.stockValue?.unit);
return unit ? unit[1] : ''
},
newAmount: function() {
const currentAmount = new Decimal(this.stockValue?.amount || 0);
const amount = new Decimal(this.amount || 0)
let value;
switch (this.operation) {
case 2:
value = currentAmount.plus(amount);
break;
case 3:
value = currentAmount.minus(amount);
break;
default:
value = amount;
break;
fetchStockValueData(stockValueUrl) {
if (!stockValueUrl) return;
$.ajax({
method: 'GET',
url: stockValueUrl,
dataType: 'json',
success: (result) => {
this.repositoryRowName = result.repository_row_name;
this.stockValue = result.stock_value;
this.amount = Number(new Decimal(result.stock_value.amount || 0));
this.units = result.stock_value.units;
this.unit = result.stock_value.unit;
this.reminderEnabled = result.stock_value.reminder_enabled;
this.lowStockTreshold = result.stock_value.low_stock_treshold;
this.operation = 'set';
this.stockUrl = result.stock_url;
/* eslint-disable no-undef */
this.operations = [
['set', `${I18n.t('repository_stock_values.manage_modal.set')}`],
['add', `${I18n.t('repository_stock_values.manage_modal.add')}`],
['remove', `${I18n.t('repository_stock_values.manage_modal.remove')}`]
];
/* eslint-enable no-undef */
this.errors = {};
}
return Number(value)
}
},
created() {
window.manageStockModalComponent = this;
},
beforeDestroy() {
delete window.manageStockModalComponent;
},
mounted() {
// Focus stock amount input field
$(this.$refs.modal).on('show.bs.modal', function() {
setTimeout(() => {
$('#stock-amount')[0]?.focus()
}, 500)
});
},
methods: {
setOperation($event) {
if ($event !== this.operation) {
this.amount = null;
}
this.operation = $event;
if ([2, 3].includes($event)) {
this.unit = this.stockValue.unit;
}
},
fetchStockValueData(stockValueUrl) {
if (!stockValueUrl) return;
$.ajax({
method: 'GET',
url: stockValueUrl,
dataType: 'json',
success: (result) => {
this.repositoryRowName = result.repository_row_name
this.stockValue = result.stock_value
this.amount = Number(new Decimal(result.stock_value.amount || 0))
this.units = result.stock_value.units
this.unit = result.stock_value.unit
this.reminderEnabled = result.stock_value.reminder_enabled
this.lowStockTreshold = result.stock_value.low_stock_treshold
this.operation = 1;
this.stockUrl = result.stock_url;
this.operations = [[1, 'set'], [2, 'add'], [3, 'remove']];
this.errors = {};
}
});
},
closeModal() {
$(this.$refs.modal).modal('hide');
},
showModal(stockValueUrl, closeCallback) {
$(this.$refs.modal).modal('show');
this.fetchStockValueData(stockValueUrl);
this.closeCallback = closeCallback;
},
validateAndsaveStockValue() {
let newErrors = {};
this.errors = newErrors;
if (!this.unit)
newErrors['unit'] = I18n.t('repository_stock_values.manage_modal.unit_error');
if (!this.amount)
newErrors['amount'] = I18n.t('repository_stock_values.manage_modal.amount_error');
if (this.amount && this.amount < 0)
newErrors['amount'] = I18n.t('repository_stock_values.manage_modal.negative_error');
if (this.reminderEnabled && !this.lowStockTreshold)
newErrors['tresholdAmount'] = I18n.t('repository_stock_values.manage_modal.amount_error');
closeModal() {
$(this.$refs.modal).modal('hide');
},
showModal(stockValueUrl, closeCallback) {
$(this.$refs.modal).modal('show');
this.fetchStockValueData(stockValueUrl);
this.closeCallback = closeCallback;
},
validateAndsaveStockValue() {
const newErrors = {};
this.errors = newErrors;
if (!this.unit) { newErrors.unit = I18n.t('repository_stock_values.manage_modal.unit_error'); }
if (!this.amount) { newErrors.amount = I18n.t('repository_stock_values.manage_modal.amount_error'); }
if (this.amount && this.amount < 0) { newErrors.amount = I18n.t('repository_stock_values.manage_modal.negative_error'); }
if (this.reminderEnabled && !this.lowStockTreshold) { newErrors.tresholdAmount = I18n.t('repository_stock_values.manage_modal.amount_error'); }
this.errors = newErrors;
this.errors = newErrors;
if (!$.isEmptyObject(newErrors)) return;
if (!$.isEmptyObject(newErrors)) return;
const $this = this
$.ajax({
method: 'POST',
url: this.stockUrl,
dataType: 'json',
data: {
repository_stock_value: {
unit_item_id: this.unit,
amount: this.newAmount,
comment: this.comment,
low_stock_threshold: this.reminderEnabled ? this.lowStockTreshold : null
},
operator: this.operations.find(operation => operation[0] == this.operation)?.[1],
change_amount: Math.abs(this.amount),
const $this = this;
$.ajax({
method: 'POST',
url: this.stockUrl,
dataType: 'json',
data: {
repository_stock_value: {
unit_item_id: this.unit,
amount: this.newAmount,
comment: this.comment,
low_stock_threshold: this.reminderEnabled ? this.lowStockTreshold : null
},
success: function(result) {
$this.stockValue = null;
$this.closeModal();
$this.closeCallback && $this.closeCallback(result);
}
})
}
operator: this.operations.find((operation) => operation[0] == this.operation)?.[0],
change_amount: Math.abs(this.amount)
},
success: (result) => {
$this.stockValue = null;
$this.closeModal();
$this.closeCallback && $this.closeCallback(result);
}
});
}
}
};
</script>

Some files were not shown because too many files have changed in this diff Show more