mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-03-03 19:24:48 +08:00
Add assign/unassign modal and move modal [SCI-10870]
This commit is contained in:
parent
66d620b5da
commit
2da397cc76
25 changed files with 541 additions and 63 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -95,3 +95,7 @@ public/marvin4js-license.cxl
|
|||
|
||||
/app/assets/builds/*
|
||||
!/app/assets/builds/.keep
|
||||
|
||||
# Ignore automatically generated js-routes files.
|
||||
/app/javascript/routes.js
|
||||
/app/javascript/routes.d.ts
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -94,6 +94,7 @@ gem 'graphviz'
|
|||
|
||||
gem 'cssbundling-rails'
|
||||
gem 'jsbundling-rails'
|
||||
gem 'js-routes'
|
||||
|
||||
gem 'tailwindcss-rails', '~> 2.4'
|
||||
|
||||
|
|
|
@ -386,6 +386,8 @@ GEM
|
|||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
js-routes (2.2.8)
|
||||
railties (>= 4)
|
||||
jsbundling-rails (1.1.1)
|
||||
railties (>= 6.0.0)
|
||||
json (2.6.3)
|
||||
|
@ -826,6 +828,7 @@ DEPENDENCIES
|
|||
image_processing
|
||||
img2zpl!
|
||||
jbuilder
|
||||
js-routes
|
||||
jsbundling-rails
|
||||
json-jwt
|
||||
json_matchers
|
||||
|
|
2
Rakefile
2
Rakefile
|
@ -5,3 +5,5 @@ require File.expand_path('../config/application', __FILE__)
|
|||
|
||||
Rails.application.load_tasks
|
||||
Doorkeeper::Rake.load_tasks
|
||||
# Update js-routes file before javascript build
|
||||
task 'javascript:build' => 'js:routes:typescript'
|
||||
|
|
|
@ -10,14 +10,14 @@ class RepositoriesController < ApplicationController
|
|||
include MyModulesHelper
|
||||
|
||||
before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar
|
||||
export_modal export_repositories)
|
||||
before_action :load_repositories, only: :index
|
||||
export_modal export_repositories list)
|
||||
before_action :load_repositories, only: %i(index list)
|
||||
before_action :load_repositories_for_archiving, only: :archive
|
||||
before_action :load_repositories_for_restoring, only: :restore
|
||||
before_action :check_view_all_permissions, only: %i(index sidebar)
|
||||
before_action :check_view_all_permissions, only: %i(index sidebar list)
|
||||
before_action :check_view_permissions, except: %i(index create_modal create update destroy parse_sheet
|
||||
import_records sidebar archive restore actions_toolbar
|
||||
export_modal export_repositories)
|
||||
export_modal export_repositories list)
|
||||
before_action :check_manage_permissions, only: %i(rename_modal update)
|
||||
before_action :check_delete_permissions, only: %i(destroy destroy_modal)
|
||||
before_action :check_archive_permissions, only: %i(archive restore)
|
||||
|
@ -44,6 +44,16 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def list
|
||||
results = @repositories
|
||||
results = results.name_like(params[:query]) if params[:query].present?
|
||||
render json: { data: results.map { |r| [r.id, r.name] } }
|
||||
end
|
||||
|
||||
def rows_list
|
||||
render json: { data: @repository.repository_rows.map { |r| [r.id, r.name] } }
|
||||
end
|
||||
|
||||
def sidebar
|
||||
render json: {
|
||||
html: render_to_string(partial: 'repositories/sidebar', locals: {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StorageLocationRepositoryRowsController < ApplicationController
|
||||
before_action :load_storage_location_repository_row, only: %i(update destroy)
|
||||
before_action :load_storage_location_repository_row, only: %i(update destroy move)
|
||||
before_action :load_storage_location
|
||||
before_action :load_repository_row, only: %i(create update destroy)
|
||||
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)
|
||||
|
||||
|
@ -13,7 +13,6 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
).call
|
||||
render json: storage_location_repository_row,
|
||||
each_serializer: Lists::StorageLocationRepositoryRowSerializer,
|
||||
include: %i(repository_row),
|
||||
meta: (pagination_dict(storage_location_repository_row) unless @storage_location.with_grid?)
|
||||
end
|
||||
|
||||
|
@ -22,8 +21,7 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
|
||||
if @storage_location_repository_row.save
|
||||
render json: @storage_location_repository_row,
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer,
|
||||
include: :repository_row
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer
|
||||
else
|
||||
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
|
||||
end
|
||||
|
@ -39,13 +37,30 @@ class StorageLocationRepositoryRowsController < ApplicationController
|
|||
|
||||
if @storage_location_repository_row.save
|
||||
render json: @storage_location_repository_row,
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer,
|
||||
include: :repository_row
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer
|
||||
else
|
||||
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def move
|
||||
ActiveRecord::Base.transaction do
|
||||
@storage_location_repository_row.discard
|
||||
@storage_location_repository_row = StorageLocationRepositoryRow.create!(
|
||||
repository_row: @repository_row,
|
||||
storage_location: @storage_location,
|
||||
metadata: storage_location_repository_row_params[:metadata] || {},
|
||||
created_by: current_user
|
||||
)
|
||||
|
||||
render json: @storage_location_repository_row,
|
||||
serializer: Lists::StorageLocationRepositoryRowSerializer
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
render json: { errors: e.record.errors.full_messages }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @storage_location_repository_row.discard
|
||||
render json: {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StorageLocationsController < ApplicationController
|
||||
before_action :load_storage_location, only: %i(update destroy duplicate move show)
|
||||
before_action :load_storage_location, only: %i(update destroy duplicate move show available_positions unassign_rows)
|
||||
before_action :check_read_permissions, except: %i(index create tree actions_toolbar)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(update destroy duplicate move)
|
||||
|
@ -81,10 +81,20 @@ class StorageLocationsController < ApplicationController
|
|||
end
|
||||
|
||||
def tree
|
||||
records = current_team.storage_locations.where(parent: nil, container: false)
|
||||
records = current_team.storage_locations.where(parent: nil, container: [false, params[:container] == 'true'])
|
||||
render json: storage_locations_recursive_builder(records)
|
||||
end
|
||||
|
||||
def available_positions
|
||||
render json: { positions: @storage_location.available_positions }
|
||||
end
|
||||
|
||||
def unassign_rows
|
||||
@storage_location.storage_location_repository_rows.where(id: params[:ids]).discard_all
|
||||
|
||||
render json: { status: :ok }
|
||||
end
|
||||
|
||||
def actions_toolbar
|
||||
render json: {
|
||||
actions:
|
||||
|
@ -172,7 +182,9 @@ class StorageLocationsController < ApplicationController
|
|||
storage_locations.map do |storage_location|
|
||||
{
|
||||
storage_location: storage_location,
|
||||
children: storage_locations_recursive_builder(storage_location.storage_locations.where(container: false))
|
||||
children: storage_locations_recursive_builder(
|
||||
storage_location.storage_locations.where(container: [false, params[:container] == 'true'])
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
<div v-if="withGrid">
|
||||
<div class="py-4">
|
||||
<div class="h-11">
|
||||
<button class="btn btn-primary">
|
||||
<button class="btn btn-primary" @click="assignRow">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
{{ i18n.t('storage_locations.show.toolbar.assign') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Grid :gridSize="gridSize" :assignedItems="assignedItems" />
|
||||
<Grid :gridSize="gridSize" :assignedItems="assignedItems" @assign="assignRowToPosition"/>
|
||||
</div>
|
||||
<div class="h-full bg-white p-4">
|
||||
<DataTable :columnDefs="columnDefs"
|
||||
|
@ -20,9 +20,31 @@
|
|||
:toolbarActions="toolbarActions"
|
||||
:actionsUrl="actionsUrl"
|
||||
:scrollMode="paginationMode"
|
||||
@assign="assignRow"
|
||||
@move="moveRow"
|
||||
@unassign="unassignRows"
|
||||
@tableReloaded="handleTableReload"
|
||||
/>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<AssignModal
|
||||
v-if="openAssignModal"
|
||||
:assignMode="assignMode"
|
||||
:selectedContainer="assignToContainer"
|
||||
:selectedPosition="assignToPosition"
|
||||
:selectedRow="rowIdToMove"
|
||||
:cellId="cellIdToUnassign"
|
||||
:withGrid="withGrid"
|
||||
@close="openAssignModal = false; this.reloadingTable = true"
|
||||
></AssignModal>
|
||||
<ConfirmationModal
|
||||
:title="i18n.t('storage_locations.show.unassign_modal.title')"
|
||||
:description="storageLocationUnassignDescription"
|
||||
confirmClass="btn btn-danger"
|
||||
:confirmText="i18n.t('storage_locations.show.unassign_modal.button')"
|
||||
ref="unassignStorageLocationModal"
|
||||
></ConfirmationModal>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -32,12 +54,16 @@
|
|||
import axios from '../../packs/custom_axios.js';
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import Grid from './grid.vue';
|
||||
import AssignModal from './modals/assign.vue';
|
||||
import ConfirmationModal from '../shared/confirmation_modal.vue';
|
||||
|
||||
export default {
|
||||
name: 'StorageLocationsContainer',
|
||||
components: {
|
||||
DataTable,
|
||||
Grid
|
||||
Grid,
|
||||
AssignModal,
|
||||
ConfirmationModal
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
|
@ -52,6 +78,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
containerId: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
gridSize: Array
|
||||
},
|
||||
data() {
|
||||
|
@ -62,7 +92,14 @@ export default {
|
|||
editStorageLocation: null,
|
||||
objectToMove: null,
|
||||
moveToUrl: null,
|
||||
assignedItems: []
|
||||
assignedItems: [],
|
||||
openAssignModal: false,
|
||||
assignToPosition: null,
|
||||
assignToContainer: null,
|
||||
rowIdToMove: null,
|
||||
cellIdToUnassign: null,
|
||||
assignMode: 'assign',
|
||||
storageLocationUnassignDescription: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -123,6 +160,44 @@ export default {
|
|||
handleTableReload(items) {
|
||||
this.reloadingTable = false;
|
||||
this.assignedItems = items;
|
||||
},
|
||||
assignRow() {
|
||||
this.openAssignModal = true;
|
||||
this.rowIdToMove = null;
|
||||
this.assignToContainer = this.containerId;
|
||||
this.assignToPosition = null;
|
||||
this.cellIdToUnassign = null;
|
||||
this.assignMode = 'assign';
|
||||
},
|
||||
assignRowToPosition(position) {
|
||||
this.openAssignModal = true;
|
||||
this.rowIdToMove = null;
|
||||
this.assignToContainer = this.containerId;
|
||||
this.assignToPosition = position;
|
||||
this.cellIdToUnassign = null;
|
||||
this.assignMode = 'assign';
|
||||
},
|
||||
moveRow(_event, data) {
|
||||
this.openAssignModal = true;
|
||||
this.rowIdToMove = data[0].row_id;
|
||||
this.assignToContainer = null;
|
||||
this.assignToPosition = null;
|
||||
this.cellIdToUnassign = data[0].id;
|
||||
this.assignMode = 'move';
|
||||
},
|
||||
async unassignRows(event, rows) {
|
||||
this.storageLocationUnassignDescription = this.i18n.t(
|
||||
'storage_locations.show.unassign_modal.description',
|
||||
{ items: rows.length }
|
||||
);
|
||||
const ok = await this.$refs.unassignStorageLocationModal.show();
|
||||
if (ok) {
|
||||
axios.post(event.path).then(() => {
|
||||
this.reloadingTable = true;
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -25,9 +25,10 @@
|
|||
>
|
||||
<div
|
||||
class="h-full w-full rounded-full items-center flex justify-center"
|
||||
@click="assignRow(cell.row, cell.column)"
|
||||
:class="{
|
||||
'bg-sn-background-green': cellIsOccupied(cell.row, cell.column),
|
||||
'bg-white': !cellIsOccupied(cell.row, cell.column)
|
||||
'bg-white cursor-pointer': !cellIsOccupied(cell.row, cell.column)
|
||||
}"
|
||||
>
|
||||
{{ rowsList[cell.row] }}{{ columnsList[cell.column] }}
|
||||
|
@ -81,6 +82,12 @@ export default {
|
|||
cellIsOccupied(row, column) {
|
||||
return this.assignedItems.some((item) => item.position[0] === row + 1 && item.position[1] === column + 1);
|
||||
},
|
||||
assignRow(row, column) {
|
||||
if (this.cellIsOccupied(row, column)) {
|
||||
return;
|
||||
}
|
||||
this.$emit('assign', [row + 1, column + 1]);
|
||||
},
|
||||
handleScroll() {
|
||||
this.$refs.columnsContainer.scrollLeft = this.$refs.cellsContainer.scrollLeft;
|
||||
this.$refs.rowContainer.scrollTop = this.$refs.cellsContainer.scrollTop;
|
||||
|
|
99
app/javascript/vue/storage_locations/modals/assign.vue
Normal file
99
app/javascript/vue/storage_locations/modals/assign.vue
Normal file
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_title`) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="mb-4">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_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 && withGrid"
|
||||
: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">
|
||||
{{ i18n.t(`storage_locations.show.assign_modal.${assignMode}_action`) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
import RowSelector from './assign/row_selector.vue';
|
||||
import ContainerSelector from './assign/container_selector.vue';
|
||||
import PositionSelector from './assign/position_selector.vue';
|
||||
import {
|
||||
storage_location_storage_location_repository_rows_path,
|
||||
move_storage_location_storage_location_repository_row_path,
|
||||
|
||||
} from '../../../routes.js';
|
||||
|
||||
export default {
|
||||
name: 'NewProjectModal',
|
||||
props: {
|
||||
selectedRow: Number,
|
||||
selectedContainer: Number,
|
||||
cellId: Number,
|
||||
selectedPosition: Array,
|
||||
withGrid: Boolean,
|
||||
assignMode: String
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
computed: {
|
||||
createUrl() {
|
||||
return storage_location_storage_location_repository_rows_path({
|
||||
storage_location_id: this.containerId
|
||||
});
|
||||
},
|
||||
moveUrl() {
|
||||
return move_storage_location_storage_location_repository_row_path(this.containerId, this.cellId);
|
||||
},
|
||||
actionUrl() {
|
||||
return this.assignMode === 'assign' ? this.createUrl : this.moveUrl;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rowId: this.selectedRow,
|
||||
containerId: this.selectedContainer,
|
||||
position: this.selectedPosition
|
||||
};
|
||||
},
|
||||
components: {
|
||||
RowSelector,
|
||||
ContainerSelector,
|
||||
PositionSelector
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
axios.post(this.actionUrl, {
|
||||
repository_row_id: this.rowId,
|
||||
metadata: { position: this.position?.map((pos) => parseInt(pos, 10)) }
|
||||
}).then(() => {
|
||||
this.$emit('close');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<div class="sci-input-container-v2 left-icon">
|
||||
<input type="text"
|
||||
v-model="query"
|
||||
class="sci-input-field"
|
||||
ref="input"
|
||||
autofocus="true"
|
||||
:placeholder=" i18n.t('storage_locations.index.move_modal.placeholder.find_storage_locations')" />
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
<div class="p-2 flex items-center gap-2 cursor-pointer text-sn-blue hover:bg-sn-super-light-grey"
|
||||
@click="selectStorageLocation(null)"
|
||||
:class="{'!bg-sn-super-light-blue': selectedStorageLocationId == null}">
|
||||
<i class="sn-icon sn-icon-projects"></i>
|
||||
{{ i18n.t('storage_locations.index.move_modal.search_header') }}
|
||||
</div>
|
||||
<MoveTree :storageLocationTrees="filteredStorageLocationTree" :value="selectedStorageLocationId" @selectStorageLocation="selectStorageLocation" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MoveTreeMixin from '../move_tree_mixin';
|
||||
|
||||
export default {
|
||||
name: 'ContainerSelector',
|
||||
mixins: [MoveTreeMixin],
|
||||
data() {
|
||||
return {
|
||||
container: true
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedStorageLocationId() {
|
||||
this.$emit('change', this.selectedStorageLocationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div class="">
|
||||
<div class="sci-label">{{ i18n.t(`storage_locations.show.assign_modal.row`) }}</div>
|
||||
<SelectDropdown
|
||||
:options="availableRows"
|
||||
:value="selectedRow"
|
||||
@change="selectedRow = $event"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sci-label">{{ i18n.t(`storage_locations.show.assign_modal.column`) }}</div>
|
||||
<SelectDropdown
|
||||
:disabled="!selectedRow"
|
||||
:options="availableColumns"
|
||||
:value="selectedColumn"
|
||||
@change="selectedColumn= $event"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectDropdown from '../../../shared/select_dropdown.vue';
|
||||
import axios from '../../../../packs/custom_axios.js';
|
||||
import {
|
||||
available_positions_storage_location_path,
|
||||
} from '../../../../routes.js';
|
||||
|
||||
export default {
|
||||
name: 'PositionSelector',
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
props: {
|
||||
selectedContainerId: Number
|
||||
},
|
||||
created() {
|
||||
axios.get(this.positionsUrl)
|
||||
.then((response) => {
|
||||
this.availablePositions = response.data.positions;
|
||||
this.$nextTick(() => {
|
||||
[[this.selectedRow]] = this.availableRows;
|
||||
this.$nextTick(() => {
|
||||
[[this.selectedColumn]] = this.availableColumns;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
selectedRow() {
|
||||
[[this.selectedColumn]] = this.availableColumns;
|
||||
},
|
||||
selectedColumn() {
|
||||
this.$emit('change', [this.selectedRow, this.selectedColumn]);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
positionsUrl() {
|
||||
return available_positions_storage_location_path(this.selectedContainerId);
|
||||
},
|
||||
availableRows() {
|
||||
return Object.keys(this.availablePositions).map((row) => [row, row]);
|
||||
},
|
||||
availableColumns() {
|
||||
return (this.availablePositions[this.selectedRow] || []).map((col) => [col, col]);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
availablePositions: {},
|
||||
selectedRow: null,
|
||||
selectedColumn: null
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<div class="sci-label">{{ i18n.t(`storage_locations.show.assign_modal.inventory`) }}</div>
|
||||
<SelectDropdown
|
||||
:optionsUrl="repositoriesUrl"
|
||||
placeholder="Select inventory"
|
||||
:searchable="true"
|
||||
@change="selectedRepository = $event"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sci-label">{{ i18n.t(`storage_locations.show.assign_modal.item`) }}</div>
|
||||
<SelectDropdown
|
||||
:disabled="!selectedRepository"
|
||||
:optionsUrl="rowsUrl"
|
||||
:urlParams="{ repository_id: selectedRepository }"
|
||||
placeholder="Select item"
|
||||
:searchable="true"
|
||||
@change="selectedRow= $event"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectDropdown from '../../../shared/select_dropdown.vue';
|
||||
import {
|
||||
list_team_repositories_path,
|
||||
rows_list_team_repositories_path
|
||||
} from '../../../../routes.js';
|
||||
|
||||
export default {
|
||||
name: 'RowSelector',
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
created() {
|
||||
this.teamId = document.body.dataset.currentTeamId;
|
||||
},
|
||||
watch: {
|
||||
selectedRepository() {
|
||||
this.selectedRow = null;
|
||||
},
|
||||
selectedRow() {
|
||||
this.$emit('change', this.selectedRow);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
repositoriesUrl() {
|
||||
return list_team_repositories_path(this.teamId);
|
||||
},
|
||||
rowsUrl() {
|
||||
if (!this.selectedRepository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rows_list_team_repositories_path(this.teamId);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedRepository: null,
|
||||
selectedRow: null,
|
||||
teamId: null
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -51,16 +51,15 @@
|
|||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
import MoveTree from './move_tree.vue';
|
||||
import MoveTreeMixin from './move_tree_mixin';
|
||||
|
||||
export default {
|
||||
name: 'NewProjectModal',
|
||||
props: {
|
||||
selectedObject: Array,
|
||||
storageLocationTreeUrl: String,
|
||||
moveToUrl: String
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
mixins: [modalMixin, MoveTreeMixin],
|
||||
data() {
|
||||
return {
|
||||
selectedStorageLocationId: null,
|
||||
|
@ -68,37 +67,7 @@ export default {
|
|||
query: ''
|
||||
};
|
||||
},
|
||||
components: {
|
||||
MoveTree
|
||||
},
|
||||
mounted() {
|
||||
axios.get(this.storageLocationTreeUrl).then((response) => {
|
||||
this.storageLocationTree = response.data;
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
filteredStorageLocationTree() {
|
||||
if (this.query === '') {
|
||||
return this.storageLocationTree;
|
||||
}
|
||||
|
||||
return this.storageLocationTree.map((storageLocation) => (
|
||||
{
|
||||
storage_location: storageLocation.storage_location,
|
||||
children: storageLocation.children.filter((child) => (
|
||||
child.storage_location.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
))
|
||||
}
|
||||
)).filter((storageLocation) => (
|
||||
storageLocation.storage_location.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
|| storageLocation.children.length > 0
|
||||
));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectStorageLocation(storageLocationId) {
|
||||
this.selectedStorageLocationId = storageLocationId;
|
||||
},
|
||||
submit() {
|
||||
axios.post(this.moveToUrl, {
|
||||
destination_storage_location_id: this.selectedStorageLocationId || 'root_storage_location'
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
class="cursor-pointer flex items-center pl-1 flex-1 gap-2
|
||||
text-sn-blue hover:bg-sn-super-light-grey"
|
||||
:class="{'!bg-sn-super-light-blue': storageLocationTree.storage_location.id == value}">
|
||||
<i class="sn-icon sn-icon-folder"></i>
|
||||
<i v-if="storageLocationTree.storage_location.container" class="sn-icon sn-icon-item"></i>
|
||||
<div class="flex-1 truncate p-2 pl-0" :title="storageLocationTree.storage_location.name">
|
||||
{{ storageLocationTree.storage_location.name }}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import axios from '../../../packs/custom_axios.js';
|
||||
import MoveTree from './move_tree.vue';
|
||||
import {
|
||||
tree_storage_locations_path
|
||||
} from '../../../routes.js';
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
axios.get(this.storageLocationTreeUrl).then((response) => {
|
||||
this.storageLocationTree = response.data;
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedStorageLocationId: null,
|
||||
storageLocationTree: [],
|
||||
query: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
storageLocationTreeUrl() {
|
||||
return tree_storage_locations_path({ format: 'json', container: this.container });
|
||||
},
|
||||
filteredStorageLocationTree() {
|
||||
if (this.query === '') {
|
||||
return this.storageLocationTree;
|
||||
}
|
||||
|
||||
return this.storageLocationTree.map((storageLocation) => (
|
||||
{
|
||||
storage_location: storageLocation.storage_location,
|
||||
children: storageLocation.children.filter((child) => (
|
||||
child.storage_location.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
))
|
||||
}
|
||||
)).filter((storageLocation) => (
|
||||
storageLocation.storage_location.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
|| storageLocation.children.length > 0
|
||||
));
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MoveTree
|
||||
},
|
||||
methods: {
|
||||
selectStorageLocation(storageLocationId) {
|
||||
this.selectedStorageLocationId = storageLocationId;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -24,7 +24,7 @@
|
|||
:editStorageLocation="editStorageLocation"
|
||||
/>
|
||||
<MoveModal v-if="objectToMove" :moveToUrl="moveToUrl"
|
||||
:selectedObject="objectToMove" :storageLocationTreeUrl="storageLocationTreeUrl"
|
||||
:selectedObject="objectToMove"
|
||||
@close="objectToMove = null" @move="updateTable()" />
|
||||
<ConfirmationModal
|
||||
:title="storageLocationDeleteTitle"
|
||||
|
@ -68,9 +68,6 @@ export default {
|
|||
},
|
||||
directUploadUrl: {
|
||||
type: String
|
||||
},
|
||||
storageLocationTreeUrl: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -47,6 +47,24 @@ class StorageLocation < ApplicationRecord
|
|||
metadata['dimensions'] if with_grid?
|
||||
end
|
||||
|
||||
def available_positions
|
||||
return unless with_grid?
|
||||
|
||||
occupied_positions = storage_location_repository_rows.pluck(:metadata).map { |metadata| metadata['position'] }
|
||||
|
||||
rows = {}
|
||||
|
||||
grid_size[0].times do |row|
|
||||
rows_cells = []
|
||||
grid_size[1].times.filter_map do |col|
|
||||
rows_cells.push(col + 1) if occupied_positions.exclude?([row + 1, col + 1])
|
||||
end
|
||||
rows[row + 1] = rows_cells unless rows_cells.empty?
|
||||
end
|
||||
|
||||
rows
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recursive_duplicate(old_parent_id = nil, new_parent_id = nil)
|
||||
|
|
|
@ -28,12 +28,10 @@ module Toolbars
|
|||
|
||||
def unassign_action
|
||||
{
|
||||
name: 'edit',
|
||||
name: 'unassign',
|
||||
label: I18n.t('storage_locations.show.toolbar.unassign'),
|
||||
icon: 'sn-icon sn-icon-close',
|
||||
path: unassign_storage_location_storage_location_repository_rows_path(
|
||||
@storage_location, ids: @assigned_rows.pluck(:id)
|
||||
),
|
||||
path: unassign_rows_storage_location_path(@storage_location, ids: @assigned_rows.pluck(:id)),
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
data-source="<%= storage_locations_path(format: :json, parent_id: params[:parent_id]) %>"
|
||||
direct-upload-url="<%= rails_direct_uploads_url %>"
|
||||
create-url="<%= storage_locations_path(parent_id: params[:parent_id]) if can_create_storage_locations?(current_team) %>"
|
||||
storage-location-tree-url="<%= tree_storage_locations_path %>"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
data-source="<%= storage_location_storage_location_repository_rows_path(@storage_location) %>"
|
||||
:with-grid="<%= @storage_location.with_grid? %>"
|
||||
:grid-size="<%= @storage_location.grid_size.to_json %>"
|
||||
:container-id="<%= @storage_location.id %>"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -105,4 +105,8 @@ Rails.application.configure do
|
|||
config.x.new_team_on_signup = false
|
||||
end
|
||||
config.hosts << "dev.scinote.test"
|
||||
|
||||
# Automatically update js-routes file
|
||||
# when routes.rb is changed
|
||||
config.middleware.use(JsRoutes::Middleware)
|
||||
end
|
||||
|
|
7
config/initializers/js_routes.rb
Normal file
7
config/initializers/js_routes.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
JsRoutes.setup do |c|
|
||||
# Setup your JS module system:
|
||||
# ESM, CJS, AMD, UMD or nil
|
||||
# c.module_type = "ESM"
|
||||
end
|
|
@ -2684,6 +2684,21 @@ en:
|
|||
assign: 'Assign item'
|
||||
unassign: 'Unassign'
|
||||
move: 'Move'
|
||||
unassign_modal:
|
||||
title: 'Unassign location'
|
||||
description: 'Are you sure you want to remove %{items} item(s) from their current storage location?'
|
||||
button: 'Unassign'
|
||||
assign_modal:
|
||||
assign_title: 'Assign position'
|
||||
move_title: 'Move item'
|
||||
assign_description: 'Select an item to assign it to a location.'
|
||||
move_description: 'Select a new location for your item.'
|
||||
assign_action: 'Assign'
|
||||
move_action: 'Move'
|
||||
row: 'Row'
|
||||
column: 'Column'
|
||||
inventory: 'Inventory'
|
||||
item: 'Item'
|
||||
index:
|
||||
head_title: "Locations"
|
||||
new_location: "New location"
|
||||
|
|
|
@ -194,6 +194,8 @@ Rails.application.routes.draw do
|
|||
get 'create_modal', to: 'repositories#create_modal',
|
||||
defaults: { format: 'json' }
|
||||
get 'actions_toolbar'
|
||||
get :list
|
||||
get :rows_list
|
||||
end
|
||||
member do
|
||||
get :export_empty_repository
|
||||
|
@ -815,11 +817,12 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
post :move
|
||||
post :duplicate
|
||||
post :unassign_rows
|
||||
get :available_positions
|
||||
end
|
||||
resources :storage_location_repository_rows, only: %i(index create destroy update) do
|
||||
collection do
|
||||
get :actions_toolbar
|
||||
post :unassign
|
||||
end
|
||||
member do
|
||||
post :move
|
||||
|
|
Loading…
Reference in a new issue