diff --git a/app/controllers/form_fields_controller.rb b/app/controllers/form_fields_controller.rb index 8adb1fa28..d637710e3 100644 --- a/app/controllers/form_fields_controller.rb +++ b/app/controllers/form_fields_controller.rb @@ -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 diff --git a/app/controllers/forms_controller.rb b/app/controllers/forms_controller.rb index b1c8961bb..577d52ca2 100644 --- a/app/controllers/forms_controller.rb +++ b/app/controllers/forms_controller.rb @@ -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]) diff --git a/app/helpers/left_menu_bar_helper.rb b/app/helpers/left_menu_bar_helper.rb index b6a2d22d2..60929d6d6 100644 --- a/app/helpers/left_menu_bar_helper.rb +++ b/app/helpers/left_menu_bar_helper.rb @@ -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 diff --git a/app/javascript/packs/vue/forms_show.js b/app/javascript/packs/vue/forms_show.js new file mode 100644 index 000000000..6391a90c9 --- /dev/null +++ b/app/javascript/packs/vue/forms_show.js @@ -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'); diff --git a/app/javascript/packs/vue/forms_table.js b/app/javascript/packs/vue/forms_table.js new file mode 100644 index 000000000..26de6a572 --- /dev/null +++ b/app/javascript/packs/vue/forms_table.js @@ -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'); diff --git a/app/javascript/vue/forms/edit_field.vue b/app/javascript/vue/forms/edit_field.vue new file mode 100644 index 000000000..fe69c678c --- /dev/null +++ b/app/javascript/vue/forms/edit_field.vue @@ -0,0 +1,95 @@ + + + diff --git a/app/javascript/vue/forms/renderers/name.vue b/app/javascript/vue/forms/renderers/name.vue new file mode 100644 index 000000000..ac969e84f --- /dev/null +++ b/app/javascript/vue/forms/renderers/name.vue @@ -0,0 +1,16 @@ + + + diff --git a/app/javascript/vue/forms/show.vue b/app/javascript/vue/forms/show.vue new file mode 100644 index 000000000..5ab29a66a --- /dev/null +++ b/app/javascript/vue/forms/show.vue @@ -0,0 +1,169 @@ + + + diff --git a/app/javascript/vue/forms/table.vue b/app/javascript/vue/forms/table.vue new file mode 100644 index 000000000..c5694de0f --- /dev/null +++ b/app/javascript/vue/forms/table.vue @@ -0,0 +1,113 @@ + + + diff --git a/app/models/form.rb b/app/models/form.rb index ef0597540..3ea72afeb 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Form < ApplicationRecord + ID_PREFIX = 'FR' + include PrefixedIdModel include ArchivableModel belongs_to :team diff --git a/app/models/form_field.rb b/app/models/form_field.rb index f4f432a45..30d97f576 100644 --- a/app/models/form_field.rb +++ b/app/models/form_field.rb @@ -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 diff --git a/app/serializers/form_field_serializer.rb b/app/serializers/form_field_serializer.rb index 30b8d1f8d..a9a1f001f 100644 --- a/app/serializers/form_field_serializer.rb +++ b/app/serializers/form_field_serializer.rb @@ -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 diff --git a/app/serializers/form_serializer.rb b/app/serializers/form_serializer.rb new file mode 100644 index 000000000..ee2c4ee9c --- /dev/null +++ b/app/serializers/form_serializer.rb @@ -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 diff --git a/app/serializers/lists/form_serializer.rb b/app/serializers/lists/form_serializer.rb index a44bce9bd..fc2a21c1e 100644 --- a/app/serializers/lists/form_serializer.rb +++ b/app/serializers/lists/form_serializer.rb @@ -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 diff --git a/app/services/toolbars/forms_service.rb b/app/services/toolbars/forms_service.rb new file mode 100644 index 000000000..7885f9ebb --- /dev/null +++ b/app/services/toolbars/forms_service.rb @@ -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 diff --git a/app/views/forms/index.html.erb b/app/views/forms/index.html.erb new file mode 100644 index 000000000..4c7eaa5a2 --- /dev/null +++ b/app/views/forms/index.html.erb @@ -0,0 +1,18 @@ +
+ +
+ +
+
+<%= javascript_include_tag 'vue_form_table' %> diff --git a/app/views/forms/show.html.erb b/app/views/forms/show.html.erb new file mode 100644 index 000000000..6df8ef32d --- /dev/null +++ b/app/views/forms/show.html.erb @@ -0,0 +1,7 @@ +
+ +
+ +<%= javascript_include_tag 'vue_form_show' %> diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index 8f8854f46..09e1b621f 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -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 diff --git a/config/locales/en.yml b/config/locales/en.yml index d900109db..8fae6c7f9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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: "%{number} 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" diff --git a/config/routes.rb b/config/routes.rb index 2bca68964..de8727242 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -857,6 +857,7 @@ Rails.application.routes.draw do end collection do + get :actions_toolbar post :archive post :restore end diff --git a/config/webpack/webpack.config.js b/config/webpack/webpack.config.js index 07ac593cd..0a8e3d022 100644 --- a/config/webpack/webpack.config.js +++ b/config/webpack/webpack.config.js @@ -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 diff --git a/db/migrate/20241209074134_create_forms.rb b/db/migrate/20241209074134_create_forms.rb index 3f38ef60f..73c0abf01 100644 --- a/db/migrate/20241209074134_create_forms.rb +++ b/db/migrate/20241209074134_create_forms.rb @@ -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 diff --git a/spec/controllers/form_fields_controller_spec.rb b/spec/controllers/form_fields_controller_spec.rb index e0cb0b99c..3e6739704 100644 --- a/spec/controllers/form_fields_controller_spec.rb +++ b/spec/controllers/form_fields_controller_spec.rb @@ -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 {