mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-03-04 19:53:19 +08:00
Merge branch 'develop' into features/file-versioning
This commit is contained in:
commit
8c356f7293
27 changed files with 251 additions and 92 deletions
|
@ -6,7 +6,7 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
before_action :load_storage_location
|
||||
before_action :load_repository_row, only: %i(create update destroy move)
|
||||
before_action :check_read_permissions, except: %i(create actions_toolbar)
|
||||
before_action :check_manage_permissions, only: %i(create update destroy)
|
||||
before_action :check_manage_permissions, only: %i(create update destroy move)
|
||||
|
||||
def index
|
||||
storage_location_repository_row = Lists::StorageLocationRepositoryRowsService.new(
|
||||
|
@ -54,6 +54,9 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
|
||||
def move
|
||||
ActiveRecord::Base.transaction do
|
||||
@original_storage_location = @storage_location_repository_row.storage_location
|
||||
@original_position = @storage_location_repository_row.human_readable_position
|
||||
|
||||
@storage_location_repository_row.discard
|
||||
@storage_location_repository_row = StorageLocationRepositoryRow.create!(
|
||||
repository_row: @repository_row,
|
||||
|
@ -61,7 +64,13 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
metadata: storage_location_repository_row_params[:metadata] || {},
|
||||
created_by: current_user
|
||||
)
|
||||
log_activity(:storage_location_repository_row_moved)
|
||||
log_activity(
|
||||
:storage_location_repository_row_moved,
|
||||
{
|
||||
storage_location_original: @original_storage_location.id,
|
||||
position_original: @original_position
|
||||
}
|
||||
)
|
||||
render json: @storage_location_repository_row,
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
|
@ -125,7 +134,7 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 unless can_create_storage_location_repository_rows?(@storage_location)
|
||||
render_403 unless can_manage_storage_location_repository_rows?(@storage_location)
|
||||
end
|
||||
|
||||
def log_activity(type_of, message_items = {})
|
||||
|
|
|
@ -10,6 +10,7 @@ class StorageLocationsController < ApplicationController
|
|||
before_action :check_storage_locations_enabled, except: :unassign_rows
|
||||
before_action :load_storage_location, only: %i(update destroy duplicate move show available_positions unassign_rows export_container import_container)
|
||||
before_action :check_read_permissions, except: %i(index create tree actions_toolbar import_container unassign_rows)
|
||||
before_action :check_manage_repository_rows_permissions, only: %i(import_container unassign_rows)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy duplicate move)
|
||||
before_action :set_breadcrumbs_items, only: %i(index show)
|
||||
|
@ -86,7 +87,7 @@ class StorageLocationsController < ApplicationController
|
|||
|
||||
def duplicate
|
||||
ActiveRecord::Base.transaction do
|
||||
new_storage_location = @storage_location.duplicate!(current_user)
|
||||
new_storage_location = @storage_location.duplicate!(current_user, current_team)
|
||||
if new_storage_location
|
||||
@storage_location = new_storage_location
|
||||
log_activity('storage_location_created')
|
||||
|
@ -104,9 +105,11 @@ class StorageLocationsController < ApplicationController
|
|||
if move_params[:destination_storage_location_id] == 'root_storage_location'
|
||||
nil
|
||||
else
|
||||
current_team.storage_locations.find(move_params[:destination_storage_location_id])
|
||||
StorageLocation.find(move_params[:destination_storage_location_id])
|
||||
end
|
||||
|
||||
render_403 and return if destination_storage_location && !can_manage_storage_location?(destination_storage_location)
|
||||
|
||||
@storage_location.update!(parent: destination_storage_location)
|
||||
|
||||
log_activity('storage_location_moved', {
|
||||
|
@ -228,6 +231,10 @@ class StorageLocationsController < ApplicationController
|
|||
render_403 unless can_manage_storage_location?(@storage_location)
|
||||
end
|
||||
|
||||
def check_manage_repository_rows_permissions
|
||||
render_403 unless can_manage_storage_location_repository_rows?(@storage_location)
|
||||
end
|
||||
|
||||
def set_breadcrumbs_items
|
||||
@breadcrumbs_items = []
|
||||
|
||||
|
|
|
@ -8,26 +8,24 @@ class TeamSharedObjectsController < ApplicationController
|
|||
ActiveRecord::Base.transaction do
|
||||
@activities_to_log = []
|
||||
|
||||
global_permission_level =
|
||||
if params[:select_all_teams]
|
||||
params[:select_all_write_permission] ? :shared_write : :shared_read
|
||||
else
|
||||
:not_shared
|
||||
end
|
||||
|
||||
# Global share
|
||||
if @model.globally_shareable?
|
||||
permission_level =
|
||||
if params[:select_all_teams]
|
||||
params[:select_all_write_permission] ? :shared_write : :shared_read
|
||||
else
|
||||
:not_shared
|
||||
end
|
||||
|
||||
@model.permission_level = permission_level
|
||||
@model.permission_level = global_permission_level
|
||||
|
||||
if @model.permission_level_changed?
|
||||
@model.save!
|
||||
@model.team_shared_objects.each(&:destroy!) unless permission_level == :not_shared
|
||||
@model.team_shared_objects.each(&:destroy!) unless global_permission_level == :not_shared
|
||||
case @model
|
||||
when Repository
|
||||
setup_repository_global_share_activity
|
||||
end
|
||||
|
||||
log_activities and next
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -35,11 +33,10 @@ class TeamSharedObjectsController < ApplicationController
|
|||
params[:team_share_params].each do |t|
|
||||
next unless t['private_shared_with']
|
||||
|
||||
@model.update!(permission_level: :not_shared) if @model.globally_shareable?
|
||||
|
||||
team_shared_object = @model.team_shared_objects.find_or_initialize_by(team_id: t['id'])
|
||||
|
||||
new_record = team_shared_object.new_record?
|
||||
|
||||
team_shared_object.update!(
|
||||
permission_level: t['private_shared_with_write'] ? :shared_write : :shared_read
|
||||
)
|
||||
|
|
|
@ -225,6 +225,7 @@
|
|||
:title="i18n.t('protocols.reorder_steps.modal.title')"
|
||||
:items="steps"
|
||||
:includeNumbers="true"
|
||||
dataE2e="protocol-templateSteps-reorder"
|
||||
@reorder="updateStepOrder"
|
||||
@close="closeStepReorderModal"
|
||||
/>
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
<ReorderableItemsModal v-if="reordering"
|
||||
:title="i18n.t('protocols.steps.modals.reorder_elements.title', { step_position: step.attributes.position + 1 })"
|
||||
:items="reorderableElements"
|
||||
:dataE2e="`e2e-BT-protocol-step${step.id}-reorder`"
|
||||
:dataE2e="`protocol-step${step.id}-reorder`"
|
||||
@reorder="updateElementOrder"
|
||||
@close="closeReorderModal"
|
||||
/>
|
||||
|
|
|
@ -31,7 +31,12 @@
|
|||
</div>
|
||||
<div class="mt-6" :class="{'hidden': !visible}">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.role_label") }}</label>
|
||||
<SelectDropdown :options="userRoles" :value="defaultRole" @change="changeRole" />
|
||||
<SelectDropdown
|
||||
:options="userRoles"
|
||||
:value="defaultRole"
|
||||
:data-e2e="`e2e-DD-newProtocolModal-defaultUserRole`"
|
||||
@change="changeRole"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
|
@ -54,8 +54,27 @@
|
|||
v-if="shareRepository"
|
||||
:object="shareRepository"
|
||||
:globalShareEnabled="true"
|
||||
:confirmationModal="$refs.shareConfirmationModal"
|
||||
@close="shareRepository = null"
|
||||
@share="updateTable" />
|
||||
<ConfirmationModal
|
||||
ref="shareConfirmationModal"
|
||||
:title="i18n.t('repositories.index.modal_confirm_sharing.title')"
|
||||
:description="`
|
||||
<p>${i18n.t('repositories.index.modal_confirm_sharing.description_1')}</p>
|
||||
<p><b>${i18n.t('repositories.index.modal_confirm_sharing.description_2')}</b></p>
|
||||
`"
|
||||
:confirmClass="'btn btn-danger'"
|
||||
:confirmText="i18n.t('repositories.index.modal_confirm_sharing.confirm')"
|
||||
:e2eAttributes="{
|
||||
modalName: 'e2e-MD-confirmSharingChanges',
|
||||
title: 'e2e-TX-confirmSharingChangesModal-title',
|
||||
content: 'e2e-TX-confirmSharingChangesModal-content',
|
||||
close: 'e2e-BT-confirmSharingChangesModal-close',
|
||||
cancel: 'e2e-BT-confirmSharingChangesModal-cancel',
|
||||
confirm: 'e2e-BT-confirmSharingChangesModal-delete'
|
||||
}"
|
||||
></ConfirmationModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
<ReorderableItemsModal v-if="reordering"
|
||||
:title="i18n.t('my_modules.modals.reorder_results.title')"
|
||||
:items="reorderableElements"
|
||||
:dataE2e="`task-result${result.id}-reorder`"
|
||||
@reorder="updateElementOrder"
|
||||
@close="closeReorderModal"
|
||||
/>
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
<template>
|
||||
<div class="mb-6">
|
||||
<div class="sci-label mb-2">
|
||||
<div class="sci-label mb-2" :data-e2e="`e2e-TX-${dataE2e}-grantAccessLabel`">
|
||||
{{ i18n.t('access_permissions.partials.new_assignments_form.grant_access') }}
|
||||
</div>
|
||||
<GeneralDropdown ref="dropdown" @open="$emit('assigningNewUsers', true)" @close="$emit('assigningNewUsers', false)" :fieldOnlyOpen="true" :fixed-width="true">
|
||||
<template v-slot:field>
|
||||
<div class="sci-input-container-v2 left-icon">
|
||||
<input type="text" v-model="query" class="sci-input-field"
|
||||
:placeholder="i18n.t('access_permissions.partials.new_assignments_form.find_people_html')" />
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
v-model="query"
|
||||
class="sci-input-field"
|
||||
:placeholder="i18n.t('access_permissions.partials.new_assignments_form.find_people_html')"
|
||||
:data-e2e="`e2e-IF-${dataE2e}-searchUsers`"
|
||||
/>
|
||||
<i class="sn-icon sn-icon-search" :data-e2e="`e2e-IC-${dataE2e}-searchUsers`"></i>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div v-if="!visible && roles.length > 0" class="py-2 flex border-solid border-0 border-b border-b-sn-sleepy-grey items-center gap-2">
|
||||
<div>
|
||||
<img src="/images/icon/team.png" class="rounded-full w-8 h-8">
|
||||
<img src="/images/icon/team.png" class="rounded-full w-8 h-8" :data-e2e="`e2e-IC-${dataE2e}-grantAccessTeam`">
|
||||
</div>
|
||||
<div>
|
||||
<div :data-e2e="`e2e-TX-${dataE2e}-grantAccessTeam`">
|
||||
{{ i18n.t('user_assignment.assign_all_team_members') }}
|
||||
</div>
|
||||
<MenuDropdown
|
||||
|
@ -25,16 +30,23 @@
|
|||
btnText="Assign"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:data-e2e="`e2e-DD-${dataE2e}-grantAccessTeam`"
|
||||
@setRole="(...args) => this.assignRole('all', ...args)"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
<perfect-scrollbar class="max-h-80 relative">
|
||||
<div v-for="user in filteredUsers" :key="user.id" class="py-2 flex items-center w-full">
|
||||
<div>
|
||||
<img :src="user.attributes.avatar_url" class="rounded-full w-8 h-8">
|
||||
<img :src="user.attributes.avatar_url" class="rounded-full w-8 h-8" :data-e2e="`e2e-IC-${dataE2e}-${user.attributes.name.replace(/\W/g, '')}-grantAccess`">
|
||||
</div>
|
||||
<div class="truncate ml-2" :title="user.attributes.name">{{ user.attributes.name }}</div>
|
||||
<div v-if="user.attributes.current_user" class="text-nowrap ml-1">
|
||||
<div
|
||||
class="truncate ml-2"
|
||||
:title="user.attributes.name"
|
||||
:data-e2e="`e2e-TX-${dataE2e}-${user.attributes.name.replace(/\W/g, '')}-grantAccess-name`"
|
||||
>
|
||||
{{ user.attributes.name }}
|
||||
</div>
|
||||
<div v-if="user.attributes.current_user" class="text-nowrap ml-1" :data-e2e="`e2e-TX-${dataE2e}-${user.attributes.name.replace(/\W/g, '')}-grantAccess-permission`">
|
||||
{{ `(${i18n.t('access_permissions.you')})` }}
|
||||
</div>
|
||||
<MenuDropdown
|
||||
|
@ -43,10 +55,11 @@
|
|||
btnText="Assign"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:data-e2e="`e2e-DD-${dataE2e}-${user.attributes.name.replace(/\W/g, '')}-grantAccess`"
|
||||
@setRole="(...args) => this.assignRole(user.id, ...args)"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
<div v-if="filteredUsers.length === 0" class="p-2 flex items-center w-full">
|
||||
<div v-if="filteredUsers.length === 0" class="p-2 flex items-center w-full" :data-e2e="`e2e-TX-${dataE2e.replace(/\W/g, '')}-grantAccess-noResults`">
|
||||
{{ i18n.t('access_permissions.no_results') }}
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
|
@ -76,6 +89,10 @@ export default {
|
|||
},
|
||||
reloadUsers: {
|
||||
type: Boolean
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
<perfect-scrollbar class="h-[50vh] relative">
|
||||
<div v-if="roles.length > 0 && visible && default_role" class="p-2 flex items-center gap-2 border-solid border-0 border-b border-b-sn-sleepy-grey">
|
||||
<div>
|
||||
<img src="/images/icon/team.png" class="rounded-full w-8 h-8">
|
||||
<img src="/images/icon/team.png" class="rounded-full w-8 h-8" :data-e2e="`e2e-IC-${dataE2e}-everyoneElse-team`">
|
||||
</div>
|
||||
<div>
|
||||
<div :data-e2e="`e2e-TX-${dataE2e}-everyoneElse`">
|
||||
{{ i18n.t('access_permissions.everyone_else', { team_name: params.object.team }) }}
|
||||
</div>
|
||||
<GeneralDropdown @open="loadUsers" @close="closeFlyout">
|
||||
<template v-slot:field>
|
||||
<i class="sn-icon sn-icon-info"></i>
|
||||
<i class="sn-icon sn-icon-info" :data-e2e="`e2e-IC-${dataE2e}-everyoneElse-info`"></i>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<perfect-scrollbar class="flex flex-col max-h-96 max-w-[280px] relative pr-4 gap-y-px">
|
||||
|
@ -18,8 +18,12 @@
|
|||
:key="user.attributes.user.id"
|
||||
:title="user.attributes.user.name"
|
||||
class="rounded px-3 py-2.5 flex items-center hover:no-underline leading-5 gap-2">
|
||||
<img :src="user.attributes.user.avatar_url" class="w-6 h-6 rounded-full">
|
||||
<span class="truncate">{{ user.attributes.user.name }}</span>
|
||||
<img
|
||||
:src="user.attributes.user.avatar_url"
|
||||
class="w-6 h-6 rounded-full"
|
||||
:data-e2e="`e2e-IC-${dataE2e}-everyoneElse-${user.attributes.user.name.replace(/\W/g, '')}`"
|
||||
>
|
||||
<span class="truncate" :data-e2e="`e2e-TX-${dataE2e}-everyoneElse-${user.attributes.user.name.replace(/\W/g, '')}`">{{ user.attributes.user.name }}</span>
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
</template>
|
||||
|
@ -31,6 +35,7 @@
|
|||
:btnText="this.roles.find((role) => role[0] == default_role)[1]"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:data-e2e="`e2e-DD-${dataE2e}-everyoneElse-roles`"
|
||||
@setRole="(...args) => this.changeDefaultRole(...args)"
|
||||
@removeRole="() => this.changeDefaultRole()"
|
||||
></MenuDropdown>
|
||||
|
@ -43,18 +48,29 @@
|
|||
:key="userAssignment.id"
|
||||
class="p-2 flex items-center gap-2">
|
||||
<div>
|
||||
<img :src="userAssignment.attributes.user.avatar_url" class="rounded-full w-8 h-8">
|
||||
<img
|
||||
class="rounded-full w-8 h-8"
|
||||
:src="userAssignment.attributes.user.avatar_url"
|
||||
:data-e2e="`e2e-IC-${dataE2e}-${userAssignment.attributes.user.name.replace(/\W/g, '')}`"
|
||||
>
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="truncate"
|
||||
:title="userAssignment.attributes.user.name"
|
||||
:data-e2e="`e2e-TX-${dataE2e}-${userAssignment.attributes.user.name.replace(/\W/g, '')}-name`"
|
||||
>{{ userAssignment.attributes.user.name }}</div>
|
||||
<div v-if="userAssignment.attributes.current_user" class="text-nowrap">
|
||||
<div
|
||||
v-if="userAssignment.attributes.current_user"
|
||||
class="text-nowrap"
|
||||
:data-e2e="`e2e-TX-${dataE2e}-${userAssignment.attributes.user.name.replace(/\W/g, '')}-currentUserLabel`"
|
||||
>
|
||||
{{ `(${i18n.t('access_permissions.you')})` }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-sn-grey text-nowrap">{{ userAssignment.attributes.inherit_message }}</div>
|
||||
<div class="text-xs text-sn-grey text-nowrap" :data-e2e="`e2e-TX-${dataE2e}-${userAssignment.attributes.user.name.replace(/\W/g, '')}-inheritLabel`">
|
||||
{{ userAssignment.attributes.inherit_message }}
|
||||
</div>
|
||||
</div>
|
||||
<MenuDropdown
|
||||
v-if="!userAssignment.attributes.last_owner && params.object.urls.update_access && !(userAssignment.attributes.current_user && userAssignment.attributes.inherit_message)"
|
||||
|
@ -63,10 +79,11 @@
|
|||
:btnText="userAssignment.attributes.user_role.name"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:data-e2e="`e2e-DD-${dataE2e}-${userAssignment.attributes.user.name.replace(/\W/g, '')}-role`"
|
||||
@setRole="(...args) => this.changeRole(userAssignment.attributes.user.id, ...args)"
|
||||
@removeRole="() => this.removeRole(userAssignment.attributes.user.id)"
|
||||
></MenuDropdown>
|
||||
<div class="ml-auto btn btn-light pointer-events-none" v-else>
|
||||
<div v-else class="ml-auto btn btn-light pointer-events-none" :data-e2e="`e2e-TX-${dataE2e}-${userAssignment.attributes.user.name.replace(/\W/g, '')}-role`">
|
||||
{{ userAssignment.attributes.user_role.name }}
|
||||
<div class="h-6 w-6"></div>
|
||||
</div>
|
||||
|
@ -78,7 +95,7 @@
|
|||
<script>
|
||||
/* global HelperModule */
|
||||
import MenuDropdown from '../menu_dropdown.vue';
|
||||
import GeneralDropdown from '../../shared/general_dropdown.vue';
|
||||
import GeneralDropdown from '../general_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
|
@ -96,6 +113,10 @@ export default {
|
|||
},
|
||||
reloadUsers: {
|
||||
type: Boolean
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" data-e2e="e2e-MD-manageAccess">
|
||||
<div class="modal-header">
|
||||
<button type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title truncate !block">
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-manageAccess-close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title truncate !block" data-e2e="e2e-TX-manageAccessModal-title">
|
||||
{{ i18n.t(`access_permissions.${params.object.type}.modals.edit_modal.title`, {
|
||||
resource_name: params.object.name
|
||||
}) }}
|
||||
|
@ -20,12 +21,13 @@
|
|||
:visible="visible"
|
||||
:default_role="default_role"
|
||||
:reloadUsers="reloadUnAssignedUsers"
|
||||
:dataE2e="`manageAccessModal-flyout`"
|
||||
@modified="modified = true; reloadUsers = true"
|
||||
@assigningNewUsers="(v) => { assigningNewUsers = v }"
|
||||
@usersReloaded="reloadUnAssignedUsers = false"
|
||||
@changeVisibility="changeVisibility"
|
||||
/>
|
||||
<h5 class="py-2.5">
|
||||
<h5 class="py-2.5" data-e2e="e2e-TX-manageAccessModal-peopleWithAccess">
|
||||
{{ i18n.t('access_permissions.partials.new_assignments_form.people_with_access') }}
|
||||
</h5>
|
||||
<editView
|
||||
|
@ -34,6 +36,7 @@
|
|||
:visible="visible"
|
||||
:default_role="default_role"
|
||||
:reloadUsers="reloadUsers"
|
||||
:dataE2e="`manageAccessModal-usersWithAccess`"
|
||||
@modified="modified = true; reloadUnAssignedUsers = true"
|
||||
@usersReloaded="reloadUsers = false"
|
||||
@changeVisibility="changeVisibility"
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
<a :class="`rounded flex gap-2 items-center py-1.5 px-1.5 xl:px-2.5 hover:text-sn-white hover:bg-sn-blue
|
||||
bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`"
|
||||
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
|
||||
:data-target="action.target"
|
||||
:data-toggle="action.type === 'modal' && 'modal'"
|
||||
:id="action.button_id"
|
||||
:title="action.label"
|
||||
:data-e2e="`e2e-BT-actionToolbar-${action.name}`"
|
||||
|
@ -73,6 +75,9 @@ export default {
|
|||
this.$emit('toolbar:action', action);
|
||||
// do nothing, this is handled by legacy code based on the button class
|
||||
break;
|
||||
case 'modal':
|
||||
// do nothihg, boostrap modal handled by data-toggle="modal" and data-target
|
||||
break;
|
||||
case 'link':
|
||||
// do nothing, already handled by href
|
||||
break;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<div ref="modal" @keydown.esc="close" class="modal sci-reorderable-items" tabindex="-1" role="dialog">
|
||||
<div ref="modal" @keydown.esc="close" class="modal sci-reorderable-items" tabindex="-1" role="dialog" :data-e2e="`e2e-MD-${dataE2e}`">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button @click="close" type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title">
|
||||
<button @click="close" type="button" class="close" data-dismiss="modal" aria-label="Close" :data-e2e="`e2e-BT-${dataE2e}-close`">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title" :data-e2e="`e2e-TX-${dataE2e}-title`">
|
||||
{{ title }}
|
||||
</h4>
|
||||
</div>
|
||||
|
@ -20,13 +22,23 @@
|
|||
<template #item="{element, index}">
|
||||
<div class="step-element-header flex items-center">
|
||||
<div class="step-element-grip step-element-grip--draggable">
|
||||
<i class="sn-icon sn-icon-drag"></i>
|
||||
<i class="sn-icon sn-icon-drag" :data-e2e="`e2e-BT-${dataE2e}-element${index + 1}-drag`"></i>
|
||||
</div>
|
||||
<div class="step-element-name text-center flex items-center gap-2">
|
||||
<strong v-if="includeNumbers" class="step-element-number">{{ index + 1 }}</strong>
|
||||
<i v-if="element.attributes.icon" class="fas" :class="element.attributes.icon"></i>
|
||||
<span :title="nameWithFallbacks(element)" v-if="nameWithFallbacks(element)">{{ nameWithFallbacks(element) }}</span>
|
||||
<span :title="element.attributes.placeholder" v-else class="step-element-name-placeholder">{{ element.attributes.placeholder }}</span>
|
||||
<strong v-if="includeNumbers" class="step-element-number" :data-e2e="`e2e-TX-${dataE2e}-element${index + 1}-position`">
|
||||
{{ index + 1 }}
|
||||
</strong>
|
||||
<i v-if="element.attributes.icon" class="fas" :class="element.attributes.icon" :data-e2e="`e2e-IC-${dataE2e}-element${index + 1}`"></i>
|
||||
<span
|
||||
:title="nameWithFallbacks(element)"
|
||||
v-if="nameWithFallbacks(element)"
|
||||
:data-e2e="`e2e-TX-${dataE2e}-element${index + 1}-name`"
|
||||
>
|
||||
{{ nameWithFallbacks(element) }}
|
||||
</span>
|
||||
<span :title="element.attributes.placeholder" v-else class="step-element-name-placeholder" :data-e2e="`e2e-TX-${dataE2e}-element${index + 1}-name`">
|
||||
{{ element.attributes.placeholder }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -56,6 +68,10 @@ export default {
|
|||
includeNumbers: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -77,31 +77,56 @@ export default {
|
|||
name: 'ShareObjectModal',
|
||||
props: {
|
||||
object: Object,
|
||||
globalShareEnabled: { type: Boolean, default: false }
|
||||
globalShareEnabled: { type: Boolean, default: false },
|
||||
confirmationModal: { type: Object }
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
sharedWithAllRead: this.object.shared_read || this.object.shared_write,
|
||||
sharedWithAllWrite: this.object.shared_write,
|
||||
shareableTeams: [],
|
||||
permission_changes: {}
|
||||
initialState: {},
|
||||
shareableTeams: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getTeams();
|
||||
this.initTeams();
|
||||
},
|
||||
computed: {
|
||||
willUnshare() {
|
||||
if (this.globalShareEnabled && !this.sharedWithAllRead && this.initialState.sharedWithAllRead) return true;
|
||||
|
||||
// true if any team would switch from shared to unshared, based on initial state
|
||||
return this.shareableTeams.some((t) => {
|
||||
return this.initialState.shareableTeams.find((it) => t.id === it.id).attributes.private_shared_with && !t.attributes.private_shared_with;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTeams() {
|
||||
initTeams() {
|
||||
axios.get(this.object.urls.shareable_teams).then((response) => {
|
||||
this.initialState = {
|
||||
shareableTeams: JSON.parse(JSON.stringify(response.data.data)), // object needs to be deep cloned to get rid of references
|
||||
sharedWithAllRead: this.sharedWithAllRead,
|
||||
sharedWithAllWrite: this.sharedWithAllWrite
|
||||
};
|
||||
this.shareableTeams = response.data.data;
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
async submit() {
|
||||
$(this.$refs.modal).hide();
|
||||
|
||||
if (this.confirmationModal ? !this.willUnshare || await this.confirmationModal.show() : true) {
|
||||
this.doRequest();
|
||||
} else {
|
||||
$(this.$refs.modal).show();
|
||||
}
|
||||
},
|
||||
doRequest() {
|
||||
const data = {
|
||||
select_all_teams: this.sharedWithAllRead,
|
||||
select_all_write_permission: this.sharedWithAllWrite,
|
||||
team_share_params: this.shareableTeams.map((team) => { return { id: team.id, ...team.attributes } })
|
||||
team_share_params: this.sharedWithAllRead ? [] : this.shareableTeams.map((team) => { return { id: team.id, ...team.attributes } })
|
||||
};
|
||||
axios.post(this.object.urls.share, data).then(() => {
|
||||
HelperModule.flashAlertMsg(this.i18n.t(
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
:selectedContainer="assignToContainer"
|
||||
:selectedPosition="assignToPosition"
|
||||
:selectedRow="rowIdToMove"
|
||||
:selectedRowName="rowNameToMove"
|
||||
:cellId="cellIdToUnassign"
|
||||
@close="openAssignModal = false; resetTableSearch(); this.reloadingTable = true"
|
||||
></AssignModal>
|
||||
|
@ -115,6 +116,7 @@ export default {
|
|||
assignToPosition: null,
|
||||
assignToContainer: null,
|
||||
rowIdToMove: null,
|
||||
rowNameToMove: null,
|
||||
cellIdToUnassign: null,
|
||||
assignMode: 'assign',
|
||||
storageLocationUnassignDescription: ''
|
||||
|
@ -179,15 +181,15 @@ export default {
|
|||
type: 'emit',
|
||||
buttonStyle: 'btn btn-primary'
|
||||
});
|
||||
}
|
||||
|
||||
left.push({
|
||||
name: 'import',
|
||||
icon: 'sn-icon sn-icon-import',
|
||||
label: this.i18n.t('storage_locations.show.import_modal.import_button'),
|
||||
type: 'emit',
|
||||
buttonStyle: 'btn btn-light'
|
||||
});
|
||||
left.push({
|
||||
name: 'import',
|
||||
icon: 'sn-icon sn-icon-import',
|
||||
label: this.i18n.t('storage_locations.show.import_modal.import_button'),
|
||||
type: 'emit',
|
||||
buttonStyle: 'btn btn-light'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
left,
|
||||
|
@ -219,6 +221,7 @@ export default {
|
|||
assignRow() {
|
||||
this.openAssignModal = true;
|
||||
this.rowIdToMove = null;
|
||||
this.rowNameToMove = null;
|
||||
this.assignToContainer = this.containerId;
|
||||
this.assignToPosition = null;
|
||||
this.cellIdToUnassign = null;
|
||||
|
@ -227,6 +230,7 @@ export default {
|
|||
assignRowToPosition(position) {
|
||||
this.openAssignModal = true;
|
||||
this.rowIdToMove = null;
|
||||
this.rowNameToMove = null;
|
||||
this.assignToContainer = this.containerId;
|
||||
this.assignToPosition = position;
|
||||
this.cellIdToUnassign = null;
|
||||
|
@ -235,6 +239,7 @@ export default {
|
|||
moveRow(_event, data) {
|
||||
this.openAssignModal = true;
|
||||
this.rowIdToMove = data[0].row_id;
|
||||
this.rowNameToMove = data[0].row_name || this.i18n.t('storage_locations.show.hidden');
|
||||
this.assignToContainer = null;
|
||||
this.assignToPosition = null;
|
||||
this.cellIdToUnassign = data[0].id;
|
||||
|
|
|
@ -10,31 +10,37 @@
|
|||
<h4 v-if="selectedPosition" class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.selected_position_title`, { position: formattedPosition }) }}
|
||||
</h4>
|
||||
<h4 v-else-if="selectedRow && selectedRowName" class="modal-title truncate !block">
|
||||
<h4 v-else-if="assignMode === 'assign' && selectedRow && selectedRowName" class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.selected_row_title`) }}
|
||||
</h4>
|
||||
<h4 v-else-if="assignMode === 'move'" class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.move_title`, { name: selectedRowName }) }}
|
||||
</h4>
|
||||
<h4 v-else class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_title`) }}
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.assign_title`) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p v-if="selectedRow && selectedRowName" class="mb-4">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.selected_row_description`, { name: selectedRowName }) }}
|
||||
</p>
|
||||
<h4 v-else-if="assignMode === 'move'" class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.move_description`, { name: selectedRowName }) }}
|
||||
</h4>
|
||||
<p v-else class="mb-4">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_description`) }}
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.assign_description`) }}
|
||||
</p>
|
||||
<RowSelector v-if="!selectedRow" @change="this.rowId = $event" class="mb-4"></RowSelector>
|
||||
<ContainerSelector v-if="!selectedContainer" @change="this.containerId = $event"></ContainerSelector>
|
||||
<PositionSelector
|
||||
v-if="containerId && !selectedPosition"
|
||||
v-if="containerId && containerId > 0 && !selectedPosition"
|
||||
:key="containerId"
|
||||
:selectedContainerId="containerId"
|
||||
@change="this.position = $event"></PositionSelector>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<button class="btn btn-primary" type="submit" :disabled="!validObject">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_action`) }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -70,6 +76,9 @@ export default {
|
|||
},
|
||||
mixins: [modalMixin],
|
||||
computed: {
|
||||
validObject() {
|
||||
return this.rowId && this.containerId && this.containerId > 0;
|
||||
},
|
||||
createUrl() {
|
||||
return storage_location_storage_location_repository_rows_path({
|
||||
storage_location_id: this.containerId
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<button class="btn btn-primary" :disabled="!validContainer" type="submit">
|
||||
{{ i18n.t('general.move') }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -69,6 +69,11 @@ export default {
|
|||
created() {
|
||||
this.teamId = this.selectedObject.team_id;
|
||||
},
|
||||
computed: {
|
||||
validContainer() {
|
||||
return (this.selectedStorageLocationId && this.selectedStorageLocationId > 0) || this.selectedStorageLocationId === null;
|
||||
}
|
||||
},
|
||||
mixins: [modalMixin, MoveTreeMixin],
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -61,14 +61,15 @@ class StorageLocation < ApplicationRecord
|
|||
storage_location_repository_rows.count.zero?
|
||||
end
|
||||
|
||||
def duplicate!(user)
|
||||
def duplicate!(user, team)
|
||||
ActiveRecord::Base.transaction do
|
||||
new_storage_location = dup
|
||||
new_storage_location.name = next_clone_name
|
||||
new_storage_location.team = team unless parent_id
|
||||
new_storage_location.created_by = user
|
||||
new_storage_location.save!
|
||||
copy_image(self, new_storage_location)
|
||||
recursive_duplicate(id, new_storage_location.id, user)
|
||||
recursive_duplicate(id, new_storage_location.id, user, new_storage_location.team)
|
||||
new_storage_location
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
false
|
||||
|
@ -144,14 +145,15 @@ class StorageLocation < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def recursive_duplicate(old_parent_id = nil, new_parent_id = nil, user = nil)
|
||||
def recursive_duplicate(old_parent_id = nil, new_parent_id = nil, user = nil, team = nil)
|
||||
StorageLocation.where(parent_id: old_parent_id).find_each do |child|
|
||||
new_child = child.dup
|
||||
new_child.parent_id = new_parent_id
|
||||
new_child.team = team
|
||||
new_child.created_by = user
|
||||
new_child.save!
|
||||
copy_image(child, new_child)
|
||||
recursive_duplicate(child.id, new_child.id, user)
|
||||
recursive_duplicate(child.id, new_child.id, user, team)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -19,9 +19,7 @@ Canaid::Permissions.register_for(StorageLocation) do
|
|||
can :manage_storage_location do |user, storage_location|
|
||||
root_storage_location = storage_location.root_storage_location
|
||||
|
||||
next true if root_storage_location.shared_with_write?(user.current_team)
|
||||
|
||||
user.current_team == root_storage_location.team && root_storage_location.team.permission_granted?(
|
||||
next false unless user.current_team.permission_granted?(
|
||||
user,
|
||||
if root_storage_location.container?
|
||||
TeamPermissions::STORAGE_LOCATION_CONTAINERS_MANAGE
|
||||
|
@ -29,10 +27,15 @@ Canaid::Permissions.register_for(StorageLocation) do
|
|||
TeamPermissions::STORAGE_LOCATIONS_MANAGE
|
||||
end
|
||||
)
|
||||
|
||||
next true if user.current_team == root_storage_location.team
|
||||
|
||||
root_storage_location.shared_with_write?(user.current_team)
|
||||
end
|
||||
|
||||
can :create_storage_location_repository_rows do |user, storage_location|
|
||||
can_read_storage_location?(user, storage_location)
|
||||
can :manage_storage_location_repository_rows do |user, storage_location|
|
||||
can_read_storage_location?(user, storage_location) &&
|
||||
user.current_team.permission_granted?(user, TeamPermissions::STORAGE_LOCATION_CONTAINERS_MANAGE)
|
||||
end
|
||||
|
||||
can :share_storage_location do |user, storage_location|
|
||||
|
|
|
@ -9,7 +9,7 @@ module RepositoryDatatable
|
|||
def value
|
||||
@user = scope[:user]
|
||||
{
|
||||
view: value_object.has_smart_annotation? ? custom_auto_link(value_object.data, simple_format: true, team: scope[:team]) : escape_input(value_object.data),
|
||||
view: value_object.has_smart_annotation? ? custom_auto_link(value_object.data, simple_format: true, team: scope[:team]) : sanitize_input(value_object.data),
|
||||
edit: value_object.data
|
||||
}
|
||||
end
|
||||
|
|
|
@ -83,7 +83,7 @@ class RepositoryDatatableService
|
|||
repository_rows =
|
||||
if @repository.archived? || @repository.is_a?(RepositorySnapshot)
|
||||
# don't load reminders for archived repositories or snapshots
|
||||
repository_rows.select('FALSE AS has_active_stock_reminders, FALSE AS has_active_datetime_reminders')
|
||||
repository_rows.select('FALSE AS has_active_reminders')
|
||||
else
|
||||
repository_rows.left_outer_joins_active_reminders(@repository, @user)
|
||||
.select('COUNT(repository_cells_with_active_reminders.id) > 0 AS has_active_reminders')
|
||||
|
|
|
@ -20,6 +20,8 @@ class RepositorySnapshotDatatableService < RepositoryDatatableService
|
|||
repository_rows = fetch_rows(search_value).preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
|
||||
repository_rows = repository_rows.preload(:repository_columns, repository_cells: { value: @repository.cell_preload_includes }) if @preload_cells
|
||||
repository_rows = repository_rows.preload(:repository_stock_cell, :repository_stock_value) if @repository.has_stock_management?
|
||||
# don't load reminders for snapshots
|
||||
repository_rows = repository_rows.select('FALSE AS has_active_reminders') if Repository.reminders_enabled?
|
||||
|
||||
sort_rows(order_by_column, repository_rows)
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ module StorageLocations
|
|||
end
|
||||
|
||||
def import_items
|
||||
@rows = SpreadsheetParser.spreadsheet_enumerator(@sheet).reject { |r| r.all?(&:blank?) }
|
||||
@rows = SpreadsheetParser.spreadsheet_enumerator(@sheet).to_a
|
||||
|
||||
# Check if the file has proper headers
|
||||
header = SpreadsheetParser.parse_row(@rows[0], @sheet)
|
||||
|
@ -73,6 +73,8 @@ module StorageLocations
|
|||
repository_row_id: (row[1].to_s.gsub('IT', '') if row[1].present?)
|
||||
}
|
||||
end
|
||||
|
||||
@rows.reject! { |r| r[:repository_row_id].blank? && r[:position].blank? }
|
||||
end
|
||||
|
||||
def import_row!(row)
|
||||
|
|
|
@ -27,7 +27,7 @@ module Toolbars
|
|||
private
|
||||
|
||||
def unassign_action
|
||||
return unless can_read_storage_location?(@storage_location)
|
||||
return unless can_manage_storage_location_repository_rows?(@storage_location)
|
||||
|
||||
{
|
||||
name: 'unassign',
|
||||
|
@ -39,7 +39,7 @@ module Toolbars
|
|||
end
|
||||
|
||||
def move_action
|
||||
return unless @single && can_read_storage_location?(@storage_location)
|
||||
return unless @single && can_manage_storage_location_repository_rows?(@storage_location)
|
||||
|
||||
{
|
||||
name: 'move',
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
ref="container"
|
||||
actions-url="<%= actions_toolbar_storage_location_storage_location_repository_rows_path(@storage_location) %>"
|
||||
data-source="<%= storage_location_storage_location_repository_rows_path(@storage_location) %>"
|
||||
:can-manage="<%= can_create_storage_location_repository_rows?(@storage_location) %>"
|
||||
:can-manage="<%= can_manage_storage_location_repository_rows?(@storage_location) %>"
|
||||
:with-grid="<%= @storage_location.with_grid? %>"
|
||||
:grid-size="<%= @storage_location.grid_size.to_json %>"
|
||||
:container-id="<%= @storage_location.id %>"
|
||||
|
|
|
@ -2017,6 +2017,11 @@ en:
|
|||
name_placeholder: "My inventory"
|
||||
submit: "Create"
|
||||
success_flash_html: "Inventory <strong>%{name}</strong> successfully created."
|
||||
modal_confirm_sharing:
|
||||
title: "Inventory sharing changes"
|
||||
description_1: "You will no longer share this inventory with some of the teams. All unshared inventory items assigned to tasks will be automatically removed and this action is irreversible. Any item relationship links (if they exist) will also be deleted."
|
||||
description_2: "Are you sure you want to apply the changes you made?"
|
||||
confirm: "Apply"
|
||||
export:
|
||||
notification:
|
||||
error:
|
||||
|
@ -2699,9 +2704,9 @@ en:
|
|||
selected_position_title: 'Assign to position %{position}'
|
||||
selected_row_title: 'Assign new location'
|
||||
assign_title: 'Assign position'
|
||||
move_title: 'Move item'
|
||||
move_title: 'Move %{name}'
|
||||
assign_description: 'Select an item to assign it to a location.'
|
||||
move_description: 'Select a new location for your item.'
|
||||
move_description: 'Select where you want to move %{name}.'
|
||||
selected_row_description: "Select a location for the item %{name}."
|
||||
assign_action: 'Assign'
|
||||
move_action: 'Move'
|
||||
|
@ -2712,7 +2717,7 @@ en:
|
|||
import_modal:
|
||||
import_button: 'Import items'
|
||||
title: "Import items to a box"
|
||||
description: "Import items to a box allows for assigning items to a box and updating positions within a grid box. First, export the current box data to download a file listing the items already in the box. Then, edit the exported file to add or update the items you want to place in the box. When importing the file, ensure it includes the ‘Position’ and ‘Item ID’ columns for a successful import."
|
||||
description: "Import items to a box allows for assigning items to a box and updating positions within a grid box. First, export the current box data to download a file listing the items already in the box. Then, edit the exported file to add or update the items you want to place in the box. When importing the file, ensure it includes the ‘Box position’ and ‘Item ID’ columns for a successful import."
|
||||
export: "Export"
|
||||
export_button: "Export current box"
|
||||
import: "Import"
|
||||
|
|
|
@ -338,7 +338,7 @@ en:
|
|||
container_storage_location_sharing_updated_html: "%{user} changed permission of shared box %{storage_location} with team %{team} to %{permission_level}."
|
||||
storage_location_repository_row_created_html: "%{user} assigned %{repository_row} to box %{storage_location} %{position}."
|
||||
storage_location_repository_row_deleted_html: "%{user} unassigned %{repository_row} from box %{storage_location} %{position}."
|
||||
storage_location_repository_row_moved_html: "%{user} moved item %{repository_row} from box %{storage_location_original} %{positions} to box %{storage_location_destination} %{positions}."
|
||||
storage_location_repository_row_moved_html: "%{user} moved item %{repository_row} from box %{storage_location_original} %{position_original} to box %{storage_location} %{position}."
|
||||
container_storage_location_imported_html: "%{user} assigned %{assigned_count} item(s) and unassigned %{unassigned_count} item(s) by import to %{storage_location}."
|
||||
activity_name:
|
||||
create_project: "Project created"
|
||||
|
|
Loading…
Reference in a new issue