mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-02 18:04:29 +08:00
Add form interacrtions to step [SCI-11341][SCI-11381]
This commit is contained in:
parent
07b6a9e8db
commit
f9d7066d55
24 changed files with 416 additions and 114 deletions
|
@ -9,7 +9,8 @@ class FormFieldValuesController < ApplicationController
|
|||
@form_field_value = @form_response.create_value!(
|
||||
current_user,
|
||||
@form_field,
|
||||
form_field_value_params[:value]
|
||||
form_field_value_params[:value],
|
||||
not_applicable: form_field_value_params[:not_applicable]
|
||||
)
|
||||
|
||||
render json: @form_field_value, serializer: FormFieldValueSerializer, user: current_user
|
||||
|
@ -18,7 +19,7 @@ class FormFieldValuesController < ApplicationController
|
|||
private
|
||||
|
||||
def form_field_value_params
|
||||
params.require(:form_field_value).permit(:form_field_id, :value)
|
||||
params.require(:form_field_value).permit(:form_field_id, :value, :not_applicable, value: [])
|
||||
end
|
||||
|
||||
def load_form_response
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormResponsesController < ApplicationController
|
||||
before_action :load_form, only: :create
|
||||
before_action :load_parent, only: :create
|
||||
before_action :load_form_response, except: :create
|
||||
|
||||
def create
|
||||
case @parent
|
||||
when Step
|
||||
render_403 and return unless can_create_protocol_form_responses?(@parent.protocol)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@form_response = FormResponse.create!(form: @form, created_by: current_user)
|
||||
@parent.step_orderable_elements.create!(orderable: @form_response)
|
||||
end
|
||||
else
|
||||
render_422
|
||||
end
|
||||
|
||||
render json: @form_response, serializer: FormResponseSerializer, user: current_user
|
||||
end
|
||||
|
||||
def submit
|
||||
render_403 and return unless can_submit_form_response?(@form_response)
|
||||
|
||||
@form_response.submit!(current_user)
|
||||
|
||||
render json: @form_response, serializer: FormResponseSerializer, user: current_user
|
||||
end
|
||||
|
||||
def reset
|
||||
render_403 and return unless can_reset_form_response?(@form_response)
|
||||
|
||||
@form_response.reset!(current_user)
|
||||
|
||||
render json: @form_response, serializer: FormResponseSerializer, user: current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_response_params
|
||||
params.require(:form_response).permit(:form_id, :parent_id, :parent_type)
|
||||
end
|
||||
|
||||
def load_form
|
||||
@form = Form.find_by(id: form_response_params[:form_id])
|
||||
|
||||
render_404 unless @form && can_read_form?(@form)
|
||||
end
|
||||
|
||||
def load_parent
|
||||
case form_response_params[:parent_type]
|
||||
when 'Step'
|
||||
@parent = Step.find_by(id: form_response_params[:parent_id])
|
||||
else
|
||||
return render_422
|
||||
end
|
||||
|
||||
render_404 unless @parent
|
||||
end
|
||||
|
||||
def load_form_response
|
||||
@form_response = FormResponse.find_by(id: params[:id])
|
||||
|
||||
render_404 unless @form_response
|
||||
end
|
||||
end
|
57
app/controllers/step_elements/form_responses_controller.rb
Normal file
57
app/controllers/step_elements/form_responses_controller.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
module StepElements
|
||||
class FormResponsesController < BaseController
|
||||
before_action :load_form, only: :create
|
||||
before_action :load_parent, only: :create
|
||||
before_action :load_form_response, except: :create
|
||||
|
||||
def create
|
||||
render_403 and return unless can_create_protocol_form_responses?(@parent.protocol)
|
||||
ActiveRecord::Base.transaction do
|
||||
@form_response = FormResponse.create!(form: @form, created_by: current_user)
|
||||
@parent.step_orderable_elements.create!(orderable: @form_response)
|
||||
end
|
||||
|
||||
render_step_orderable_element(@form_response)
|
||||
end
|
||||
|
||||
def submit
|
||||
render_403 and return unless can_submit_form_response?(@form_response)
|
||||
|
||||
@form_response.submit!(current_user)
|
||||
|
||||
render_step_orderable_element(@form_response)
|
||||
end
|
||||
|
||||
def reset
|
||||
render_403 and return unless can_reset_form_response?(@form_response)
|
||||
|
||||
new_form_response = @form_response.reset!(current_user)
|
||||
|
||||
render_step_orderable_element(@form_response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_response_params
|
||||
params.permit(:form_id, :step_id)
|
||||
end
|
||||
|
||||
def load_form
|
||||
@form = Form.find_by(id: form_response_params[:form_id])
|
||||
|
||||
render_404 unless @form && can_read_form?(@form)
|
||||
end
|
||||
|
||||
def load_parent
|
||||
@parent = Step.find_by(id: form_response_params[:step_id])
|
||||
render_404 unless @parent
|
||||
end
|
||||
|
||||
def load_form_response
|
||||
@form_response = FormResponse.find_by(id: params[:id])
|
||||
|
||||
render_404 unless @form_response
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,21 +3,23 @@
|
|||
<div>
|
||||
<div class="font-bold">
|
||||
{{ field.attributes.name }}
|
||||
<span v-if="unit">({{ unit }})</span>
|
||||
<span v-if="field.attributes.required" class="text-sn-delete-red">*</span>
|
||||
</div>
|
||||
<div v-if="field.attributes.description">
|
||||
{{ field.attributes.description }}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<component :is="field.attributes.data.type" :field="field" :marked_as_na="mark_as_na" />
|
||||
<component :is="field.attributes.data.type" ref="formField" :disabled="disabled" :field="field" :marked_as_na="markAsNa" @save="saveValue" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end items-end">
|
||||
<button class="btn btn-secondary mb-0.5"
|
||||
:disabled="disabled"
|
||||
v-if="field.attributes.allow_not_applicable"
|
||||
:class="{'!bg-sn-super-light-blue !border-sn-blue': mark_as_na}"
|
||||
@click="mark_as_na = !mark_as_na">
|
||||
<div class="w-4 h-4 !border-sn-blue border rounded-sm flex items-center justify-center" :class="{'bg-sn-blue': mark_as_na}">
|
||||
:class="{'!bg-sn-super-light-blue !border-sn-blue': markAsNa}"
|
||||
@click="markAsNa = !markAsNa">
|
||||
<div class="w-4 h-4 !border-sn-blue border rounded-sm flex items-center justify-center" :class="{'bg-sn-blue': markAsNa}">
|
||||
<i class="sn-icon sn-icon-check text-white" style="font-size: 16px !important;"></i>
|
||||
</div>
|
||||
{{ i18n.t('forms.fields.mark_as_na') }}
|
||||
|
@ -36,7 +38,12 @@ import MultipleChoiceField from './fields/multiple_choice.vue';
|
|||
export default {
|
||||
name: 'ViewField',
|
||||
props: {
|
||||
field: Object
|
||||
field: Object,
|
||||
disabled: Boolean,
|
||||
formResponse: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DatetimeField,
|
||||
|
@ -47,8 +54,30 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
mark_as_na: false
|
||||
markAsNa: this.field.field_value?.not_applicable || false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
markAsNa() {
|
||||
this.saveValue(null);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
unit() {
|
||||
return this.field.attributes.data.unit;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveValue(value) {
|
||||
if (this.formResponse) {
|
||||
this.$emit(
|
||||
'save',
|
||||
this.field.id,
|
||||
value,
|
||||
this.markAsNa
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -4,13 +4,17 @@
|
|||
<DateTimePicker
|
||||
@change="updateFromDate"
|
||||
:mode="mode"
|
||||
:defaultValue="fromValue"
|
||||
:clearable="true"
|
||||
:disabled="fieldDisabled"
|
||||
:placeholder="i18n.t('forms.fields.from')"
|
||||
:class="{'error': !validValue}"
|
||||
/>
|
||||
<DateTimePicker
|
||||
@change="updateToDate"
|
||||
:defaultValue="toValue"
|
||||
:mode="mode"
|
||||
:disabled="fieldDisabled"
|
||||
:clearable="true"
|
||||
:placeholder="i18n.t('forms.fields.to')"
|
||||
:class="{'error': !validValue}"
|
||||
|
@ -19,7 +23,9 @@
|
|||
<DateTimePicker
|
||||
v-else
|
||||
@change="updateDate"
|
||||
:defaultValue="value"
|
||||
:mode="mode"
|
||||
:disabled="fieldDisabled"
|
||||
:clearable="true"
|
||||
:placeholder="i18n.t(`forms.fields.add_${mode}`)"
|
||||
/>
|
||||
|
@ -43,6 +49,15 @@ export default {
|
|||
toValue: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.field.field_value?.datetime) {
|
||||
this.value = new Date(this.field.field_value.datetime);
|
||||
this.fromValue = new Date(this.field.field_value.datetime);
|
||||
}
|
||||
if (this.field.field_value?.datetime_to) {
|
||||
this.toValue = new Date(this.field.field_value.datetime_to);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mode() {
|
||||
return this.field.attributes.data.time ? 'datetime' : 'date';
|
||||
|
@ -57,15 +72,31 @@ export default {
|
|||
return this.value;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
marked_as_na() {
|
||||
if (this.marked_as_na) {
|
||||
this.value = null;
|
||||
this.fromValue = null;
|
||||
this.toValue = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateDate(date) {
|
||||
this.value = date;
|
||||
this.$emit('save', this.value);
|
||||
},
|
||||
updateFromDate(date) {
|
||||
this.fromValue = date;
|
||||
if (this.validValue) {
|
||||
this.$emit('save', [this.fromValue, this.toValue]);
|
||||
}
|
||||
},
|
||||
updateToDate(date) {
|
||||
this.toValue = date;
|
||||
if (this.validValue) {
|
||||
this.$emit('save', [this.fromValue, this.toValue]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
export default {
|
||||
props: {
|
||||
field: Object,
|
||||
marked_as_na: Boolean
|
||||
marked_as_na: Boolean,
|
||||
disabled: Boolean
|
||||
},
|
||||
computed: {
|
||||
fieldDisabled() {
|
||||
return this.marked_as_na || this.disabled;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<SelectDropdown
|
||||
:disabled="marked_as_na"
|
||||
:disabled="fieldDisabled"
|
||||
:options="options"
|
||||
:value="value"
|
||||
@change="saveValue"
|
||||
:multiple="true"
|
||||
:withCheckboxes="true"
|
||||
:clearable="true"
|
||||
|
@ -21,12 +23,20 @@ export default {
|
|||
SelectDropdown
|
||||
},
|
||||
computed: {
|
||||
value() {
|
||||
return (this.field.field_value?.selection || '[]');
|
||||
},
|
||||
options() {
|
||||
if (!this.field.attributes.data.options) {
|
||||
return [];
|
||||
}
|
||||
return this.field.attributes.data.options.map((option) => ([option, option]));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveValue(value) {
|
||||
this.$emit('save', value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="sci-input-container-v2 mb-2" :class="{'error': !isValidValue}" :data-error="errorMessage">
|
||||
<input type="number" v-model="value" class="sci-input" :disabled="marked_as_na" :placeholder="i18n.t('forms.fields.add_number')"></input>
|
||||
<input type="number" v-model="value" class="sci-input" :disabled="fieldDisabled" @change="saveValue" :placeholder="i18n.t('forms.fields.add_number')"></input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -12,7 +12,7 @@ export default {
|
|||
mixins: [fieldMixin],
|
||||
data() {
|
||||
return {
|
||||
value: ''
|
||||
value: this.field.field_value?.value
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -43,6 +43,13 @@ export default {
|
|||
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveValue() {
|
||||
if (this.isValidValue) {
|
||||
this.$emit('save', this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<SelectDropdown
|
||||
:disabled="marked_as_na"
|
||||
:disabled="fieldDisabled"
|
||||
:options="options"
|
||||
:value="value"
|
||||
@change="saveValue"
|
||||
:clearable="true"
|
||||
/>
|
||||
</div>
|
||||
|
@ -19,12 +21,20 @@ export default {
|
|||
SelectDropdown
|
||||
},
|
||||
computed: {
|
||||
value() {
|
||||
return this.field.field_value?.value;
|
||||
},
|
||||
options() {
|
||||
if (!this.field.attributes.data.options) {
|
||||
return [];
|
||||
}
|
||||
return this.field.attributes.data.options.map((option) => ([option, option]));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveValue(value) {
|
||||
this.$emit('save', value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="sci-input-container-v2 h-24">
|
||||
<textarea class="sci-input" :disabled="marked_as_na" :placeholder="i18n.t('forms.fields.add_text')"></textarea>
|
||||
<textarea class="sci-input" :value="value" :disabled="fieldDisabled" @change="saveValue" :placeholder="i18n.t('forms.fields.add_text')"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -9,6 +9,16 @@ import fieldMixin from './field_mixin';
|
|||
|
||||
export default {
|
||||
name: 'TextField',
|
||||
mixins: [fieldMixin]
|
||||
mixins: [fieldMixin],
|
||||
methods: {
|
||||
saveValue(event) {
|
||||
this.$emit('save', event.target.value);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value() {
|
||||
return this.field.field_value?.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -168,7 +168,11 @@
|
|||
@reorder="updateElementOrder"
|
||||
@close="closeReorderModal"
|
||||
/>
|
||||
<SelectFormModal v-if="openFormSelectModal" @close="openFormSelectModal = false" />
|
||||
<SelectFormModal
|
||||
v-if="openFormSelectModal"
|
||||
@close="openFormSelectModal = false"
|
||||
@submit="createElement('form_response', null, $event); openFormSelectModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -183,6 +187,7 @@
|
|||
import StepTable from '../shared/content/table.vue'
|
||||
import StepText from '../shared/content/text.vue'
|
||||
import Checklist from '../shared/content/checklist.vue'
|
||||
import FormResponse from '../shared/content/form_response.vue'
|
||||
import deleteStepModal from './modals/delete_step.vue'
|
||||
import Attachments from '../shared/content/attachments.vue'
|
||||
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
|
||||
|
@ -285,7 +290,8 @@
|
|||
ReorderableItemsModal,
|
||||
MenuDropdown,
|
||||
ContentToolbar,
|
||||
SelectFormModal
|
||||
SelectFormModal,
|
||||
FormResponse
|
||||
},
|
||||
created() {
|
||||
this.loadAttachments();
|
||||
|
@ -624,10 +630,10 @@
|
|||
}
|
||||
});
|
||||
},
|
||||
createElement(elementType, tableDimensions = null) {
|
||||
createElement(elementType, tableDimensions = null, formId = null) {
|
||||
let plateTemplate = tableDimensions != null;
|
||||
tableDimensions ||= [5, 5];
|
||||
$.post(this.urls[`create_${elementType}_url`], { tableDimensions: tableDimensions, plateTemplate: plateTemplate }, (result) => {
|
||||
$.post(this.urls[`create_${elementType}_url`], { tableDimensions: tableDimensions, plateTemplate: plateTemplate, form_id: formId }, (result) => {
|
||||
result.data.isNew = true;
|
||||
this.elements.push(result.data)
|
||||
|
||||
|
|
165
app/javascript/vue/shared/content/form_response.vue
Normal file
165
app/javascript/vue/shared/content/form_response.vue
Normal file
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<div class="content__form-container pr-8" :data-e2e="`e2e-CO-${dataE2e}-stepForm${element.id}`">
|
||||
<div class="sci-divider my-6" v-if="!inRepository"></div>
|
||||
<div class="flex items-center gap-4">
|
||||
<MenuDropdown
|
||||
class="ml-auto"
|
||||
:listItems="this.actionMenu"
|
||||
:btnClasses="'btn btn-light icon-btn btn-sm'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-${dataE2e}-stepText${element.id}-options`"
|
||||
@move="showMoveModal"
|
||||
@delete="showDeleteModal"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
<div class="max-w-[800px] w-full rounded bg-sn-super-light-grey p-6 flex flex-col gap-4">
|
||||
<div class="flex items-center">
|
||||
<h3 class="my-1">{{ form.name }}</h3>
|
||||
<div v-if="this.formResponse.status == 'submitted'" class="ml-auto text-right text-xs text-sn-grey-700">
|
||||
{{ i18n.t('forms.response.submitted_on') }} {{ this.formResponse.submitted_at }}<br>
|
||||
{{ i18n.t('forms.response.by') }} {{ this.formResponse.submitted_by_full_name }}
|
||||
</div>
|
||||
</div>
|
||||
<Field v-for="field in formFields" :disabled="formDisabled" ref="formFields" :key="field.id" :field="field" :formResponse="formResponse" @save="saveValue" />
|
||||
<div>
|
||||
<button v-if="this.formResponse.urls.submit" class="btn btn-primary" :disabled="!validResponse" @click="submitForm">
|
||||
{{ i18n.t('forms.response.submit') }}
|
||||
</button>
|
||||
<button v-else-if="this.formResponse.urls.reset" class="btn btn-secondary" @click="resetForm">
|
||||
{{ i18n.t('general.edit')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<deleteElementModal v-if="confirmingDelete" @confirm="deleteElement($event)" @cancel="closeDeleteModal"/>
|
||||
<moveElementModal v-if="movingElement"
|
||||
:parent_type="element.attributes.orderable.parent_type"
|
||||
:targets_url="''"
|
||||
@confirm="moveElement($event)" @cancel="closeMoveModal"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global I18n */
|
||||
|
||||
import DeleteMixin from './mixins/delete.js';
|
||||
import MoveMixin from './mixins/move.js';
|
||||
import deleteElementModal from './modal/delete.vue';
|
||||
import moveElementModal from './modal/move.vue';
|
||||
import MenuDropdown from '../menu_dropdown.vue';
|
||||
import Field from '../../forms/field.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'TextContent',
|
||||
components: {
|
||||
deleteElementModal,
|
||||
moveElementModal,
|
||||
MenuDropdown,
|
||||
Field
|
||||
},
|
||||
mixins: [DeleteMixin, MoveMixin],
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inRepository: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
reorderElementUrl: {
|
||||
type: String
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: this.element.attributes.orderable.form,
|
||||
formResponse: this.element.attributes.orderable,
|
||||
formFieldValues: this.element.attributes.orderable.form_field_values
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
computed: {
|
||||
formDisabled() {
|
||||
return !this.formResponse.urls.submit;
|
||||
},
|
||||
validResponse() {
|
||||
return this.formFields.every((field) => {
|
||||
if (field.attributes.required) {
|
||||
return field.field_value?.value
|
||||
|| field.field_value?.selection
|
||||
|| field.field_value?.datetime
|
||||
|| field.field_value?.datetime_to
|
||||
|| field.field_value?.not_applicable;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
formFields() {
|
||||
return this.element.attributes.orderable.form_fields.map((field) => ({
|
||||
id: field.id,
|
||||
attributes: field,
|
||||
field_value: this.formFieldValues.find((value) => value.form_field_id === field.id)
|
||||
}));
|
||||
},
|
||||
actionMenu() {
|
||||
const menu = [];
|
||||
if (this.element.attributes.orderable.urls.move_targets_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.move'),
|
||||
emit: 'move',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-move`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.delete_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.delete'),
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-delete`
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveValue(formFieldId, value, markAsNa) {
|
||||
axios.post(this.formResponse.urls.add_value, {
|
||||
form_field_value: {
|
||||
form_field_id: formFieldId,
|
||||
value,
|
||||
not_applicable: markAsNa
|
||||
}
|
||||
}).then((response) => {
|
||||
if (this.formFieldValues.find((formFieldValue) => formFieldValue.form_field_id === formFieldId)) {
|
||||
this.formFieldValues = this.formFieldValues.map((formFieldValue) => {
|
||||
if (formFieldValue.form_field_id === formFieldId) {
|
||||
return response.data.data.attributes;
|
||||
}
|
||||
return formFieldValue;
|
||||
});
|
||||
} else {
|
||||
this.formFieldValues.push(response.data.data.attributes);
|
||||
}
|
||||
});
|
||||
},
|
||||
submitForm() {
|
||||
axios.post(this.formResponse.urls.submit).then((response) => {
|
||||
const { attributes } = response.data.data;
|
||||
this.formResponse = attributes.orderable;
|
||||
});
|
||||
},
|
||||
resetForm() {
|
||||
axios.post(this.formResponse.urls.reset).then((response) => {
|
||||
const { attributes } = response.data.data;
|
||||
this.formResponse = attributes.orderable;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="close">{{ i18n.t('general.cancel') }}</button>
|
||||
<button v-if="anyForms" :disabled="!form" @submit="$emit('submit', form)" class="btn btn-primary">
|
||||
<button v-if="anyForms" :disabled="!form" @click="$emit('submit', form)" class="btn btn-primary">
|
||||
{{ i18n.t('protocols.steps.modals.form_modal.add_form') }}
|
||||
</button>
|
||||
<a v-else :href="formsPageUrl" class="btn btn-primary">
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
:format="format"
|
||||
:month-change-on-scroll="false"
|
||||
:six-weeks="true"
|
||||
:disabled="disabled"
|
||||
:auto-apply="true"
|
||||
:partial-flow="true"
|
||||
:markers="markers"
|
||||
|
|
|
@ -25,7 +25,7 @@ class FormResponse < ApplicationRecord
|
|||
step_orderable_element&.step
|
||||
end
|
||||
|
||||
def create_value!(created_by, form_field, value)
|
||||
def create_value!(created_by, form_field, value, not_applicable: false)
|
||||
ActiveRecord::Base.transaction(requires_new: true) do
|
||||
form_field_values.where(form_field: form_field).find_each do |form_field_value|
|
||||
form_field_value.update!(latest: false)
|
||||
|
@ -38,7 +38,8 @@ class FormResponse < ApplicationRecord
|
|||
created_by: created_by,
|
||||
submitted_by: created_by,
|
||||
submitted_at: DateTime.current,
|
||||
value: value
|
||||
value: value,
|
||||
not_applicable: not_applicable
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ Canaid::Permissions.register_for(FormResponse) do
|
|||
case parent
|
||||
when Step
|
||||
next false unless parent.protocol.my_module # protocol template forms can't be submitted
|
||||
next false unless form_response.pending?
|
||||
|
||||
parent.protocol.my_module.permission_granted?(user, FormResponsePermissions::SUBMIT)
|
||||
end
|
||||
|
@ -25,6 +26,7 @@ Canaid::Permissions.register_for(FormResponse) do
|
|||
case parent
|
||||
when Step
|
||||
next false unless parent.protocol.my_module # protocol template forms can't be reset
|
||||
next false unless form_response.submitted?
|
||||
|
||||
parent.protocol.my_module.permission_granted?(user, FormResponsePermissions::SUBMIT)
|
||||
end
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
class FormFieldValueSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
attributes :form_field_id, :type, :value, :submitted_at, :submitted_by_full_name, :unit
|
||||
attributes :form_field_id, :type, :value, :submitted_at, :submitted_by_full_name,
|
||||
:unit, :not_applicable, :selection, :datetime, :datetime_to
|
||||
|
||||
def submitted_by_full_name
|
||||
object.submitted_by.full_name
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FormResponseSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
attributes :id, :created_at, :form_id
|
||||
|
||||
has_many :form_field_values do
|
||||
object.form_field_values.latest
|
||||
end
|
||||
|
||||
def submitted_by_full_name
|
||||
object.submitted_by.full_name
|
||||
end
|
||||
end
|
|
@ -23,15 +23,16 @@ class FormSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def urls
|
||||
user = scope[:user] || @instance_options[:user]
|
||||
list = {
|
||||
show: form_path(object),
|
||||
create_field: form_form_fields_path(object),
|
||||
reorder_fields: reorder_form_form_fields_path(object)
|
||||
}
|
||||
|
||||
list[:publish] = publish_form_path(object) if can_publish_form?(current_user, object)
|
||||
list[:publish] = publish_form_path(object) if can_publish_form?(user, object)
|
||||
|
||||
list[:unpublish] = unpublish_form_path(object) if can_unpublish_form?(current_user, object)
|
||||
list[:unpublish] = unpublish_form_path(object) if can_unpublish_form?(user, object)
|
||||
|
||||
list
|
||||
end
|
||||
|
|
39
app/serializers/step_form_response_serializer.rb
Normal file
39
app/serializers/step_form_response_serializer.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StepFormResponseSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :created_at, :form_id, :urls, :submitted_by_full_name, :status, :submitted_at
|
||||
|
||||
has_one :form, serializer: FormSerializer
|
||||
|
||||
has_many :form_fields, serializer: FormFieldSerializer do
|
||||
object.form.form_fields
|
||||
end
|
||||
|
||||
has_many :form_field_values do
|
||||
object.form_field_values.latest
|
||||
end
|
||||
|
||||
def submitted_by_full_name
|
||||
object.submitted_by&.full_name
|
||||
end
|
||||
|
||||
def submitted_at
|
||||
I18n.l(object.submitted_at, format: :full) if object.submitted_at
|
||||
end
|
||||
|
||||
def urls
|
||||
user = scope[:user] || @instance_options[:user]
|
||||
|
||||
list = {
|
||||
add_value: form_response_form_field_values_path(object)
|
||||
}
|
||||
|
||||
list[:submit] = submit_step_form_response_path(object.step ,object) if can_submit_form_response?(user, object)
|
||||
list[:reset] = reset_step_form_response_path(object.step ,object) if can_reset_form_response?(user, object)
|
||||
|
||||
list
|
||||
end
|
||||
end
|
|
@ -11,6 +11,8 @@ class StepOrderableElementSerializer < ActiveModel::Serializer
|
|||
TableSerializer.new(object.orderable.table, scope: { user: @instance_options[:user] }).as_json
|
||||
when 'StepText'
|
||||
StepTextSerializer.new(object.orderable, scope: { user: @instance_options[:user] }).as_json
|
||||
when 'FormResponse'
|
||||
StepFormResponseSerializer.new(object.orderable, scope: { user: @instance_options[:user] }).as_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -97,6 +97,7 @@ class StepSerializer < ActiveModel::Serializer
|
|||
create_table_url: step_tables_path(object),
|
||||
create_text_url: step_texts_path(object),
|
||||
create_checklist_url: step_checklists_path(object),
|
||||
create_form_response_url: step_form_responses_path(object),
|
||||
update_asset_view_mode_url: update_asset_view_mode_step_path(object),
|
||||
update_view_state_url: update_view_state_step_path(object),
|
||||
direct_upload_url: rails_direct_uploads_url,
|
||||
|
|
|
@ -1133,6 +1133,10 @@ en:
|
|||
SingleChoiceField: 'Single choice'
|
||||
MultipleChoiceField: 'Multiple choice'
|
||||
DatetimeField: 'Date & Time'
|
||||
response:
|
||||
submit: 'Submit form'
|
||||
submitted_on: 'Submitted on'
|
||||
by: 'by'
|
||||
label_templates:
|
||||
types:
|
||||
fluics_label_template: 'Fluics'
|
||||
|
|
|
@ -613,6 +613,12 @@ Rails.application.routes.draw do
|
|||
post :reorder, on: :collection
|
||||
end
|
||||
end
|
||||
resources :form_responses, controller: 'step_elements/form_responses', only: %i(create) do
|
||||
member do
|
||||
post :submit
|
||||
post :reset
|
||||
end
|
||||
end
|
||||
member do
|
||||
get 'elements'
|
||||
get 'attachments'
|
||||
|
@ -876,12 +882,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :form_responses, only: %i(create) do
|
||||
member do
|
||||
post :submit
|
||||
post :reset
|
||||
end
|
||||
|
||||
resources :form_responses, only: [] do
|
||||
resources :form_field_values, only: %i(create)
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue