diff --git a/app/controllers/organizations/tasks_controller.rb b/app/controllers/organizations/tasks_controller.rb new file mode 100644 index 000000000..03be9c5fd --- /dev/null +++ b/app/controllers/organizations/tasks_controller.rb @@ -0,0 +1,72 @@ +class Organizations::TasksController < Organizations::BaseController + before_action :set_pet, only: [:new, :create, :edit, :update, :destroy] + before_action :set_task, only: [:edit, :update] + + def new + @task = @pet.tasks.build + render partial: "form", locals: {task: @task} + end + + def create + @task = @pet.tasks.build(task_params) + + if @task.save + respond_to do |format| + format.html { redirect_to pet_path(@pet, active_tab: "tasks") } + format.turbo_stream + end + else + render :new + end + end + + def edit + end + + def update + if @task.update(task_params) + respond_to do |format| + format.html { redirect_to @task, notice: "Task was successfully updated." } + format.turbo_stream { render turbo_stream: turbo_stream.replace("tasks_list", partial: "organizations/tasks/tasks", locals: {task: @task}) } + end + else + respond_to do |format| + format.html { render :edit } + end + end + end + + def destroy + @task = Task.find(params[:id]) + @task.destroy + + respond_to do |format| + format.html { redirect_to pet_path(@pet), notice: "Task was successfully deleted." } + format.turbo_stream + end + rescue ActiveRecord::RecordNotFound + redirect_to pets_path + end + + private + + def set_pet + @organization = current_user.organization + raise ActiveRecord::RecordNotFound if @organization.nil? + + pet_id = params[:pet_id] || params[:id] + @pet = @organization.pets.find(pet_id) + rescue ActiveRecord::RecordNotFound + redirect_to pets_path unless @pet + end + + def set_task + @task = @pet.tasks.find(params[:id]) + rescue ActiveRecord::RecordNotFound + redirect_to pets_path + end + + def task_params + params.require(:task).permit(:name, :description, :completed) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 88870c1fc..ebe95da52 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -7,6 +7,10 @@ def organization_home_path(organization) home_index_path(script_name: "/#{organization.slug}") end + def path_to_partial(active_tab) + "pets/#{active_tab}" + end + def current_organization_name if Current.organization.present? Current.organization.name diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb new file mode 100644 index 000000000..ce894d00c --- /dev/null +++ b/app/helpers/tasks_helper.rb @@ -0,0 +1,2 @@ +module TasksHelper +end diff --git a/app/models/pet.rb b/app/models/pet.rb index 9d351727e..afc66e710 100644 --- a/app/models/pet.rb +++ b/app/models/pet.rb @@ -31,6 +31,7 @@ class Pet < ApplicationRecord acts_as_tenant(:organization) has_many :adopter_applications, dependent: :destroy + has_many :tasks, dependent: :destroy has_one :match, dependent: :destroy has_many_attached :images enum species: ["Dog", "Cat"] diff --git a/app/models/task.rb b/app/models/task.rb new file mode 100644 index 000000000..772847569 --- /dev/null +++ b/app/models/task.rb @@ -0,0 +1,23 @@ +# == Schema Information +# +# Table name: tasks +# +# id :bigint not null, primary key +# completed :boolean default(FALSE) +# description :text +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# pet_id :bigint not null +# +# Indexes +# +# index_tasks_on_pet_id (pet_id) +# +# Foreign Keys +# +# fk_rails_... (pet_id => pets.id) +# +class Task < ApplicationRecord + belongs_to :pet +end diff --git a/app/views/organizations/pets/show.html.erb b/app/views/organizations/pets/show.html.erb index 8b3a3b46b..22e246956 100644 --- a/app/views/organizations/pets/show.html.erb +++ b/app/views/organizations/pets/show.html.erb @@ -1,112 +1,113 @@ <% breadcrumb :dashboard_pet, @pet %> -<%= render "components/dashboard/page" do |p| %> - <% p.header_title @pet.name %> - <% p.nav_tabs do %> - <%= render "components/dashboard/nav_tab", url: pet_path(@pet, active_tab: "overview"), text: "Summary", options: { active: /overview/ } %> - <%= render "components/dashboard/nav_tab", url: pet_path(@pet, active_tab: "applications"), text: "Tasks", options: { active: /applications/ } %> - <% end %> - <% p.content do %> -
- -
- <%= render partial: "organizations/tasks/#{@active_tab}", locals: { pet: @pet } %> -
- -
- -
-
- <% if @pet.images.attached? %> - <%= image_tag @pet.images.first, class: 'rounded card-img-top' %> - <% else %> - <%= image_tag('coming_soon.jpg', class: 'rounded card-img-top') %> - <% end %> -
+
+ +
+ + +
+
+
+ <%= render partial: "organizations/tasks/#{@active_tab}", locals: { pet: @pet } %> +
+ +
+ +
+ <% if @pet.images.attached? %> + <%= image_tag @pet.images.first, class: 'rounded card-img-top' %> + <% else %> + <%= image_tag('coming_soon.jpg', class: 'rounded card-img-top') %> + <% end %> +
+
+
+ +
+
+

Recent Activity

- -
- -
-
-

Recent Activity -

-
- -
- -
- -
    -
  • -
    -
    -
    - + +
    + +
    + +
      +
    • +
      +
      +
      + +
      +
      +
      +

      Task Finished

      +

      Paula finished figma task

      +
    • +
      +
      +
      + +
      +
      +
      +

      New Comment

      +

      Georg commented on task.

      +
      +
      + 1 hour ago
      -
      -

      Task Finished

      -

      Paula finished figma task

      -
    • -
      -
      -
      - -
      -
      -
      -

      New Comment

      -

      Georg commented on task.

      - -
      -
      - 1 hour ago - -
      +
    • +
    • +
      +
      +
      +
      -
    • -
    • -
      -
      -
      - -
      -
      -
      -

      Task Overdue

      -

      Task status updatd for board - is overdue.

      - -
      -
      - 1 day - -
      -
      -
    • -
    • -
      -
      -
      - -
      -
      -
      -

      Update Send to Client

      -

      Jitu send email to update design - for client Geeks UI.

      - -
      -
      - 1 day -
      +
      +
      +

      Task Overdue

      +

      Task status updatd for board is overdue.

      +
      +
      + 1 day +
      +
    +
  • +
  • +
    +
    +
    +
    -
  • -
+
+
+

Update Send to Client

+

Jitu send email to update design for client Geeks UI.

+
+
+ 1 day +
-
-
+ +
+ +
- <% end %> - <% end %> +
+
+
diff --git a/app/views/organizations/tasks/_applications.html.erb b/app/views/organizations/tasks/_applications.html.erb index 6706735b9..a8477e610 100644 --- a/app/views/organizations/tasks/_applications.html.erb +++ b/app/views/organizations/tasks/_applications.html.erb @@ -1,3 +1,33 @@ -
- <%= render "partials/pet_applications", applications: pet.adopter_applications, pet: nil %> -
+ +
+
+
+
+
+
+

Summary

+
+ +
+ + + + Settings + <%= link_to t('general.edit'), edit_pet_path(@pet), class: 'dropdown-item' %> + <%= link_to t('general.delete'), pet_path(@pet), class: 'dropdown-item', + data: + { + turbo_method: :delete, + turbo_confirm: t('organization_pets.show.are_you_sure_delete') + } %> + + +
+
+
+
+
+
+
diff --git a/app/views/organizations/tasks/_files.html.erb b/app/views/organizations/tasks/_files.html.erb new file mode 100644 index 000000000..a8477e610 --- /dev/null +++ b/app/views/organizations/tasks/_files.html.erb @@ -0,0 +1,33 @@ + +
+
+
+
+
+
+

Summary

+
+ +
+ + + + Settings + <%= link_to t('general.edit'), edit_pet_path(@pet), class: 'dropdown-item' %> + <%= link_to t('general.delete'), pet_path(@pet), class: 'dropdown-item', + data: + { + turbo_method: :delete, + turbo_confirm: t('organization_pets.show.are_you_sure_delete') + } %> + + +
+
+
+
+
+
+
diff --git a/app/views/organizations/tasks/_form.html.erb b/app/views/organizations/tasks/_form.html.erb new file mode 100644 index 000000000..0e739a214 --- /dev/null +++ b/app/views/organizations/tasks/_form.html.erb @@ -0,0 +1,9 @@ +<%= form_for [@pet, @task], remote: true do |f| %> +
+ <%= f.label :name, "Task Name" %> + <%= f.text_field :name, class: 'form-control' %> +
+
+ <%= f.submit "Update Task", class: 'btn btn-primary' %> +
+<% end %> diff --git a/app/views/organizations/tasks/_new_task_modal.html.erb b/app/views/organizations/tasks/_new_task_modal.html.erb new file mode 100644 index 000000000..e7789cb0f --- /dev/null +++ b/app/views/organizations/tasks/_new_task_modal.html.erb @@ -0,0 +1,35 @@ + diff --git a/app/views/organizations/tasks/_overview.html.erb b/app/views/organizations/tasks/_overview.html.erb index 524b7800f..b48b819c3 100644 --- a/app/views/organizations/tasks/_overview.html.erb +++ b/app/views/organizations/tasks/_overview.html.erb @@ -1,112 +1,107 @@ -
-
-
-
-
-
-

Summary

+ +
+
+
+
+
+
+

Summary

+
+ +
+ + + + Settings + <%= link_to t('general.edit'), edit_pet_path(@pet), class: 'dropdown-item' %> + <%= link_to t('general.delete'), pet_path(@pet), class: 'dropdown-item', + data: + { + turbo_method: :delete, + turbo_confirm: t('organization_pets.show.are_you_sure_delete') + } %> + + +
- <%= link_to t('general.edit'), edit_pet_path(@pet), class: 'btn btn-outline-dark' %>
-
- -
-

<%= @pet.description %>

- -
    -
  • -
    -
    - -
    -
    Sex
    -
    -
    -
    -
    -

    <%= @pet.sex %>

    -
    -
    -
    -
  • -
  • -
    -
    - -
    -
    Breed
    +
    +

    <%= @pet.description %>

    +
      +
    • +
      +
      + +
      +
      Sex
      +
      -
      -
      -

      <%= @pet.breed %>

      +
      +

      <%= @pet.sex %>

      +
      -
    -
  • -
  • -
    -
    - -
    -
    Weight
    +
  • +
  • +
    +
    + +
    +
    Breed
    +
    -
    -
    -

    <%= "#{@pet.weight_from} - #{@pet.weight_to} #{@pet.weight_unit}" %>

    +
    +

    <%= @pet.breed %>

    +
    -
- -
  • -
    -
    - -
    -
    Placement Type
    +
  • +
  • +
    +
    + +
    +
    Weight
    +
    -
    -
    -

    <%= @pet.placement_type %>

    +
    +

    <%= "#{@pet.weight_from} - #{@pet.weight_to} #{@pet.weight_unit}" %>

    +
    -
  • - -
  • -
    +
  • +
  • - -
    -
    Application Status
    + justify-content-between + align-items-center"> +
    + +
    +
    Application Status
    +
    -
    -
    -

    - <%= @pet.application_paused == false ? t('.application.active') : t('.application.paused') %> -

    +
    +

    + <%= @pet.application_paused == false ? t('.application.active') : t('.application.paused') %> +

    +
    -
    -
  • - - + + +
    - <%= - button_to t('general.delete'), - pet_path(@pet), method: :delete, - class: 'btn btn-outline-danger mt-2', - data: { turbo_confirm: t('.are_you_sure_delete') } - %>
    -
    \ No newline at end of file + diff --git a/app/views/organizations/tasks/_tasks.html.erb b/app/views/organizations/tasks/_tasks.html.erb new file mode 100644 index 000000000..7fc96aac9 --- /dev/null +++ b/app/views/organizations/tasks/_tasks.html.erb @@ -0,0 +1,87 @@ + +
    +
    +
    +
    +
    +
    +

    Summary

    +
    + +
    + + + + Settings + <%= link_to t('general.edit'), edit_pet_path(@pet), class: 'dropdown-item' %> + <%= link_to t('general.delete'), pet_path(@pet), class: 'dropdown-item', + data: + { + turbo_method: :delete, + turbo_confirm: t('organization_pets.show.are_you_sure_delete') + } %> + + +
    +
    +
    +
      + <% @pet.tasks.each do |task| %> + <%= turbo_frame_tag dom_id(task) do %> +
    • +
      +
      + <%= task.name %> +

      <%= task.description %>

      +
      + + +
      +
      +
      + <%= task.completed %> +
      +
      + +
      +
      + <%= link_to 'Delete', pet_task_path(@pet, task), class: 'btn btn-danger', data: { turbo_method: "delete", turbo_confirm: 'Are you sure?' } %> +
      +
      +
      + + +
    • + <% end %> + <% end %> +
    + <%= render 'organizations/tasks/new_task_modal', pet: @pet %> + + +
    +
    +
    +
    diff --git a/app/views/organizations/tasks/create.turbo_stream.erb b/app/views/organizations/tasks/create.turbo_stream.erb new file mode 100644 index 000000000..e922ab1c2 --- /dev/null +++ b/app/views/organizations/tasks/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.update 'tasks_list', partial: 'organizations/tasks/tasks', locals: { task: @task } %> diff --git a/app/views/organizations/tasks/destroy.turbo_stream.erb b/app/views/organizations/tasks/destroy.turbo_stream.erb new file mode 100644 index 000000000..6d0b5f4ce --- /dev/null +++ b/app/views/organizations/tasks/destroy.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.remove dom_id(@task) %> diff --git a/app/views/organizations/tasks/edit.html.erb b/app/views/organizations/tasks/edit.html.erb new file mode 100644 index 000000000..1a267207f --- /dev/null +++ b/app/views/organizations/tasks/edit.html.erb @@ -0,0 +1,26 @@ + diff --git a/config/routes.rb b/config/routes.rb index d75454326..b40cc109c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,7 +16,9 @@ resource :organization_profile, only: %i[edit update] resources :home, only: [:index] - resources :pets + resources :pets do + resources :tasks, only: [:new, :create, :edit, :update, :destroy] + end resources :dashboard resources :adoption_application_reviews, only: [:index, :edit, :update] resources :foster_application_reviews, only: [:index] diff --git a/db/migrate/20231017180938_create_tasks.rb b/db/migrate/20231017180938_create_tasks.rb new file mode 100644 index 000000000..f5f84c150 --- /dev/null +++ b/db/migrate/20231017180938_create_tasks.rb @@ -0,0 +1,12 @@ +class CreateTasks < ActiveRecord::Migration[7.0] + def change + create_table :tasks do |t| + t.string :name + t.text :description + t.boolean :completed, default: false + t.references :pet, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20231129153055_add_not_null_to_tasks.rb b/db/migrate/20231129153055_add_not_null_to_tasks.rb new file mode 100644 index 000000000..feccb935e --- /dev/null +++ b/db/migrate/20231129153055_add_not_null_to_tasks.rb @@ -0,0 +1,7 @@ +class AddNotNullToTasks < ActiveRecord::Migration[7.0] + def change + safety_assured do + change_column_null :tasks, :name, false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index bc9554e63..77f935ba9 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[7.0].define(version: 2023_11_10_194816) do +ActiveRecord::Schema[7.0].define(version: 2023_11_29_153055) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -225,6 +225,16 @@ t.index ["staff_account_id"], name: "index_staff_accounts_roles_on_staff_account_id" end + create_table "tasks", force: :cascade do |t| + t.string "name", null: false + t.text "description" + t.boolean "completed", default: false + t.bigint "pet_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["pet_id"], name: "index_tasks_on_pet_id" + end + create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -270,4 +280,5 @@ add_foreign_key "pets", "organizations" add_foreign_key "staff_accounts", "organizations" add_foreign_key "staff_accounts", "users" + add_foreign_key "tasks", "pets" end diff --git a/test/controllers/organizations/tasks_controller_test.rb b/test/controllers/organizations/tasks_controller_test.rb new file mode 100644 index 000000000..c9581c51d --- /dev/null +++ b/test/controllers/organizations/tasks_controller_test.rb @@ -0,0 +1,54 @@ +require "test_helper" + +class TasksControllerTest < ActionDispatch::IntegrationTest + include Devise::Test::IntegrationHelpers + + setup do + @user = create(:user, :verified_staff, :staff_admin) + set_organization(@user.organization) + @organization = ActsAsTenant.test_tenant + @pet = create(:pet) + @task = create(:task, pet: @pet) + sign_in @user + end + + test "should get new" do + get new_pet_task_url(@pet) + assert_response :success + end + + test "new action should handle missing pet" do + get new_pet_task_url(-1) + assert_response :redirect + assert_redirected_to pets_path + end + + test "create action should handle invalid task parameters" do + task_name = "Super task" + post pet_tasks_url(@pet), params: {task: {name: task_name, description: "Some description", completed: false}} + assert_response :redirect + assert_redirected_to pet_path(@pet, active_tab: "tasks") + end + + test "edit action should handle non-existent task" do + get edit_pet_task_url(@pet, -1) + assert_response :redirect + assert_redirected_to pets_path + end + + test "should handle non-existent task during update" do + non_existent_task_id = -1 + patch pet_task_path(@pet, non_existent_task_id), params: {task: {name: "Updated Name"}} + + assert_response :redirect + assert_redirected_to pets_path + end + + test "destroy action should handle non-existent task" do + assert_no_difference "Task.count" do + delete pet_task_url(@pet, -1) + end + assert_response :redirect + assert_redirected_to pets_path + end +end diff --git a/test/factories.rb b/test/factories.rb index 300fd2fed..8289c317f 100644 --- a/test/factories.rb +++ b/test/factories.rb @@ -1,4 +1,11 @@ FactoryBot.define do + factory :task do + name { "MyString" } + description { "MyText" } + completed { false } + pet + end + factory :adopter_account do transient do organization { ActsAsTenant.test_tenant } diff --git a/test/models/task_test.rb b/test/models/task_test.rb new file mode 100644 index 000000000..7a729ec31 --- /dev/null +++ b/test/models/task_test.rb @@ -0,0 +1,36 @@ +require "test_helper" + +class TaskTest < ActiveSupport::TestCase + test "should have valid factory" do + assert build(:task).valid? + end + + test "should belong to a pet" do + task = build(:task) + assert_difference("task.pet.tasks.count", 1) do + task.save + end + end + + test "completed defaults to false" do + task = build(:task) + refute task.completed + end + + test "can set completed flag" do + task = build(:task) + refute task.completed + + task.completed = true + assert task.completed + end + + test "can update description" do + task = create(:task, description: "Old description") + + assert_equal "Old description", task.description + + task.update(description: "New description") + assert_equal "New description", task.description + end +end