From df799af4d2288d12ec773a5a3cb4963de37bcd42 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sat, 4 Apr 2026 21:58:03 +0800 Subject: [PATCH 01/15] Add free edit as a field type --- app/javascript/project_template_fields.js | 1 + app/models/project_template_field.rb | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/javascript/project_template_fields.js b/app/javascript/project_template_fields.js index efda186c..813a7cb5 100644 --- a/app/javascript/project_template_fields.js +++ b/app/javascript/project_template_fields.js @@ -14,6 +14,7 @@ document.addEventListener("turbo:load", function () { { value: "textarea", label: "Textarea" }, { value: "dropdown", label: "Dropdown" }, { value: "radio", label: "Radio" }, + { value: "free_edit", label: "Free edit" }, ]; const applicableToOptions = [ diff --git a/app/models/project_template_field.rb b/app/models/project_template_field.rb index 0a320fec..efef15c1 100644 --- a/app/models/project_template_field.rb +++ b/app/models/project_template_field.rb @@ -2,15 +2,17 @@ class ProjectTemplateField < ApplicationRecord belongs_to :project_template has_many :project_instance_fields, dependent: :destroy - enum :field_type, { shorttext: 0, textarea: 1, dropdown: 2, radio: 3 } + enum :field_type, { shorttext: 0, textarea: 1, dropdown: 2, radio: 3, free_edit: 4 } enum :applicable_to, { topics: 0, proposals: 1, both: 2 } validates :label, presence: true validates :field_type, presence: true validates :applicable_to, presence: true validates :options, presence: true, if: -> { field_type.in?(%w[dropdown radio]) } + validates :required, inclusion: { in: [false] }, if: :free_edit? before_validation :force_title_required + before_validation :force_free_edit_unrequired before_destroy :cannot_delete_if_in_use def option_list @@ -29,4 +31,8 @@ def cannot_delete_if_in_use def force_title_required self.required = true if label == 'Project Title' end + + def force_free_edit_unrequired + self.required = false if free_edit? + end end From ebc6667227df28776489c7919be97a9b3a526234 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sat, 4 Apr 2026 23:03:35 +0800 Subject: [PATCH 02/15] Fix free edit can be required --- app/javascript/project_template_fields.js | 33 ++++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/app/javascript/project_template_fields.js b/app/javascript/project_template_fields.js index 813a7cb5..b6ae747f 100644 --- a/app/javascript/project_template_fields.js +++ b/app/javascript/project_template_fields.js @@ -198,6 +198,20 @@ document.addEventListener("turbo:load", function () { const optionsSection = fieldRow.querySelector(".options-section"); const addOptionBtn = optionsSection.querySelector(".add-option-btn"); const fieldType = e.target.value; + const requiredCheckbox = fieldRow.querySelector('input[type="checkbox"][name*="[required]"]'); + + if (requiredCheckbox) { + if (fieldType === "free_edit") { + requiredCheckbox.checked = false; + requiredCheckbox.disabled = true; + requiredCheckbox.style.cursor = "not-allowed"; + requiredCheckbox.title = "Free edit fields cannot be marked as required."; + } else { + requiredCheckbox.disabled = false; + requiredCheckbox.style.cursor = "pointer"; + requiredCheckbox.title = ""; + } + } if (optionsSection) { if (fieldType === "dropdown" || fieldType === "radio") { @@ -319,11 +333,12 @@ document.addEventListener("turbo:load", function () { } }); - // Initial check to disable remove button on Project Title + // Initial check to disable remove button on Project Title and handle free edit templateFields.querySelectorAll(".field-row").forEach((row) => { - const labelInput = row.querySelector( - 'textarea[name*="[label]"], input[name*="[label]"]', - ); + const labelInput = row.querySelector('textarea[name*="[label]"], input[name*="[label]"]'); + const typeSelect = row.querySelector(".field-type-select"); + const requiredCheckbox = row.querySelector('input[type="checkbox"][name*="[required]"]'); + if (labelInput && labelInput.value.trim() === "Project Title") { const btn = row.querySelector(".remove-field"); if (btn) { @@ -331,9 +346,6 @@ document.addEventListener("turbo:load", function () { btn.title = "Cannot remove title"; btn.classList.add("opacity-50", "cursor-not-allowed"); } - const requiredCheckbox = row.querySelector( - 'input[type="checkbox"][name*="[required]"]', - ); if (requiredCheckbox) { requiredCheckbox.checked = true; requiredCheckbox.disabled = true; @@ -341,6 +353,13 @@ document.addEventListener("turbo:load", function () { requiredCheckbox.title = "Title is Required"; } } + + if (typeSelect && typeSelect.value === "free_edit" && requiredCheckbox) { + requiredCheckbox.checked = false; + requiredCheckbox.disabled = true; + requiredCheckbox.style.cursor = "not-allowed"; + requiredCheckbox.title = "Free edit fields cannot be marked as required."; + } }); // Focus handling for visual feedback From 0901aa6cc01cca4303e5eff786e6c763dd1bbd0c Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 13:17:44 +0800 Subject: [PATCH 03/15] Revert changes to main --- app/javascript/project_template_fields.js | 34 +++++------------------ app/models/project_template_field.rb | 8 +----- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/app/javascript/project_template_fields.js b/app/javascript/project_template_fields.js index b6ae747f..efda186c 100644 --- a/app/javascript/project_template_fields.js +++ b/app/javascript/project_template_fields.js @@ -14,7 +14,6 @@ document.addEventListener("turbo:load", function () { { value: "textarea", label: "Textarea" }, { value: "dropdown", label: "Dropdown" }, { value: "radio", label: "Radio" }, - { value: "free_edit", label: "Free edit" }, ]; const applicableToOptions = [ @@ -198,20 +197,6 @@ document.addEventListener("turbo:load", function () { const optionsSection = fieldRow.querySelector(".options-section"); const addOptionBtn = optionsSection.querySelector(".add-option-btn"); const fieldType = e.target.value; - const requiredCheckbox = fieldRow.querySelector('input[type="checkbox"][name*="[required]"]'); - - if (requiredCheckbox) { - if (fieldType === "free_edit") { - requiredCheckbox.checked = false; - requiredCheckbox.disabled = true; - requiredCheckbox.style.cursor = "not-allowed"; - requiredCheckbox.title = "Free edit fields cannot be marked as required."; - } else { - requiredCheckbox.disabled = false; - requiredCheckbox.style.cursor = "pointer"; - requiredCheckbox.title = ""; - } - } if (optionsSection) { if (fieldType === "dropdown" || fieldType === "radio") { @@ -333,12 +318,11 @@ document.addEventListener("turbo:load", function () { } }); - // Initial check to disable remove button on Project Title and handle free edit + // Initial check to disable remove button on Project Title templateFields.querySelectorAll(".field-row").forEach((row) => { - const labelInput = row.querySelector('textarea[name*="[label]"], input[name*="[label]"]'); - const typeSelect = row.querySelector(".field-type-select"); - const requiredCheckbox = row.querySelector('input[type="checkbox"][name*="[required]"]'); - + const labelInput = row.querySelector( + 'textarea[name*="[label]"], input[name*="[label]"]', + ); if (labelInput && labelInput.value.trim() === "Project Title") { const btn = row.querySelector(".remove-field"); if (btn) { @@ -346,6 +330,9 @@ document.addEventListener("turbo:load", function () { btn.title = "Cannot remove title"; btn.classList.add("opacity-50", "cursor-not-allowed"); } + const requiredCheckbox = row.querySelector( + 'input[type="checkbox"][name*="[required]"]', + ); if (requiredCheckbox) { requiredCheckbox.checked = true; requiredCheckbox.disabled = true; @@ -353,13 +340,6 @@ document.addEventListener("turbo:load", function () { requiredCheckbox.title = "Title is Required"; } } - - if (typeSelect && typeSelect.value === "free_edit" && requiredCheckbox) { - requiredCheckbox.checked = false; - requiredCheckbox.disabled = true; - requiredCheckbox.style.cursor = "not-allowed"; - requiredCheckbox.title = "Free edit fields cannot be marked as required."; - } }); // Focus handling for visual feedback diff --git a/app/models/project_template_field.rb b/app/models/project_template_field.rb index efef15c1..0a320fec 100644 --- a/app/models/project_template_field.rb +++ b/app/models/project_template_field.rb @@ -2,17 +2,15 @@ class ProjectTemplateField < ApplicationRecord belongs_to :project_template has_many :project_instance_fields, dependent: :destroy - enum :field_type, { shorttext: 0, textarea: 1, dropdown: 2, radio: 3, free_edit: 4 } + enum :field_type, { shorttext: 0, textarea: 1, dropdown: 2, radio: 3 } enum :applicable_to, { topics: 0, proposals: 1, both: 2 } validates :label, presence: true validates :field_type, presence: true validates :applicable_to, presence: true validates :options, presence: true, if: -> { field_type.in?(%w[dropdown radio]) } - validates :required, inclusion: { in: [false] }, if: :free_edit? before_validation :force_title_required - before_validation :force_free_edit_unrequired before_destroy :cannot_delete_if_in_use def option_list @@ -31,8 +29,4 @@ def cannot_delete_if_in_use def force_title_required self.required = true if label == 'Project Title' end - - def force_free_edit_unrequired - self.required = false if free_edit? - end end From 9973f76664198165f52fe170aa18aaf2fb3d176f Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 20:27:33 +0800 Subject: [PATCH 04/15] Add column 'free_edit' into 'project_template_fields' --- ...0260405053252_add_free_edit_to_project_template_fields.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20260405053252_add_free_edit_to_project_template_fields.rb diff --git a/db/migrate/20260405053252_add_free_edit_to_project_template_fields.rb b/db/migrate/20260405053252_add_free_edit_to_project_template_fields.rb new file mode 100644 index 00000000..ab59ca4f --- /dev/null +++ b/db/migrate/20260405053252_add_free_edit_to_project_template_fields.rb @@ -0,0 +1,5 @@ +class AddFreeEditToProjectTemplateFields < ActiveRecord::Migration[8.0] + def change + add_column :project_template_fields, :free_edit, :boolean, default: false, null: false + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index d2b345fe..3066bcbc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_04_05_032531) do +ActiveRecord::Schema[8.0].define(version: 2026_04_05_053252) do create_table "comments", force: :cascade do |t| t.integer "user_id", null: false t.string "text", null: false @@ -134,6 +134,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "required", default: true + t.boolean "free_edit", default: false, null: false t.index ["project_template_id"], name: "index_project_template_fields_on_project_template_id" end From 2e7b11cb7549edd526bb6b15f260b325bc47de67 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 20:45:50 +0800 Subject: [PATCH 05/15] Add free edit option in edit template page --- .../project_templates/_field_row.html.erb | 26 ++++--- app/views/project_templates/edit.html.erb | 68 +++++++------------ 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/app/views/project_templates/_field_row.html.erb b/app/views/project_templates/_field_row.html.erb index 0df9fdfb..686fcc33 100644 --- a/app/views/project_templates/_field_row.html.erb +++ b/app/views/project_templates/_field_row.html.erb @@ -155,14 +155,22 @@ <%# --- Required --- %> - - Required - <%= field_form.check_box :required, - class: - "block w-full py-2.5 pl-3 pr-8 border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 sm:text-sm cursor-pointer" %> + + Required +
+ <%= field_form.check_box :required, + class: "h-5 w-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer" %> + +
+ + + <%# --- Free Edit --- %> + + Free Edit +
+ <%= field_form.check_box :free_edit, + class: "h-5 w-5 rounded border-gray-300 text-green-600 focus:ring-green-500 cursor-pointer" %> + +
diff --git a/app/views/project_templates/edit.html.erb b/app/views/project_templates/edit.html.erb index 1bb2a4a2..87d3721c 100644 --- a/app/views/project_templates/edit.html.erb +++ b/app/views/project_templates/edit.html.erb @@ -7,7 +7,7 @@ "Edit Project Template - #{@course.course_name.truncate(TITLE_NAME_LIMIT)} | ProPro" %>
-
+
<%# --- Page Header --- %>
@@ -48,54 +48,32 @@ <%= render "courses/copy_course_overlay", course: @course, mode: "template" %>
- +
<%# --- Table Header --- %> - - - - - - - + + + + + + + From f2df8dcf53abd18971e49bb5368ae58672b8b3b0 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 20:57:20 +0800 Subject: [PATCH 06/15] Fix new field showing old row style --- .../project_templates_controller.rb | 2 +- app/javascript/project_template_fields.js | 85 +++++++++++++------ 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/app/controllers/project_templates_controller.rb b/app/controllers/project_templates_controller.rb index 4d5c85a3..5f59d4b3 100644 --- a/app/controllers/project_templates_controller.rb +++ b/app/controllers/project_templates_controller.rb @@ -38,7 +38,7 @@ def project_template_params params.require(:project_template).permit( :description, project_template_fields_attributes: [ - :id, :label, :hint, :field_type, :applicable_to, :_destroy, { options: [] }, :required + :id, :label, :hint, :field_type, :applicable_to, :_destroy, { options: [] }, :required, :free_edit ] ) end diff --git a/app/javascript/project_template_fields.js b/app/javascript/project_template_fields.js index efda186c..92691d20 100644 --- a/app/javascript/project_template_fields.js +++ b/app/javascript/project_template_fields.js @@ -46,39 +46,50 @@ document.addEventListener("turbo:load", function () { - - + + `; From c6934d33a4ce4d964d9f1d07a9f32bb287e888c8 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 21:11:15 +0800 Subject: [PATCH 07/15] Project title cannot be free edit --- app/javascript/project_template_fields.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/javascript/project_template_fields.js b/app/javascript/project_template_fields.js index 92691d20..e0f0288d 100644 --- a/app/javascript/project_template_fields.js +++ b/app/javascript/project_template_fields.js @@ -347,7 +347,7 @@ document.addEventListener("turbo:load", function () { } }); - // Initial check to disable remove button on Project Title + // Initial check to disable remove and free edit button on Project Title templateFields.querySelectorAll(".field-row").forEach((row) => { const labelInput = row.querySelector( 'textarea[name*="[label]"], input[name*="[label]"]', @@ -368,6 +368,14 @@ document.addEventListener("turbo:load", function () { requiredCheckbox.required = true; requiredCheckbox.title = "Title is Required"; } + const freeEditCheckbox = row.querySelector( + 'input[type="checkbox"][name*="[free_edit]"]' + ); + if (freeEditCheckbox) { + freeEditCheckbox.checked = false; + freeEditCheckbox.disabled = true; + freeEditCheckbox.title = "Title cannot be changed after approval"; + } } }); From 5045902d0b5605f08beefd3ec1091da770e9e9c3 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 22:01:47 +0800 Subject: [PATCH 08/15] Allow edit proposal after approval --- app/controllers/projects_controller.rb | 77 +++++++++++--------------- app/policies/project_policy.rb | 14 ++++- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 15aad414..c24c1f5f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -209,6 +209,8 @@ def create def update authorize @project || Project.new(course: @course) + is_approved = @project.status == 'approved' || @project.approved? + has_supervisor_comment = false @project.project_instances.last.comments.each do |comment| if comment.user_id == @project.supervisor.id @@ -222,7 +224,7 @@ def update begin ActiveRecord::Base.transaction do - return unless @project.editable? + raise StandardError, 'This project is locked and cannot be edited.' unless @project.editable? || is_approved @instance = @project.instance_to_edit( created_by: current_user, @@ -230,68 +232,53 @@ def update ) new_instance_created = @instance.new_record? - # Set title - title_field_id = params[:fields].keys.first if params[:fields].present? - @instance.title = params[:fields][title_field_id] if title_field_id.present? + if !is_approved && params[:fields].present? + title_field_id = params[:fields].keys.first + @instance.title = params[:fields][title_field_id] if title_field_id.present? + end - # Timestamps @instance.last_edit_time = Time.current @instance.last_edit_by = current_user.id + @instance.save! - raise StandardError unless @instance.save - - raise StandardError unless params[:fields].present? + raise StandardError, 'No field data provided.' if params[:fields].blank? params[:fields].each do |field_id, value| - existing_field = ProjectInstanceField.find_by( + template_field = ProjectTemplateField.find(field_id) + + next if is_approved && !template_field.free_edit + + existing_field = ProjectInstanceField.find_or_initialize_by( project_template_field_id: field_id, instance: @instance ) - - if existing_field - existing_field.update!(value: value) - else - @instance.project_instance_fields.create!( - project_template_field_id: field_id, - value: value - ) - end + existing_field.update!(value: value) end - raise StandardError, 'Please choose a lecturer and topic' if params[:based_on_topic].blank? + unless is_approved + raise StandardError, 'Please choose a lecturer and topic' if params[:based_on_topic].blank? - # 2 formats, PROJECT_ID or own_proposal_LECTURER_ID - if params[:based_on_topic].start_with?('own_proposal_') - # Extract lecturer ID from value - lecturer_id = params[:based_on_topic].split('_').last.to_i + if params[:based_on_topic].start_with?('own_proposal_') + lecturer_id = params[:based_on_topic].split('_').last.to_i + supervisor_enrolment = Enrolment.find_by(id: lecturer_id, course_id: @course.id, role: :lecturer) + raise StandardError, 'Lecturer not found' unless supervisor_enrolment - # Find lecturer enrolment for course - supervisor_enrolment = Enrolment.find_by(id: lecturer_id, course_id: @course.id, role: :lecturer) - - raise StandardError unless supervisor_enrolment - - @instance.update!(source_topic_id: nil) - else - # Treat as topic_id - topic = Topic.find_by(id: params[:based_on_topic], course: @course) - - raise StandardError unless topic - - raise StandardError unless topic.owner.is_a?(User) + @instance.update!(source_topic_id: nil) + else + topic = Topic.find_by(id: params[:based_on_topic], course: @course) + raise StandardError, 'Topic not found' unless topic && topic.owner.is_a?(User) - supervisor_enrolment = Enrolment.find_by(user_id: topic.owner.id, course_id: @course.id, role: :lecturer) + supervisor_enrolment = Enrolment.find_by(user_id: topic.owner.id, course_id: @course.id, role: :lecturer) + raise StandardError, 'Supervisor enrolment missing' unless supervisor_enrolment - raise StandardError unless supervisor_enrolment + @instance.update!(source_topic: topic) + end - @instance.update!(source_topic: topic) + @project.update!(enrolment: supervisor_enrolment) end - - @project.project_instances.last.update!( - enrolment: supervisor_enrolment - ) end - rescue StandardError - redirect_to course_project_path(@course, @project), alert: 'Project update failed' + rescue StandardError => e + redirect_to course_project_path(@course, @project), alert: "Project update failed: #{e.message}" return end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a80ade00..bad27ba9 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -53,7 +53,13 @@ def create? end def update? - project_owner && !approved + return true if project_owner && !approved + + return true if project_owner && approved && any_free_edit_fields? + + return true if coordinator + + false end def change_status? @@ -98,4 +104,10 @@ def student def approved record.status.to_s == 'approved' end + + def any_free_edit_fields? + record.course.project_template + &.project_template_fields + &.exists?(free_edit: true) || false + end end From 85aba76bd00ebdb859733982d375f01476d28a18 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 22:27:54 +0800 Subject: [PATCH 09/15] Fix versioning not updating after free edit update --- app/controllers/projects_controller.rb | 49 +++++++++++++++----------- app/models/project.rb | 8 +++-- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c24c1f5f..08414b8f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -208,16 +208,10 @@ def create def update authorize @project || Project.new(course: @course) + is_approved = @project.approved? - is_approved = @project.status == 'approved' || @project.approved? - - has_supervisor_comment = false - @project.project_instances.last.comments.each do |comment| - if comment.user_id == @project.supervisor.id - has_supervisor_comment = true - break - end - end + last_instance = @project.project_instances.last + has_supervisor_comment = last_instance.comments.exists?(user_id: @project.supervisor.id) previous_supervisor_id = @project.supervisor.id new_instance_created = false @@ -232,14 +226,16 @@ def update ) new_instance_created = @instance.new_record? - if !is_approved && params[:fields].present? - title_field_id = params[:fields].keys.first - @instance.title = params[:fields][title_field_id] if title_field_id.present? - end + if new_instance_created + previous_instance = @project.project_instances.where.not(id: nil).order(version: :desc).first - @instance.last_edit_time = Time.current - @instance.last_edit_by = current_user.id - @instance.save! + previous_instance&.project_instance_fields&.each do |old_field| + @instance.project_instance_fields.build( + project_template_field_id: old_field.project_template_field_id, + value: old_field.value + ) + end + end raise StandardError, 'No field data provided.' if params[:fields].blank? @@ -248,13 +244,24 @@ def update next if is_approved && !template_field.free_edit - existing_field = ProjectInstanceField.find_or_initialize_by( - project_template_field_id: field_id, - instance: @instance - ) - existing_field.update!(value: value) + field = @instance.project_instance_fields.find { |f| f.project_template_field_id == field_id.to_i } + + if field + field.value = value + else + @instance.project_instance_fields.build( + project_template_field_id: field_id, + value: value + ) + end end + @instance.title = params[:fields].values.first if !is_approved && params[:fields].present? && @instance.title.blank? + + @instance.last_edit_time = Time.current + @instance.last_edit_by = current_user.id + @instance.save! + unless is_approved raise StandardError, 'Please choose a lecturer and topic' if params[:based_on_topic].blank? diff --git a/app/models/project.rb b/app/models/project.rb index 053abceb..d562f8d7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -70,11 +70,13 @@ def editable? end def instance_to_edit(created_by:, has_supervisor_comment:) - if rejected? || redo? || (pending? && has_supervisor_comment) + if approved? || rejected? || redo? || (pending? && has_supervisor_comment) project_instances.build( - version: project_instances.count + 1, + version: (project_instances.maximum(:version) || 0) + 1, created_by: created_by, - enrolment: enrolment + enrolment: enrolment, + title: current_title, + status: (status if approved?) ) else # If approved and pending (no supervisor comment) dont create new instance From 9932398270ad830fbd78028a7e04b2163345539d Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Sun, 5 Apr 2026 22:50:25 +0800 Subject: [PATCH 10/15] Disable field that is not read only --- app/views/projects/_project_edit.html.erb | 66 +++++++++++++---------- app/views/projects/edit.html.erb | 55 +++++++++---------- 2 files changed, 67 insertions(+), 54 deletions(-) diff --git a/app/views/projects/_project_edit.html.erb b/app/views/projects/_project_edit.html.erb index 46babb03..f6f6687e 100644 --- a/app/views/projects/_project_edit.html.erb +++ b/app/views/projects/_project_edit.html.erb @@ -2,16 +2,31 @@ id="template-fields-edit-container" class="grid grid-cols-1 gap-6 sm:gap-8" > + <% is_project_approved = @project.approved? %> + <% template_fields.each do |field| %> + <% + is_readonly = is_project_approved && !field.free_edit + + disabled_classes = is_readonly ? "opacity-60 bg-gray-100 cursor-not-allowed border-gray-200 shadow-none" : "" + current_input_classes = "#{input_classes} #{disabled_classes}" + %> +
<%= label_tag "fields_#{field.id}", class: "block text-xs sm:text-sm font-bold text-gray-700 uppercase tracking-wide mb-2", title: field.hint do %> <%= field.label %> - <% if field.required %> + <% if field.required && !is_readonly %> * <% end %> + + <% if is_readonly %> + + Locked (Approved) + + <% end %> <% end %> <% if field.hint.present? %> @@ -20,25 +35,31 @@ <% existing_value = existing_values[field.id] %> + <% if is_readonly %> + <%= hidden_field_tag "fields[#{field.id}]", existing_value %> + <% end %> + <% case field.field_type %> <% when "shorttext" %> <%= text_field_tag "fields[#{field.id}]", existing_value, - class: input_classes, + class: current_input_classes, placeholder: field.hint, - required: field.required, + required: (field.required && !is_readonly), + disabled: is_readonly, autocomplete: "off" %> <% when "textarea" %> <%= text_area_tag "fields[#{field.id}]", existing_value, - class: "#{input_classes} resize-none overflow-hidden leading-relaxed", + class: "#{current_input_classes} resize-none overflow-hidden leading-relaxed", data: { - controller: "markdown-editor", + controller: is_readonly ? "" : "markdown-editor", }, rows: 2, placeholder: field.hint, - required: field.required %> + required: (field.required && !is_readonly), + disabled: is_readonly %> <% when "dropdown" %>
@@ -46,20 +67,11 @@ <%= select_tag "fields[#{field.id}]", options_for_select(options, existing_value), prompt: "Select an option", - class: "#{input_classes} appearance-none pr-10 cursor-pointer", - required: field.required %> -
- + class: "#{current_input_classes} appearance-none pr-10", + required: (field.required && !is_readonly), + disabled: is_readonly %> +
+
@@ -72,13 +84,12 @@ option, (option.to_s == existing_value.to_s), id: "field_#{field.id}_#{idx}", - required: field.required, - class: - "w-4 h-4 sm:w-5 sm:h-5 text-blue-600 border-gray-300 focus:ring-blue-500 cursor-pointer" %> + required: (field.required && !is_readonly), + disabled: is_readonly, + class: "w-4 h-4 sm:w-5 sm:h-5 text-blue-600 border-gray-300 focus:ring-blue-500 #{is_readonly ? 'cursor-not-allowed' : 'cursor-pointer'}" %> <%= label_tag "field_#{field.id}_#{idx}", option, - class: - "ml-3 block text-sm sm:text-base font-medium text-gray-700 cursor-pointer hover:text-gray-900" %> + class: "ml-3 block text-sm sm:text-base font-medium text-gray-700 #{is_readonly ? 'cursor-not-allowed text-gray-400' : 'cursor-pointer hover:text-gray-900'}" %>
<% end %>
@@ -86,8 +97,9 @@ <% else %> <%= text_field_tag "fields[#{field.id}]", existing_value, - class: input_classes, - required: field.required, + class: current_input_classes, + required: (field.required && !is_readonly), + disabled: is_readonly, autocomplete: "off" %> <% end %> diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb index dc5a4549..cb8cc72d 100644 --- a/app/views/projects/edit.html.erb +++ b/app/views/projects/edit.html.erb @@ -68,29 +68,32 @@ <%# reusable input class definition %> <% input_classes = "w-full px-4 py-3 border border-gray-200 rounded-lg sm:rounded-xl text-gray-700 bg-gray-50 focus:outline-none focus:ring-4 focus:ring-blue-500/10 focus:border-blue-500 transition-all font-medium placeholder-gray-400 text-sm sm:text-base" %> + + <% is_project_approved = @project.approved? %> + <% is_topic_readonly = is_project_approved %>
-
From 1a10f29dd572899f503308ca9ac404364256fa58 Mon Sep 17 00:00:00 2001 From: JuneMinazuki Date: Mon, 6 Apr 2026 00:20:00 +0800 Subject: [PATCH 15/15] Use 'Editable Post-Approval' label --- app/views/project_templates/edit.html.erb | 18 +++++++++--------- app/views/projects/_project_edit.html.erb | 2 +- app/views/projects/_project_new.html.erb | 2 +- app/views/topics/edit.html.erb | 2 +- app/views/topics/new.html.erb | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/views/project_templates/edit.html.erb b/app/views/project_templates/edit.html.erb index 5637ab12..bef14266 100644 --- a/app/views/project_templates/edit.html.erb +++ b/app/views/project_templates/edit.html.erb @@ -51,28 +51,28 @@
Field Label
-
Hint Text - Field Type - ${generateFieldTypeOptions()} @@ -86,15 +97,17 @@ document.addEventListener("turbo:load", function () { Applicable To - ${generateApplicableToOptions()} + Options - + + Required - - +
+ + + +
+
+ Free Edit +
+ + + +
- Free Edit + Editable
<%# --- Table Header --- %> - + - - - - - - - diff --git a/app/views/projects/_project_edit.html.erb b/app/views/projects/_project_edit.html.erb index 1ebfec52..65185925 100644 --- a/app/views/projects/_project_edit.html.erb +++ b/app/views/projects/_project_edit.html.erb @@ -24,7 +24,7 @@ <% if !is_readonly %> - Editable after Approval + Editable Post-Approval <% end %> <% end %> diff --git a/app/views/projects/_project_new.html.erb b/app/views/projects/_project_new.html.erb index ee5b973f..ef2e2bb6 100644 --- a/app/views/projects/_project_new.html.erb +++ b/app/views/projects/_project_new.html.erb @@ -12,7 +12,7 @@ <% end %> <% if field.free_edit %> - Editable after Approval + Editable Post-Approval <% end %> <% end %> diff --git a/app/views/topics/edit.html.erb b/app/views/topics/edit.html.erb index 38d742f1..65c98131 100644 --- a/app/views/topics/edit.html.erb +++ b/app/views/topics/edit.html.erb @@ -86,7 +86,7 @@ <% end %> <% if field.free_edit %> - Editable after Approval + Editable Post-Approval <% end %> <% end %> diff --git a/app/views/topics/new.html.erb b/app/views/topics/new.html.erb index 47d0cf45..f8db7c17 100644 --- a/app/views/topics/new.html.erb +++ b/app/views/topics/new.html.erb @@ -87,7 +87,7 @@ <% end %> <% if field.free_edit %> - Editable after Approval + Editable Post-Approval <% end %> <% end %>