mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-10 13:59:56 +08:00
Add create/edit modal for storage locations [SCI-10860]/
This commit is contained in:
parent
60ce8aa425
commit
d0c07d6a3d
7 changed files with 323 additions and 20 deletions
|
@ -18,13 +18,14 @@ class StorageLocationsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
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)
|
@storage_location.update(storage_location_params)
|
||||||
|
|
||||||
if @storage_location.save
|
if @storage_location.save
|
||||||
render json: @storage_location, serializer: Lists::StorageLocationSerializer
|
render json: @storage_location, serializer: Lists::StorageLocationSerializer
|
||||||
else
|
else
|
||||||
render json: @storage_location.errors, status: :unprocessable_entity
|
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -33,12 +34,12 @@ class StorageLocationsController < ApplicationController
|
||||||
storage_location_params.merge({ team: current_team, created_by: current_user })
|
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
|
if @storage_location.save
|
||||||
render json: @storage_location, serializer: Lists::StorageLocationSerializer
|
render json: @storage_location, serializer: Lists::StorageLocationSerializer
|
||||||
else
|
else
|
||||||
render json: @storage_location.errors, status: :unprocessable_entity
|
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,8 +64,8 @@ class StorageLocationsController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def storage_location_params
|
def storage_location_params
|
||||||
params.permit(:id, :parent_id, :name, :container, :signed_blob_id, :description,
|
params.permit(:id, :parent_id, :name, :container, :description,
|
||||||
metadata: { dimensions: [], parent_coordinations: [], display_type: :string })
|
metadata: [:display_type, dimensions: [], parent_coordinations: []])
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_storage_location
|
def load_storage_location
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
{{ i18n.t('repositories.import_records.dragAndDropUpload.importText.firstPart') }}
|
{{ i18n.t('repositories.import_records.dragAndDropUpload.importText.firstPart') }}
|
||||||
</span> {{ i18n.t('repositories.import_records.dragAndDropUpload.importText.secondPart') }}
|
</span> {{ i18n.t('repositories.import_records.dragAndDropUpload.importText.secondPart') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sn-grey">
|
<div class="text-sn-grey text-center">
|
||||||
{{ supportingText }}
|
{{ supportingText }}
|
||||||
</div>
|
</div>
|
||||||
</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"
|
:reloadingTable="reloadingTable"
|
||||||
:toolbarActions="toolbarActions"
|
:toolbarActions="toolbarActions"
|
||||||
:actionsUrl="actionsUrl"
|
:actionsUrl="actionsUrl"
|
||||||
@archive="archive"
|
@create_location="openCreateLocationModal"
|
||||||
@restore="restore"
|
@create_box="openCreateBoxModal"
|
||||||
@delete="deleteRepository"
|
@edit="edit"
|
||||||
@update="update"
|
|
||||||
@duplicate="duplicate"
|
|
||||||
@export="exportRepositories"
|
|
||||||
@share="share"
|
|
||||||
@create="newRepository = true"
|
|
||||||
@tableReloaded="reloadingTable = false"
|
@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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -23,11 +28,13 @@
|
||||||
/* global */
|
/* global */
|
||||||
|
|
||||||
import DataTable from '../shared/datatable/table.vue';
|
import DataTable from '../shared/datatable/table.vue';
|
||||||
|
import EditModal from './modals/new_edit.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RepositoriesTable',
|
name: 'RepositoriesTable',
|
||||||
components: {
|
components: {
|
||||||
DataTable
|
DataTable,
|
||||||
|
EditModal
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
dataSource: {
|
dataSource: {
|
||||||
|
@ -40,11 +47,17 @@ export default {
|
||||||
},
|
},
|
||||||
createUrl: {
|
createUrl: {
|
||||||
type: String
|
type: String
|
||||||
|
},
|
||||||
|
directUploadUrl: {
|
||||||
|
type: String
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
reloadingTable: false
|
reloadingTable: false,
|
||||||
|
openEditModal: false,
|
||||||
|
editModalMode: null,
|
||||||
|
editStorageLocation: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -126,14 +139,34 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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
|
// Renderers
|
||||||
nameRenderer(params) {
|
nameRenderer(params) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
urls
|
urls
|
||||||
} = params.data;
|
} = 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"
|
return `<a class="hover:no-underline flex items-center gap-1"
|
||||||
title="${name}" href="${urls.show}">
|
title="${name}" href="${urls.show}">
|
||||||
|
${boxIcon}
|
||||||
<span class="truncate">${name}</span>
|
<span class="truncate">${name}</span>
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,24 @@ module Lists
|
||||||
class StorageLocationSerializer < ActiveModel::Serializer
|
class StorageLocationSerializer < ActiveModel::Serializer
|
||||||
include Rails.application.routes.url_helpers
|
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
|
def owned_by
|
||||||
object.team.name
|
object.team.name
|
||||||
end
|
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
|
def created_by
|
||||||
object.created_by.full_name
|
object.created_by.full_name
|
||||||
end
|
end
|
||||||
|
@ -19,8 +31,14 @@ module Lists
|
||||||
end
|
end
|
||||||
|
|
||||||
def urls
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
<storage-locations
|
<storage-locations
|
||||||
actions-url="<%= actions_toolbar_storage_locations_path(current_team) %>"
|
actions-url="<%= actions_toolbar_storage_locations_path(current_team) %>"
|
||||||
data-source="<%= storage_locations_path(format: :json, parent_id: params[:parent_id]) %>"
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2687,6 +2687,33 @@ en:
|
||||||
owned_by: "Owned by"
|
owned_by: "Owned by"
|
||||||
created_on: "Created on"
|
created_on: "Created on"
|
||||||
description: "Description"
|
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:
|
libraries:
|
||||||
manange_modal_column_index:
|
manange_modal_column_index:
|
||||||
title: "Manage columns"
|
title: "Manage columns"
|
||||||
|
|
Loading…
Add table
Reference in a new issue