mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 06:35:56 +08:00
Add create/edit modal for storage locations [SCI-10860]/
This commit is contained in:
parent
60ce8aa425
commit
d0c07d6a3d
|
@ -18,13 +18,14 @@ class StorageLocationsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
@storage_location.image.attach(storage_location_params[:signed_blob_id]) if storage_location_params[:signed_blob_id]
|
||||
@storage_location.image.purge if params[:file_name].blank?
|
||||
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id]
|
||||
@storage_location.update(storage_location_params)
|
||||
|
||||
if @storage_location.save
|
||||
render json: @storage_location, serializer: Lists::StorageLocationSerializer
|
||||
else
|
||||
render json: @storage_location.errors, status: :unprocessable_entity
|
||||
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -33,12 +34,12 @@ class StorageLocationsController < ApplicationController
|
|||
storage_location_params.merge({ team: current_team, created_by: current_user })
|
||||
)
|
||||
|
||||
@storage_location.image.attach(storage_location_params[:signed_blob_id]) if storage_location_params[:signed_blob_id]
|
||||
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id]
|
||||
|
||||
if @storage_location.save
|
||||
render json: @storage_location, serializer: Lists::StorageLocationSerializer
|
||||
else
|
||||
render json: @storage_location.errors, status: :unprocessable_entity
|
||||
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -63,8 +64,8 @@ class StorageLocationsController < ApplicationController
|
|||
private
|
||||
|
||||
def storage_location_params
|
||||
params.permit(:id, :parent_id, :name, :container, :signed_blob_id, :description,
|
||||
metadata: { dimensions: [], parent_coordinations: [], display_type: :string })
|
||||
params.permit(:id, :parent_id, :name, :container, :description,
|
||||
metadata: [:display_type, dimensions: [], parent_coordinations: []])
|
||||
end
|
||||
|
||||
def load_storage_location
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
{{ i18n.t('repositories.import_records.dragAndDropUpload.importText.firstPart') }}
|
||||
</span> {{ i18n.t('repositories.import_records.dragAndDropUpload.importText.secondPart') }}
|
||||
</div>
|
||||
<div class="text-sn-grey">
|
||||
<div class="text-sn-grey text-center">
|
||||
{{ supportingText }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
223
app/javascript/vue/storage_locations/modals/new_edit.vue
Normal file
223
app/javascript/vue/storage_locations/modals/new_edit.vue
Normal file
|
@ -0,0 +1,223 @@
|
|||
<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.index.edit_modal.title_${mode}_${editModalMode}`) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p v-if="mode == 'create'" class="mb-6">{{ i18n.t(`storage_locations.index.edit_modal.description_create_${editModalMode}`) }}</p>
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">
|
||||
{{ i18n.t(`storage_locations.index.edit_modal.name_label_${editModalMode}`) }}
|
||||
</label>
|
||||
<div class="sci-input-container-v2">
|
||||
<input
|
||||
type="text"
|
||||
v-model="object.name"
|
||||
:placeholder="i18n.t(`storage_locations.index.edit_modal.name_placeholder`)"
|
||||
>
|
||||
</div>
|
||||
<span v-if="this.errors.name" class="text-sn-coral text-xs">{{ this.errors.name }}</span>
|
||||
</div>
|
||||
<div v-if="editModalMode == 'box'" class="mb-6">
|
||||
<label class="sci-label">
|
||||
{{ i18n.t(`storage_locations.index.edit_modal.dimensions_label`) }}
|
||||
</label>
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<div class="sci-radio-container">
|
||||
<input type="radio" class="sci-radio" :disabled="object.code" v-model="object.metadata.display_type" name="display_type" value="no_grid" >
|
||||
<span class="sci-radio-label"></span>
|
||||
</div>
|
||||
<span>{{ i18n.t('storage_locations.index.edit_modal.no_grid') }}</span>
|
||||
<i class="sn-icon sn-icon-info text-sn-grey" :title="i18n.t('storage_locations.index.edit_modal.no_grid_tooltip')"></i>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<div class="sci-radio-container">
|
||||
<input type="radio" class="sci-radio" :disabled="object.code" v-model="object.metadata.display_type" name="display_type" value="grid" >
|
||||
<span class="sci-radio-label"></span>
|
||||
</div>
|
||||
<span>{{ i18n.t('storage_locations.index.edit_modal.grid') }}</span>
|
||||
<div class="sci-input-container-v2 !w-28">
|
||||
<input type="number" :disabled="object.code" v-model="object.metadata.dimensions[0]" min="1" max="24">
|
||||
</div>
|
||||
<i class="sn-icon sn-icon-close-small"></i>
|
||||
<div class="sci-input-container-v2 !w-28">
|
||||
<input type="number" :disabled="object.code" v-model="object.metadata.dimensions[1]" min="1" max="24">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">
|
||||
{{ i18n.t(`storage_locations.index.edit_modal.image_label_${editModalMode}`) }}
|
||||
</label>
|
||||
<DragAndDropUpload
|
||||
v-if="!attachedImage && !object.file_name"
|
||||
class="h-60"
|
||||
@file:dropped="addFile"
|
||||
@file:error="handleError"
|
||||
@file:error:clear="this.imageError = null"
|
||||
:supportingText="`${i18n.t('storage_locations.index.edit_modal.drag_and_drop_supporting_text')}`"
|
||||
:supportedFormats="['jpg', 'png', 'jpeg']"
|
||||
/>
|
||||
<div v-else class="border border-sn-light-grey rounded flex items-center p-2 gap-2">
|
||||
<i class="sn-icon sn-icon-result-image text-sn-grey"></i>
|
||||
<span class="text-sn-blue">{{ object.file_name || attachedImage?.name }}</span>
|
||||
<i class="sn-icon sn-icon-close text-sn-blue ml-auto cursor-pointer" @click="removeImage"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">
|
||||
{{ i18n.t(`storage_locations.index.edit_modal.description_label`) }}
|
||||
</label>
|
||||
<div class="sci-input-container-v2 h-32">
|
||||
<textarea
|
||||
ref="description"
|
||||
v-model="object.description"
|
||||
:placeholder="i18n.t(`storage_locations.index.edit_modal.description_placeholder`)"
|
||||
></textarea>
|
||||
</div>
|
||||
<span v-if="this.errors.description" class="text-sn-coral text-xs">{{ this.errors.description }}</span>
|
||||
</div>
|
||||
</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" :disabled="!validObject" type="submit">
|
||||
{{ mode == 'create' ? i18n.t('general.create') : i18n.t('general.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule SmartAnnotation ActiveStorage GLOBAL_CONSTANTS */
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
import DragAndDropUpload from '../../shared/drag_and_drop_upload.vue';
|
||||
|
||||
export default {
|
||||
name: 'EditLocationModal',
|
||||
props: {
|
||||
createUrl: String,
|
||||
editModalMode: String,
|
||||
directUploadUrl: String,
|
||||
editStorageLocation: Object
|
||||
},
|
||||
components: {
|
||||
DragAndDropUpload
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
object: {
|
||||
metadata: {
|
||||
dimensions: [9, 9],
|
||||
display_type: 'grid'
|
||||
}
|
||||
},
|
||||
attachedImage: null,
|
||||
imageError: false,
|
||||
errors: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
mode() {
|
||||
return this.editStorageLocation ? 'edit' : 'create';
|
||||
},
|
||||
validObject() {
|
||||
this.errors = {};
|
||||
|
||||
if (!this.object.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.object.name.length > GLOBAL_CONSTANTS.NAME_MAX_LENGTH) {
|
||||
this.errors.name = this.i18n.t('storage_locations.index.edit_modal.errors.max_length', { max_length: GLOBAL_CONSTANTS.NAME_MAX_LENGTH });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.object.description && this.object.description.length > GLOBAL_CONSTANTS.TEXT_MAX_LENGTH) {
|
||||
this.errors.description = this.i18n.t('storage_locations.index.edit_modal.errors.max_length', { max_length: GLOBAL_CONSTANTS.NAME_MAX_LENGTH });
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.editStorageLocation) {
|
||||
this.object = this.editStorageLocation;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
SmartAnnotation.init($(this.$refs.description), false);
|
||||
$(this.$refs.modal).on('hidden.bs.modal', this.handleAtWhoModalClose);
|
||||
|
||||
this.object.container = this.editModalMode === 'box';
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (this.attachedImage) {
|
||||
this.uploadImage();
|
||||
} else {
|
||||
this.saveLocation();
|
||||
}
|
||||
},
|
||||
saveLocation() {
|
||||
if (this.object.code) {
|
||||
axios.put(this.object.urls.update, this.object)
|
||||
.then(() => {
|
||||
this.$emit('tableReloaded');
|
||||
HelperModule.flashAlertMsg(this.i18n.t(`storage_locations.index.edit_modal.success_message.edit_${this.editModalMode}`, { name: this.object.name }), 'success');
|
||||
this.close();
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
} else {
|
||||
axios.post(this.createUrl, this.object)
|
||||
.then(() => {
|
||||
this.$emit('tableReloaded');
|
||||
HelperModule.flashAlertMsg(this.i18n.t(`storage_locations.index.edit_modal.success_message.create_${this.editModalMode}`, { name: this.object.name }), 'success');
|
||||
this.close();
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
}
|
||||
},
|
||||
handleError() {
|
||||
},
|
||||
addFile(file) {
|
||||
this.attachedImage = file;
|
||||
},
|
||||
removeImage() {
|
||||
this.attachedImage = null;
|
||||
this.object.file_name = null;
|
||||
},
|
||||
uploadImage() {
|
||||
const upload = new ActiveStorage.DirectUpload(this.attachedImage, this.directUploadUrl);
|
||||
|
||||
upload.create((error, blob) => {
|
||||
if (error) {
|
||||
// Handle the error
|
||||
} else {
|
||||
this.object.signed_blob_id = blob.signed_id;
|
||||
this.saveLocation();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleAtWhoModalClose() {
|
||||
$('.atwho-view.old').css('display', 'none');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -6,16 +6,21 @@
|
|||
:reloadingTable="reloadingTable"
|
||||
:toolbarActions="toolbarActions"
|
||||
:actionsUrl="actionsUrl"
|
||||
@archive="archive"
|
||||
@restore="restore"
|
||||
@delete="deleteRepository"
|
||||
@update="update"
|
||||
@duplicate="duplicate"
|
||||
@export="exportRepositories"
|
||||
@share="share"
|
||||
@create="newRepository = true"
|
||||
@create_location="openCreateLocationModal"
|
||||
@create_box="openCreateBoxModal"
|
||||
@edit="edit"
|
||||
@tableReloaded="reloadingTable = false"
|
||||
/>
|
||||
<Teleport to="body">
|
||||
<EditModal v-if="openEditModal"
|
||||
@close="openEditModal = false"
|
||||
@tableReloaded="reloadingTable = true"
|
||||
:createUrl="createUrl"
|
||||
:editModalMode="editModalMode"
|
||||
:directUploadUrl="directUploadUrl"
|
||||
:editStorageLocation="editStorageLocation"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -23,11 +28,13 @@
|
|||
/* global */
|
||||
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import EditModal from './modals/new_edit.vue';
|
||||
|
||||
export default {
|
||||
name: 'RepositoriesTable',
|
||||
components: {
|
||||
DataTable
|
||||
DataTable,
|
||||
EditModal
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
|
@ -40,11 +47,17 @@ export default {
|
|||
},
|
||||
createUrl: {
|
||||
type: String
|
||||
},
|
||||
directUploadUrl: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reloadingTable: false
|
||||
reloadingTable: false,
|
||||
openEditModal: false,
|
||||
editModalMode: null,
|
||||
editStorageLocation: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -126,14 +139,34 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
openCreateLocationModal() {
|
||||
this.openEditModal = true;
|
||||
this.editModalMode = 'location';
|
||||
this.editStorageLocation = null;
|
||||
},
|
||||
openCreateBoxModal() {
|
||||
this.openEditModal = true;
|
||||
this.editModalMode = 'box';
|
||||
this.editStorageLocation = null;
|
||||
},
|
||||
edit(action, params) {
|
||||
this.openEditModal = true;
|
||||
this.editModalMode = params[0].container ? 'box' : 'location';
|
||||
[this.editStorageLocation] = params;
|
||||
},
|
||||
// Renderers
|
||||
nameRenderer(params) {
|
||||
const {
|
||||
name,
|
||||
urls
|
||||
} = params.data;
|
||||
let boxIcon = '';
|
||||
if (params.data.container) {
|
||||
boxIcon = '<i class="sn-icon sn-icon-item"></i>';
|
||||
}
|
||||
return `<a class="hover:no-underline flex items-center gap-1"
|
||||
title="${name}" href="${urls.show}">
|
||||
${boxIcon}
|
||||
<span class="truncate">${name}</span>
|
||||
</a>`;
|
||||
}
|
||||
|
|
|
@ -4,12 +4,24 @@ module Lists
|
|||
class StorageLocationSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :code, :name, :container, :description, :owned_by, :created_by, :created_on, :urls
|
||||
attributes :id, :code, :name, :container, :description, :owned_by, :created_by,
|
||||
:created_on, :urls, :metadata, :file_name
|
||||
|
||||
def owned_by
|
||||
object.team.name
|
||||
end
|
||||
|
||||
def metadata
|
||||
{
|
||||
display_type: object.metadata['display_type'],
|
||||
dimensions: object.metadata['dimensions'] || []
|
||||
}
|
||||
end
|
||||
|
||||
def file_name
|
||||
object.image.filename if object.image.attached?
|
||||
end
|
||||
|
||||
def created_by
|
||||
object.created_by.full_name
|
||||
end
|
||||
|
@ -19,8 +31,14 @@ module Lists
|
|||
end
|
||||
|
||||
def urls
|
||||
show_url = if @object.container
|
||||
storage_location_path(@object)
|
||||
else
|
||||
storage_locations_path(parent_id: object.id)
|
||||
end
|
||||
{
|
||||
show: storage_locations_path(parent_id: object.id),
|
||||
show: show_url,
|
||||
update: storage_location_path(@object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
<storage-locations
|
||||
actions-url="<%= actions_toolbar_storage_locations_path(current_team) %>"
|
||||
data-source="<%= storage_locations_path(format: :json, parent_id: params[:parent_id]) %>"
|
||||
create-url="<%= storage_locations_path if can_create_storage_locations?(current_team) %>"
|
||||
direct-upload-url="<%= rails_direct_uploads_url %>"
|
||||
create-url="<%= storage_locations_path(parent_id: params[:parent_id]) if can_create_storage_locations?(current_team) %>"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2687,6 +2687,33 @@ en:
|
|||
owned_by: "Owned by"
|
||||
created_on: "Created on"
|
||||
description: "Description"
|
||||
edit_modal:
|
||||
title_create_location: "Create new location"
|
||||
title_create_box: "Create new box"
|
||||
title_edit_location: "Edit location"
|
||||
title_edit_box: "Edit box"
|
||||
description_create_location: "Fill in the fields and create a new location."
|
||||
description_create_box: "Fill in the fields to create a new box. Defining the box dimensions allows you to control the number of available spaces for placing inventory items."
|
||||
name_label_location: "Location name"
|
||||
image_label_location: "Image of location"
|
||||
name_label_box: "Box name"
|
||||
image_label_box: "Image of box"
|
||||
drag_and_drop_supporting_text: ".png or .jpg file"
|
||||
description_label: "Description"
|
||||
name_placeholder: "Big freezer"
|
||||
description_placeholder: "Keep everyone on the same page. You can also use smart annotations."
|
||||
dimensions_label: "Dimensions (rows x columns)"
|
||||
no_grid: "No grid"
|
||||
grid: "Grid"
|
||||
no_grid_tooltip: "You can assign unlimited items to the “No-grid” box but they do not have assigned position."
|
||||
success_message:
|
||||
create_location: "Location %{name} was successfully created."
|
||||
create_box: "Box %{name} was successfully created."
|
||||
edit_location: "Location %{name} was successfully updated."
|
||||
edit_box: "Box %{name} was successfully updated."
|
||||
errors:
|
||||
max_length: "is too long (maximum is %{max_length} characters)"
|
||||
|
||||
libraries:
|
||||
manange_modal_column_index:
|
||||
title: "Manage columns"
|
||||
|
|
Loading…
Reference in a new issue