mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-12 16:14:58 +08:00
Merge pull request #8093 from andrej-scinote/aj_SCI_11354
Fix reordering of the form fields [SCI-11354]
This commit is contained in:
commit
705fc4da14
23 changed files with 611 additions and 16 deletions
|
@ -39,7 +39,7 @@ class FormFieldsController < ApplicationController
|
|||
if @form_field.discard
|
||||
render json: {}
|
||||
else
|
||||
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
|
||||
render json: { error: @form_field.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -47,8 +47,8 @@ class FormFieldsController < ApplicationController
|
|||
def reorder
|
||||
ActiveRecord::Base.transaction do
|
||||
params.permit(form_field_positions: %i(position id))[:form_field_positions].each do |data|
|
||||
form_field = @form.form_fields.find(data['id'].to_i)
|
||||
form_field.insert_at(data['position'].to_i)
|
||||
form_field = @form.form_fields.find(data[:id])
|
||||
form_field.insert_at(data[:position].to_i)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class FormsController < ApplicationController
|
||||
before_action :load_form, only: %i(show update publish unpublish)
|
||||
before_action :set_breadcrumbs_items, only: %i(index show)
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
|
@ -10,14 +11,15 @@ class FormsController < ApplicationController
|
|||
forms = Lists::FormsService.new(current_user, current_team, params).call
|
||||
render json: forms,
|
||||
each_serializer: Lists::FormSerializer,
|
||||
user: current_user
|
||||
user: current_user,
|
||||
meta: pagination_dict(forms)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json { render json: @form, serializer: Lists::FormSerializer, include: %i(form_fields), user: current_user }
|
||||
format.json { render json: @form, serializer: FormSerializer, include: %i(form_fields), user: current_user }
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
@ -117,8 +119,36 @@ class FormsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def actions_toolbar
|
||||
render json: {
|
||||
actions:
|
||||
Toolbars::FormsService.new(
|
||||
current_user,
|
||||
form_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_breadcrumbs_items
|
||||
@breadcrumbs_items = []
|
||||
|
||||
@breadcrumbs_items.push(
|
||||
{ label: t('breadcrumbs.templates') }
|
||||
)
|
||||
|
||||
@breadcrumbs_items.push(
|
||||
{ label: t('breadcrumbs.forms'), url: forms_path }
|
||||
)
|
||||
|
||||
if @form
|
||||
@breadcrumbs_items.push(
|
||||
{ label: @form.name }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def load_form
|
||||
@form = Form.find_by(id: params[:id])
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@ module LeftMenuBarHelper
|
|||
icon: 'sn-icon-protocols-templates',
|
||||
active: protocols_are_selected? || label_templates_are_selected?,
|
||||
submenu: [{
|
||||
url: forms_path,
|
||||
name: t('left_menu_bar.forms'),
|
||||
active: forms_are_selected?
|
||||
}, {
|
||||
url: protocols_path,
|
||||
name: t('left_menu_bar.protocol'),
|
||||
active: protocols_are_selected?
|
||||
|
@ -79,6 +83,10 @@ module LeftMenuBarHelper
|
|||
controller_name == 'protocols'
|
||||
end
|
||||
|
||||
def forms_are_selected?
|
||||
controller_name == 'forms'
|
||||
end
|
||||
|
||||
def label_templates_are_selected?
|
||||
controller_name == 'label_templates'
|
||||
end
|
||||
|
|
10
app/javascript/packs/vue/forms_show.js
Normal file
10
app/javascript/packs/vue/forms_show.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import FormShow from '../../vue/forms/show.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('FormShow', FormShow);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
mountWithTurbolinks(app, '#formShow');
|
10
app/javascript/packs/vue/forms_table.js
Normal file
10
app/javascript/packs/vue/forms_table.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import FormsTable from '../../vue/forms/table.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('FormsTable', FormsTable);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
mountWithTurbolinks(app, '#formsTable');
|
95
app/javascript/vue/forms/edit_field.vue
Normal file
95
app/javascript/vue/forms/edit_field.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center">
|
||||
<h3>{{ i18n.t(`forms.show.blocks.${editField.attributes.type}`) }}</h3>
|
||||
<div class="ml-auto flex items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="sci-toggle-checkbox-container">
|
||||
<input type="checkbox"
|
||||
class="sci-toggle-checkbox"
|
||||
@change="updateField"
|
||||
v-model="editField.attributes.required" />
|
||||
<span class="sci-toggle-checkbox-label"></span>
|
||||
</span>
|
||||
<span>{{ i18n.t('forms.show.required_label') }}</span>
|
||||
</div>
|
||||
<GeneralDropdown position="right">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-secondary icon-btn">
|
||||
<i class="sn-icon sn-icon-more-hori"></i>
|
||||
</button>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div @click="deleteField" class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer text-sn-delete-red">
|
||||
{{ i18n.t('forms.show.delete') }}
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4 w-full">
|
||||
<div>
|
||||
<label class="sci-label">{{ i18n.t('forms.show.title_label') }}</label>
|
||||
<div class="sci-input-container-v2" :class="{ 'error': editField.attributes.name.length == 0 }" >
|
||||
<input type="text" class="sci-input" v-model="editField.attributes.name" @change="updateField" :placeholder="i18n.t('forms.show.title_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="sci-label">{{ i18n.t('forms.show.description_label') }}</label>
|
||||
<div class="sci-input-container-v2" >
|
||||
<input type="text" class="sci-input" v-model="editField.attributes.description" @change="updateField" :placeholder="i18n.t('forms.show.description_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4 w-full">
|
||||
<div class="bg-sn-super-light-grey rounded p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<h5>{{ i18n.t('forms.show.mark_as_na') }}</h5>
|
||||
<span class="sci-toggle-checkbox-container">
|
||||
<input type="checkbox"
|
||||
class="sci-toggle-checkbox"
|
||||
@change="updateField"
|
||||
v-model="editField.attributes.allow_not_applicable" />
|
||||
<span class="sci-toggle-checkbox-label"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div>{{ i18n.t('forms.show.mark_as_na_explanation') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GeneralDropdown from '../shared/general_dropdown.vue';
|
||||
|
||||
export default {
|
||||
name: 'EditField',
|
||||
props: {
|
||||
field: Object
|
||||
},
|
||||
components: {
|
||||
GeneralDropdown
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editField: { ...this.field }
|
||||
};
|
||||
},
|
||||
created() {
|
||||
},
|
||||
computed: {
|
||||
validField() {
|
||||
return this.editField.attributes.name.length > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateField() {
|
||||
if (!this.validField) {
|
||||
return;
|
||||
}
|
||||
this.$emit('update', this.editField);
|
||||
},
|
||||
deleteField() {
|
||||
this.$emit('delete', this.editField);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
16
app/javascript/vue/forms/renderers/name.vue
Normal file
16
app/javascript/vue/forms/renderers/name.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<a :href="params.data.urls.show" :title="params.data.name">
|
||||
{{ params.data.name }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
params: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
169
app/javascript/vue/forms/show.vue
Normal file
169
app/javascript/vue/forms/show.vue
Normal file
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<div v-if="form" class="content-pane flexible with-grey-background">
|
||||
<div class="content-header flex items-center mb-4">
|
||||
<div class="title-row">
|
||||
<h1>
|
||||
<InlineEdit
|
||||
v-if="canManage"
|
||||
:value="form.attributes.name"
|
||||
:characterLimit="255"
|
||||
:characterMinLimit="2"
|
||||
:allowBlank="false"
|
||||
@editingEnabled="editingName = true"
|
||||
@editingDisabled="editingName = false"
|
||||
@update="updateName"
|
||||
/>
|
||||
<template v-else>
|
||||
{{ form.attributes.name }}
|
||||
</template>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 ml-auto">
|
||||
<button class="btn btn-secondary">
|
||||
<i class="sn-icon sn-icon-visibility-show"></i>
|
||||
{{ i18n.t('forms.show.test_form') }}
|
||||
</button>
|
||||
<button class="btn btn-primary">
|
||||
{{ i18n.t('forms.show.publish') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<div class="bg-white rounded-xl grid grid-cols-[360px_auto] min-h-[calc(100vh_-_200px)]">
|
||||
<div class="p-6 border-transparent border-r-sn-sleepy-grey border-solid border-r">
|
||||
<h3 class="mb-3">{{ i18n.t('forms.show.build_form') }}</h3>
|
||||
<div class="mb-3 flex flex-col gap-3">
|
||||
<div v-for="(field) in fields"
|
||||
@click="activeField = field"
|
||||
:key="field.id"
|
||||
class="font-bold p-3 rounded-lg border-sn-grey-100 cursor-pointer border"
|
||||
:class="{ '!border-sn-blue bg-sn-super-light-blue': activeField.id === field.id }"
|
||||
>
|
||||
{{ field.attributes.name }}
|
||||
</div>
|
||||
</div>
|
||||
<GeneralDropdown>
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-secondary w-full">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
{{ i18n.t('forms.show.add_block') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div v-for="e in newFields" :key="e.type" @click="addField(e.type)" class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer">
|
||||
{{ e.name }}
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<EditField
|
||||
:key="activeField.id"
|
||||
v-if="activeField.id"
|
||||
:field="activeField"
|
||||
@update="updateField"
|
||||
@delete="deleteField"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import InlineEdit from '../shared/inline_edit.vue';
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
import GeneralDropdown from '../shared/general_dropdown.vue';
|
||||
import EditField from './edit_field.vue';
|
||||
|
||||
export default {
|
||||
name: 'ShowForm',
|
||||
props: {
|
||||
formUrl: String
|
||||
},
|
||||
components: {
|
||||
InlineEdit,
|
||||
GeneralDropdown,
|
||||
EditField
|
||||
},
|
||||
computed: {
|
||||
canManage() {
|
||||
return true;
|
||||
},
|
||||
newFields() {
|
||||
return [
|
||||
{ name: this.i18n.t('forms.show.blocks.text'), type: 'text' },
|
||||
{ name: this.i18n.t('forms.show.blocks.number'), type: 'number' },
|
||||
{ name: this.i18n.t('forms.show.blocks.single_choice'), type: 'single_choice' },
|
||||
{ name: this.i18n.t('forms.show.blocks.multiple_choice'), type: 'multiple_choice' },
|
||||
{ name: this.i18n.t('forms.show.blocks.datetime'), type: 'datetime' }
|
||||
];
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadForm();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: null,
|
||||
fields: [],
|
||||
activeField: {}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
loadForm() {
|
||||
axios.get(this.formUrl).then((response) => {
|
||||
this.form = response.data.data;
|
||||
this.fields = response.data.included || [];
|
||||
if (this.fields.length > 0) {
|
||||
[this.activeField] = this.fields;
|
||||
}
|
||||
});
|
||||
},
|
||||
addField(type) {
|
||||
axios.post(this.form.attributes.urls.create_field, {
|
||||
form_field: {
|
||||
name: this.i18n.t(`forms.show.blocks.${type}`),
|
||||
data: {
|
||||
type
|
||||
}
|
||||
}
|
||||
}).then((response) => {
|
||||
this.fields.push(response.data.data);
|
||||
if (this.fields.length === 1) {
|
||||
[this.activeField] = this.fields;
|
||||
}
|
||||
});
|
||||
},
|
||||
updateName(name) {
|
||||
axios.put(this.formUrl, {
|
||||
form: {
|
||||
name
|
||||
}
|
||||
}).then(() => {
|
||||
this.form.attributes.name = name;
|
||||
});
|
||||
},
|
||||
updateField(field) {
|
||||
const index = this.fields.findIndex((f) => f.id === field.id);
|
||||
axios.put(field.attributes.urls.show, {
|
||||
form_field: field.attributes
|
||||
}).then((response) => {
|
||||
this.fields.splice(index, 1, response.data.data);
|
||||
});
|
||||
},
|
||||
deleteField(field) {
|
||||
const index = this.fields.findIndex((f) => f.id === field.id);
|
||||
axios.delete(field.attributes.urls.show).then(() => {
|
||||
this.fields.splice(index, 1);
|
||||
if (this.fields.length > 0) {
|
||||
[this.activeField] = this.fields;
|
||||
} else {
|
||||
this.activeField = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
113
app/javascript/vue/forms/table.vue
Normal file
113
app/javascript/vue/forms/table.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div class="h-full">
|
||||
<DataTable :columnDefs="columnDefs"
|
||||
:tableId="'FormsTable'"
|
||||
:dataUrl="dataSource"
|
||||
:reloadingTable="reloadingTable"
|
||||
:toolbarActions="toolbarActions"
|
||||
:actionsUrl="actionsUrl"
|
||||
@tableReloaded="reloadingTable = false"
|
||||
@create="createForm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import DeleteModal from '../shared/confirmation_modal.vue';
|
||||
import NameRenderer from './renderers/name.vue';
|
||||
|
||||
export default {
|
||||
name: 'FormsTable',
|
||||
components: {
|
||||
DataTable,
|
||||
DeleteModal,
|
||||
NameRenderer
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
actionsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createUrl: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reloadingTable: false,
|
||||
columnDefs: [
|
||||
{
|
||||
field: 'name',
|
||||
headerName: this.i18n.t('forms.index.table.name'),
|
||||
cellRenderer: 'NameRenderer',
|
||||
sortable: true
|
||||
}, {
|
||||
field: 'code',
|
||||
headerName: this.i18n.t('forms.index.table.code'),
|
||||
sortable: true
|
||||
}, {
|
||||
field: 'versions',
|
||||
headerName: this.i18n.t('forms.index.table.versions'),
|
||||
sortable: true
|
||||
}, {
|
||||
field: 'used_in_protocols',
|
||||
headerName: this.i18n.t('forms.index.table.used_in_protocols'),
|
||||
sortable: true
|
||||
}, {
|
||||
field: 'access',
|
||||
headerName: this.i18n.t('forms.index.table.access'),
|
||||
sortable: true
|
||||
}, {
|
||||
field: 'published_by',
|
||||
headerName: this.i18n.t('forms.index.table.published_by'),
|
||||
sortable: true
|
||||
}, {
|
||||
field: 'published_on',
|
||||
headerName: this.i18n.t('forms.index.table.published_on'),
|
||||
sortable: true
|
||||
}, {
|
||||
field: 'updated_at',
|
||||
headerName: this.i18n.t('forms.index.table.updated_on'),
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
toolbarActions() {
|
||||
const left = [];
|
||||
if (this.createUrl) {
|
||||
left.push({
|
||||
name: 'create',
|
||||
icon: 'sn-icon sn-icon-new-task',
|
||||
label: this.i18n.t('forms.index.toolbar.new'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary'
|
||||
});
|
||||
}
|
||||
return {
|
||||
left,
|
||||
right: []
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createForm(action) {
|
||||
axios.post(action.path).then((response) => {
|
||||
window.location.href = response.data.data.attributes.urls.show;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Form < ApplicationRecord
|
||||
ID_PREFIX = 'FR'
|
||||
include PrefixedIdModel
|
||||
include ArchivableModel
|
||||
|
||||
belongs_to :team
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
class FormField < ApplicationRecord
|
||||
include Discard::Model
|
||||
|
||||
default_scope -> { kept }
|
||||
|
||||
belongs_to :form
|
||||
belongs_to :created_by, class_name: 'User'
|
||||
belongs_to :last_modified_by, class_name: 'User'
|
||||
|
@ -11,5 +13,8 @@ class FormField < ApplicationRecord
|
|||
validates :description, length: { maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :position, presence: true, uniqueness: { scope: :form }
|
||||
|
||||
acts_as_list scope: :form, top_of_list: 0, sequential_updates: true
|
||||
acts_as_list scope: [:form, discarded_at: nil], top_of_list: 0, sequential_updates: true
|
||||
|
||||
private
|
||||
|
||||
end
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormFieldSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :description, :updated_at, :type, :required, :allow_not_applicable, :uid, :position
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :name, :description, :updated_at, :type, :required,
|
||||
:allow_not_applicable, :uid, :position, :urls
|
||||
|
||||
def type
|
||||
object.data[:type]
|
||||
object.data['type']
|
||||
end
|
||||
|
||||
def urls
|
||||
{
|
||||
show: form_form_field_path(object.form, object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
33
app/serializers/form_serializer.rb
Normal file
33
app/serializers/form_serializer.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :name, :published_on, :published_by, :updated_at, :urls
|
||||
|
||||
has_many :form_fields,
|
||||
key: :form_fields,
|
||||
serializer: FormFieldSerializer,
|
||||
order: :position
|
||||
|
||||
|
||||
def published_by
|
||||
object.published_by&.full_name
|
||||
end
|
||||
|
||||
def published_on
|
||||
I18n.l(object.published_on, format: :full) if object.published_on
|
||||
end
|
||||
|
||||
def updated_at
|
||||
I18n.l(object.updated_at, format: :full) if object.updated_at
|
||||
end
|
||||
|
||||
def urls
|
||||
{
|
||||
show: form_path(object),
|
||||
create_field: form_form_fields_path(object)
|
||||
}
|
||||
end
|
||||
end
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
module Lists
|
||||
class FormSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :description, :published_on, :published_by, :updated_at
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
has_many :form_fields, key: :form_fields, serializer: FormFieldSerializer
|
||||
attributes :id, :name, :published_on, :published_by, :updated_at, :urls, :code
|
||||
|
||||
def published_by
|
||||
object.published_by&.full_name
|
||||
|
@ -17,5 +18,11 @@ module Lists
|
|||
def updated_at
|
||||
I18n.l(object.updated_at, format: :full) if object.updated_at
|
||||
end
|
||||
|
||||
def urls
|
||||
{
|
||||
show: form_path(object)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
26
app/services/toolbars/forms_service.rb
Normal file
26
app/services/toolbars/forms_service.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Toolbars
|
||||
class FormsService
|
||||
attr_reader :current_user
|
||||
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def initialize(current_user, form_ids: [])
|
||||
@current_user = current_user
|
||||
@forms = Form.where(id: forms_ids)
|
||||
|
||||
@single = @forms.length == 1
|
||||
end
|
||||
|
||||
def actions
|
||||
return [] if @forms.none?
|
||||
|
||||
[].compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
end
|
||||
end
|
18
app/views/forms/index.html.erb
Normal file
18
app/views/forms/index.html.erb
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="content-pane flexible">
|
||||
<div class="content-header sticky-header">
|
||||
<div class="title-row">
|
||||
<h1>
|
||||
<%= t('forms.index.head_title') %>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div id="formsTable" class="fixed-content-body"
|
||||
>
|
||||
<forms-table
|
||||
actions-url="<%= actions_toolbar_forms_url %>"
|
||||
data-source="<%= forms_path(format: :json) %>"
|
||||
create-url="<%= forms_path %>"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<%= javascript_include_tag 'vue_form_table' %>
|
7
app/views/forms/show.html.erb
Normal file
7
app/views/forms/show.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
|||
<div id="formShow" >
|
||||
<form-show
|
||||
form-url="<%= form_path(@form) %>"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag 'vue_form_show' %>
|
|
@ -678,6 +678,7 @@ class Extends
|
|||
search/index
|
||||
storage_locations/index
|
||||
storage_locations/show
|
||||
forms/show
|
||||
)
|
||||
|
||||
DEFAULT_USER_NOTIFICATION_SETTINGS = {
|
||||
|
@ -706,6 +707,8 @@ class Extends
|
|||
ProjectList_archived_state
|
||||
ProtocolTemplates_active_state
|
||||
ProtocolTemplates_archived_state
|
||||
FormsTable_active_state
|
||||
FormsTable_archived_state
|
||||
ReportTemplates_active_state
|
||||
ReportTemplates_archived_state
|
||||
Repositories_active_state
|
||||
|
|
|
@ -350,6 +350,7 @@ en:
|
|||
projects: "Projects"
|
||||
repositories: "Inventories"
|
||||
templates: "Templates"
|
||||
forms: "Forms"
|
||||
protocol: "Protocol"
|
||||
label: "Label"
|
||||
items: "Items"
|
||||
|
@ -1053,7 +1054,6 @@ en:
|
|||
select_user_role: "Please select a user role."
|
||||
add_user_generic_error: "An error occurred. "
|
||||
can_add_user_to_project: "Can not add user to the project."
|
||||
|
||||
forms:
|
||||
default_name: "Untitled form"
|
||||
restored:
|
||||
|
@ -1062,7 +1062,38 @@ en:
|
|||
archived:
|
||||
success_flash: "<strong>%{number}</strong> form(s) successfully archived."
|
||||
error_flash: "Failed to archive form(s)."
|
||||
|
||||
index:
|
||||
head_title: "Forms"
|
||||
toolbar:
|
||||
new: 'New form'
|
||||
table:
|
||||
name: 'Form name'
|
||||
code: 'ID'
|
||||
versions: 'Versions'
|
||||
used_in_protocols: 'Used in protocols'
|
||||
access: 'Access'
|
||||
published_by: 'Published by'
|
||||
published_on: 'Published on'
|
||||
updated_on: 'Updated on'
|
||||
show:
|
||||
build_form: 'Build your form'
|
||||
add_block: 'Add a block'
|
||||
title_label: Title (required)
|
||||
title_placeholder: 'Add a title'
|
||||
description_label: Description
|
||||
description_placeholder: 'Add a description'
|
||||
required_label: 'Required'
|
||||
mark_as_na: 'Mark as N/A'
|
||||
mark_as_na_explanation: 'Allow user to mark the field as Not-applicable.'
|
||||
delete: 'Delete'
|
||||
test_form: 'Test form'
|
||||
publish: 'Publish'
|
||||
blocks:
|
||||
text: 'Text'
|
||||
number: 'Number'
|
||||
single_choice: 'Single choice'
|
||||
multiple_choice: 'Multiple choice'
|
||||
datetime: 'Date & Time'
|
||||
label_templates:
|
||||
types:
|
||||
fluics_label_template: 'Fluics'
|
||||
|
@ -4483,6 +4514,7 @@ en:
|
|||
locations: "Locations"
|
||||
label_printer: "Label printer"
|
||||
fluics_printer: "Fluics printer"
|
||||
forms: "Forms"
|
||||
|
||||
Add: "Add"
|
||||
Asset: "File"
|
||||
|
|
|
@ -857,6 +857,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
collection do
|
||||
get :actions_toolbar
|
||||
post :archive
|
||||
post :restore
|
||||
end
|
||||
|
|
|
@ -68,7 +68,9 @@ const entryList = {
|
|||
vue_legacy_repository_menu_dropdown: './app/javascript/packs/vue/legacy/repository_menu_dropdown.js',
|
||||
vue_dashboard_new_task: './app/javascript/packs/vue/dashboard_new_task.js',
|
||||
vue_storage_locations_table: './app/javascript/packs/vue/storage_locations_table.js',
|
||||
vue_storage_locations_container: './app/javascript/packs/vue/storage_locations_container.js'
|
||||
vue_storage_locations_container: './app/javascript/packs/vue/storage_locations_container.js',
|
||||
vue_form_show: './app/javascript/packs/vue/forms_show.js',
|
||||
vue_form_table: './app/javascript/packs/vue/forms_table.js'
|
||||
};
|
||||
|
||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||
|
|
|
@ -33,8 +33,6 @@ class CreateForms < ActiveRecord::Migration[7.0]
|
|||
t.string :uid
|
||||
t.datetime :discarded_at, index: true
|
||||
|
||||
t.index %i(form_id position), unique: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
|
|
@ -47,7 +47,7 @@ describe FormFieldsController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'PUT create' do
|
||||
describe 'PUT update' do
|
||||
let(:action) { put :update, params: params, format: :json }
|
||||
let(:params) do
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue