inner_plan 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/inner_plan_manifest.js +6 -0
- data/app/assets/stylesheets/inner_plan/application.css +24 -0
- data/app/components/application_view.rb +4 -0
- data/app/components/inner_plan/breadcrumbs_component.rb +26 -0
- data/app/components/inner_plan/groups/edit_view.rb +72 -0
- data/app/components/inner_plan/groups/new_view.rb +66 -0
- data/app/components/inner_plan/groups/row/handle_component.rb +30 -0
- data/app/components/inner_plan/groups/row_component.rb +31 -0
- data/app/components/inner_plan/groups/rows_component.rb +17 -0
- data/app/components/inner_plan/groups/show_menu_component.rb +30 -0
- data/app/components/inner_plan/groups/show_view.rb +77 -0
- data/app/components/inner_plan/lists/completed_tasks_component.rb +30 -0
- data/app/components/inner_plan/lists/edit_view.rb +62 -0
- data/app/components/inner_plan/lists/index_view.rb +57 -0
- data/app/components/inner_plan/lists/new_view.rb +49 -0
- data/app/components/inner_plan/lists/ongoing_tasks_component.rb +17 -0
- data/app/components/inner_plan/lists/row/handle_component.rb +37 -0
- data/app/components/inner_plan/lists/row_component.rb +55 -0
- data/app/components/inner_plan/lists/show_menu_component.rb +37 -0
- data/app/components/inner_plan/lists/show_view.rb +76 -0
- data/app/components/inner_plan/progress_bar_separator_component.rb +20 -0
- data/app/components/inner_plan/tasks/completed_toggler_component.rb +27 -0
- data/app/components/inner_plan/tasks/edit_view.rb +82 -0
- data/app/components/inner_plan/tasks/form/item_component.rb +21 -0
- data/app/components/inner_plan/tasks/form/row_component.rb +19 -0
- data/app/components/inner_plan/tasks/inline_form_view.rb +62 -0
- data/app/components/inner_plan/tasks/row/addons_component.rb +22 -0
- data/app/components/inner_plan/tasks/row/base_addon_component.rb +19 -0
- data/app/components/inner_plan/tasks/row/description_addon_component.rb +13 -0
- data/app/components/inner_plan/tasks/row/due_on_addon_component.rb +14 -0
- data/app/components/inner_plan/tasks/row/group_addon_component.rb +11 -0
- data/app/components/inner_plan/tasks/row/handle_component.rb +40 -0
- data/app/components/inner_plan/tasks/row_component.rb +35 -0
- data/app/components/inner_plan/tasks/rows_component.rb +22 -0
- data/app/components/inner_plan/tasks/show/item_component.rb +20 -0
- data/app/components/inner_plan/tasks/show/row_component.rb +19 -0
- data/app/components/inner_plan/tasks/show_menu_component.rb +30 -0
- data/app/components/inner_plan/tasks/show_view.rb +76 -0
- data/app/components/inner_plan/user_with_avatar_component.rb +17 -0
- data/app/concepts/inner_plan/list/operation/index.rb +11 -0
- data/app/concepts/inner_plan/task/operation/complete.rb +12 -0
- data/app/concepts/inner_plan/task/operation/create.rb +46 -0
- data/app/concepts/inner_plan/task/operation/reopen.rb +12 -0
- data/app/concepts/inner_plan/task/operation/update.rb +25 -0
- data/app/concepts/inner_plan/task/operation/update_position.rb +25 -0
- data/app/controllers/inner_plan/application_controller.rb +12 -0
- data/app/controllers/inner_plan/groups_controller.rb +59 -0
- data/app/controllers/inner_plan/lists_controller.rb +59 -0
- data/app/controllers/inner_plan/tasks_controller.rb +73 -0
- data/app/helpers/inner_plan/application_helper.rb +4 -0
- data/app/javascript/inner_plan/application.js +15 -0
- data/app/javascript/inner_plan/controllers/groups_controller.js +47 -0
- data/app/javascript/inner_plan/controllers/lists_controller.js +30 -0
- data/app/javascript/inner_plan/controllers/task_inline_form_controller.js +40 -0
- data/app/javascript/inner_plan/controllers/tasks_controller.js +47 -0
- data/app/jobs/inner_plan/application_job.rb +4 -0
- data/app/mailers/inner_plan/application_mailer.rb +6 -0
- data/app/models/inner_plan/application_record.rb +5 -0
- data/app/models/inner_plan/list.rb +29 -0
- data/app/models/inner_plan/task.rb +36 -0
- data/app/services/inner_plan/tasks/description_renderer.rb +18 -0
- data/app/services/inner_plan/tasks/title_renderer.rb +18 -0
- data/app/views/inner_plan/shared/_blankslate.svg.erb +1 -0
- data/app/views/inner_plan/tasks/complete.turbo_stream.erb +16 -0
- data/app/views/inner_plan/tasks/create.turbo_stream.erb +11 -0
- data/app/views/inner_plan/tasks/create_failure.turbo_stream.erb +5 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/inner_plan/application.html.erb +38 -0
- data/config/importmap.rb +8 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20241205203943_create_inner_plan_tables.rb +37 -0
- data/db/seeds.rb +14 -0
- data/lib/inner_plan/configuration.rb +116 -0
- data/lib/inner_plan/engine.rb +22 -0
- data/lib/inner_plan/smart_array.rb +66 -0
- data/lib/inner_plan/version.rb +3 -0
- data/lib/inner_plan.rb +28 -0
- data/lib/tasks/inner_plan_tasks.rake +4 -0
- metadata +265 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module InnerPlan
|
2
|
+
class List < ApplicationRecord
|
3
|
+
has_many :tasks, class_name: 'InnerPlan::Task'
|
4
|
+
has_many :tasks_including_groups, ->(list){ rewhere(list_id: ([list.id] + list.lists.pluck(:id))) }, class_name: 'InnerPlan::Task'
|
5
|
+
has_many :lists, foreign_key: :parent_id
|
6
|
+
belongs_to :list, foreign_key: :parent_id, optional: true
|
7
|
+
belongs_to :user, class_name: InnerPlan.configuration.user_class_name
|
8
|
+
|
9
|
+
positioned
|
10
|
+
|
11
|
+
scope :ordered_by_position, ->{ order(position: :asc) }
|
12
|
+
scope :root, ->{ where(parent_id: nil) }
|
13
|
+
scope :sub, ->{ where.not(parent_id: nil) }
|
14
|
+
|
15
|
+
validates :title, presence: true
|
16
|
+
|
17
|
+
def root?
|
18
|
+
parent_id.blank?
|
19
|
+
end
|
20
|
+
|
21
|
+
def sub?
|
22
|
+
parent_id.present?
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_param
|
26
|
+
[id.to_s, title.to_url(limit: 50, truncate_words: false)].join('-')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module InnerPlan
|
2
|
+
class Task < ApplicationRecord
|
3
|
+
belongs_to :list
|
4
|
+
belongs_to :user, class_name: InnerPlan.configuration.user_class_name,
|
5
|
+
optional: true
|
6
|
+
|
7
|
+
positioned on: :list
|
8
|
+
|
9
|
+
scope :ongoing, ->{ where(completed_at: nil) }
|
10
|
+
scope :completed, ->{ where.not(completed_at: nil).order(completed_at: :desc) }
|
11
|
+
scope :ordered_by_position, ->{ order(position: :asc) }
|
12
|
+
scope :recently_completed_first, -> { order(completed_at: :desc) }
|
13
|
+
|
14
|
+
validates :title, presence: true
|
15
|
+
|
16
|
+
def ongoing?
|
17
|
+
!completed?
|
18
|
+
end
|
19
|
+
|
20
|
+
def completed?
|
21
|
+
completed_at.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def complete!
|
25
|
+
touch(:completed_at)
|
26
|
+
end
|
27
|
+
|
28
|
+
def reopen!
|
29
|
+
update(completed_at: nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_param
|
33
|
+
[id.to_s, title.to_url(limit: 50, truncate_words: false)].join('-')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module InnerPlan::Tasks
|
2
|
+
class DescriptionRenderer < Trailblazer::Operation
|
3
|
+
include ActionView::Helpers::TextHelper
|
4
|
+
|
5
|
+
step :extract_description
|
6
|
+
step :apply_simple_format
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def extract_description(ctx, task:, **)
|
11
|
+
ctx[:description] = task.description
|
12
|
+
end
|
13
|
+
|
14
|
+
def apply_simple_format(ctx, description:, **)
|
15
|
+
ctx[:description] = simple_format(description)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module InnerPlan::Tasks
|
2
|
+
class TitleRenderer < Trailblazer::Operation
|
3
|
+
include ActionView::Helpers::SanitizeHelper
|
4
|
+
|
5
|
+
step :extract_title
|
6
|
+
step :sanitize_title
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def extract_title(ctx, task:, **)
|
11
|
+
ctx[:title] = task.title
|
12
|
+
end
|
13
|
+
|
14
|
+
def sanitize_title(ctx, title:, **)
|
15
|
+
ctx[:title] = sanitize(title)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="opacity-75" data-name="Layer 1" width="300" viewBox="0 0 647.63626 632.17383" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z" transform="translate(-276.18187 -133.91309)" fill="#f2f2f2"/><path d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="20" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="12.66462" fill="#fff"/><path d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z" transform="translate(-276.18187 -133.91309)" fill="#e6e6e6"/><path d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="20" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="12.18187" fill="#fff"/></svg>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% if params[:context] == 'lists_index' %>
|
2
|
+
<turbo-stream action="update" target="<%= dom_id(@task.list, :ongoing_tasks) %>">
|
3
|
+
<template>
|
4
|
+
<%= render(InnerPlan::Lists::OngoingTasksComponent.new(@task.list, context: params[:context])) %>
|
5
|
+
</template>
|
6
|
+
</turbo-stream>
|
7
|
+
|
8
|
+
<% list = @task.list.root? ? @task.list : @task.list.list %>
|
9
|
+
<turbo-stream action="replace" target="<%= dom_id(list, :completed_tasks) %>">
|
10
|
+
<template>
|
11
|
+
<%= render(InnerPlan::Lists::CompletedTasksComponent.new(list, context: params[:context])) %>
|
12
|
+
</template>
|
13
|
+
</turbo-stream>
|
14
|
+
<% else %>
|
15
|
+
<turbo-stream action="refresh"></turbo-stream>
|
16
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<turbo-stream action="append" target="<%= dom_id(@list, :ongoing_tasks) %>">
|
2
|
+
<template>
|
3
|
+
<%= render(InnerPlan::Tasks::RowComponent.new(@task)) %>
|
4
|
+
</template>
|
5
|
+
</turbo-stream>
|
6
|
+
|
7
|
+
<turbo-stream action="update" target="<%= dom_id(@list, :add_task) %>">
|
8
|
+
<template>
|
9
|
+
<%= render InnerPlan::Tasks::InlineFormView.new(list: @list, form_visible: true) %>
|
10
|
+
</template>
|
11
|
+
</turbo-stream>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Inner plan</title>
|
5
|
+
|
6
|
+
<meta name="turbo-refresh-scroll" content="preserve">
|
7
|
+
|
8
|
+
<%= csrf_meta_tags %>
|
9
|
+
<%= csp_meta_tag %>
|
10
|
+
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.12/dist/turbo.es2017-umd.min.js"></script>
|
12
|
+
|
13
|
+
<%= stylesheet_link_tag "inner_plan/application", media: "all" %>
|
14
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
15
|
+
<link href="https://cdn.jsdelivr.net/npm/trix@2.1.8/dist/trix.min.css" rel="stylesheet">
|
16
|
+
|
17
|
+
<%= javascript_importmap_tags "application", importmap: InnerPlan.importmap %>
|
18
|
+
|
19
|
+
<% InnerPlan.configuration.additional_importmap_modules.each do |importmap_module| %>
|
20
|
+
<script type="module">import "<%= importmap_module %>"</script>
|
21
|
+
<% end %>
|
22
|
+
</head>
|
23
|
+
<body>
|
24
|
+
|
25
|
+
<div class="container">
|
26
|
+
<div class="row justify-content-center my-5">
|
27
|
+
<div class="col-lg-8 col-md-10 col-12">
|
28
|
+
<div class="card" style="--bs-card-border-radius: 1rem;">
|
29
|
+
<div class="card-body p-5">
|
30
|
+
<%= yield %>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
|
37
|
+
</body>
|
38
|
+
</html>
|
data/config/importmap.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
pin "application", to: "inner_plan/application.js", preload: true
|
2
|
+
pin "@hotwired/stimulus", to: "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
|
3
|
+
pin "sortablejs", to: "https://cdn.jsdelivr.net/npm/sortablejs@1.15.4/+esm"
|
4
|
+
pin "@rails/request.js", to: "https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.11/dist/requestjs.min.js"
|
5
|
+
pin "@popperjs/core", to: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
|
6
|
+
pin "bootstrap", to: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.esm.min.js"
|
7
|
+
|
8
|
+
pin_all_from InnerPlan::Engine.root.join("app/javascript/inner_plan/controllers"), under: "controllers", to: "inner_plan/controllers"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
InnerPlan::Engine.routes.draw do
|
2
|
+
resources :lists, except: [:destroy] do
|
3
|
+
patch :update_position, on: :member
|
4
|
+
|
5
|
+
resources :tasks, shallow: true, except: [:destroy] do
|
6
|
+
patch :update_position, on: :member
|
7
|
+
post :complete, on: :member
|
8
|
+
post :reopen, on: :member
|
9
|
+
end
|
10
|
+
|
11
|
+
resources :groups, shallow: true, except: [:destroy] do
|
12
|
+
patch :update_position, on: :member
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
root to: 'lists#index'
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class CreateInnerPlanTables < ActiveRecord::Migration[7.1]
|
2
|
+
def change
|
3
|
+
create_table "inner_plan_lists", force: :cascade do |t|
|
4
|
+
t.string "title"
|
5
|
+
t.integer "position", default: 0
|
6
|
+
t.datetime "created_at", null: false
|
7
|
+
t.datetime "updated_at", null: false
|
8
|
+
t.integer "completed_tasks_count", default: 0
|
9
|
+
t.integer "tasks_count", default: 0
|
10
|
+
t.integer "ongoing_tasks_count", default: 0
|
11
|
+
t.integer "user_id"
|
12
|
+
t.integer "parent_id"
|
13
|
+
t.text "description"
|
14
|
+
t.index ["parent_id"], name: "index_inner_plan_lists_on_parent_id"
|
15
|
+
t.index ["position"], name: "index_inner_plan_lists_on_position", unique: true
|
16
|
+
t.index ["user_id"], name: "index_inner_plan_lists_on_user_id"
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table "inner_plan_tasks", force: :cascade do |t|
|
20
|
+
t.string "title"
|
21
|
+
t.text "description"
|
22
|
+
t.datetime "completed_at"
|
23
|
+
t.datetime "created_at", null: false
|
24
|
+
t.datetime "updated_at", null: false
|
25
|
+
t.integer "position", default: 0
|
26
|
+
t.date "due_on"
|
27
|
+
t.integer "user_id"
|
28
|
+
t.integer "list_id"
|
29
|
+
t.index ["list_id", "position"], name: "index_inner_plan_tasks_on_list_id_and_position", unique: true
|
30
|
+
t.index ["list_id"], name: "index_inner_plan_tasks_on_list_id"
|
31
|
+
t.index ["user_id"], name: "index_inner_plan_tasks_on_user_id"
|
32
|
+
end
|
33
|
+
|
34
|
+
add_foreign_key "inner_plan_lists", "inner_plan_lists", column: "parent_id"
|
35
|
+
add_foreign_key "inner_plan_tasks", "inner_plan_lists", column: "list_id"
|
36
|
+
end
|
37
|
+
end
|
data/db/seeds.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
user = User.create! email: 'email@example.com', password: 'ChangeMe666'
|
2
|
+
InnerPlan::Profile.create! user: user
|
3
|
+
|
4
|
+
list = InnerPlan::List.create! title: 'Work and such', user: user
|
5
|
+
sub = InnerPlan::sub.create! list: list, title: 'Default', default: true, user: user
|
6
|
+
|
7
|
+
InnerPlan::Task.create! sub: sub, title: 'Duplikowanie produktów nie kopiuje cen', user: user
|
8
|
+
InnerPlan::Task.create! sub: sub, title: 'na android rozwala fotki - maja zły wymiar', user: user
|
9
|
+
InnerPlan::Task.create! sub: sub, title: 'wymuszenie w formularzu adresu wpisania numeru ulicy osobno', user: user
|
10
|
+
InnerPlan::Task.create! sub: sub, title: 'Masowe promocje nie odejmują tylko dodają do cen', user: user
|
11
|
+
InnerPlan::Task.create! sub: sub, title: 'Duplikowanie produktów nie kopiuje cen', completed_at: Time.current, user: user
|
12
|
+
InnerPlan::Task.create! sub: sub, title: 'Kiedy stan na minusie a desired na zero, nie dodawać produktu do raportu dla szwalni', completed_at: Time.current, user: user
|
13
|
+
InnerPlan::Task.create! sub: sub, title: 'Dodatki do koszyka', completed_at: Time.current
|
14
|
+
InnerPlan::Task.create! sub: sub, title: 'zliczanie na home tylko zamówień opłaconych i odejmowanie anulowanych', completed_at: Time.current, user: user
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module InnerPlan
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :user_class_name,
|
6
|
+
:current_user_method,
|
7
|
+
:task_edit_view_rows,
|
8
|
+
:task_show_view_rows,
|
9
|
+
:task_row_addons,
|
10
|
+
:additional_importmap_modules
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@user_class_name = 'User'
|
14
|
+
@current_user_method = :current_user
|
15
|
+
@additional_importmap_modules = []
|
16
|
+
|
17
|
+
# Tasks::EditView rows
|
18
|
+
due_on_item = proc { |context:, form:|
|
19
|
+
InnerPlan::Tasks::Form::ItemComponent.new(icon: Phlex::Icons::Tabler::CalendarDue, title: 'Due on') do
|
20
|
+
context.plain form.date_field :due_on,
|
21
|
+
class: "form-control",
|
22
|
+
autofocus: (context.focus == :due_on)
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
description_item = proc { |context:, form:|
|
27
|
+
InnerPlan::Tasks::Form::ItemComponent.new(icon: Phlex::Icons::Tabler::FileText, title: 'Description') do
|
28
|
+
context.plain form.text_area(:description, class: 'form-control', autofocus: (context.focus == :description))
|
29
|
+
end
|
30
|
+
}
|
31
|
+
|
32
|
+
@task_edit_view_rows = InnerPlan::SmartArray.new([
|
33
|
+
InnerPlan::SmartArray::Item.new(:primary_row, {}, InnerPlan::SmartArray.new([
|
34
|
+
InnerPlan::SmartArray::Item.new(:due_on, { span: 6 }, due_on_item),
|
35
|
+
])),
|
36
|
+
|
37
|
+
InnerPlan::SmartArray::Item.new(:secondary_row, {}, InnerPlan::SmartArray.new([
|
38
|
+
InnerPlan::SmartArray::Item.new(:description, { span: 12 }, description_item)
|
39
|
+
]))
|
40
|
+
])
|
41
|
+
|
42
|
+
# Tasks::ShowView rows
|
43
|
+
due_on_item = proc { |context:|
|
44
|
+
InnerPlan::Tasks::Show::ItemComponent.new(icon: Phlex::Icons::Tabler::CalendarDue, title: 'Due on') do
|
45
|
+
context.a(
|
46
|
+
href: (context.helpers.edit_task_path(context.task, focus: :due_on)),
|
47
|
+
class:
|
48
|
+
(
|
49
|
+
context.class_names(
|
50
|
+
"text-decoration-none",
|
51
|
+
"text-body-tertiary" => context.task.due_on.blank?,
|
52
|
+
"text-reset" => context.task.due_on.present?
|
53
|
+
)
|
54
|
+
)
|
55
|
+
) do
|
56
|
+
if context.task.due_on
|
57
|
+
context.plain context.task.due_on.strftime("%a, %b %e, %Y")
|
58
|
+
else
|
59
|
+
context.plain " Select a date... "
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
}
|
64
|
+
|
65
|
+
description_item = proc { |context:|
|
66
|
+
InnerPlan::Tasks::Show::ItemComponent.new(icon: Phlex::Icons::Tabler::FileText, title: 'Description') do
|
67
|
+
if context.task.description.present?
|
68
|
+
context.plain context.description.to_s
|
69
|
+
else
|
70
|
+
context.a(
|
71
|
+
href: (context.helpers.edit_task_path(context.task, focus: :description)),
|
72
|
+
class:
|
73
|
+
(
|
74
|
+
context.class_names(
|
75
|
+
"text-decoration-none",
|
76
|
+
"text-body-tertiary" => context.task.description.blank?,
|
77
|
+
"text-reset" => context.task.description.present?
|
78
|
+
)
|
79
|
+
)
|
80
|
+
) { " Click to add description... " }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
}
|
84
|
+
|
85
|
+
created_by_item = proc { |context:|
|
86
|
+
InnerPlan::Tasks::Show::ItemComponent.new(icon: Phlex::Icons::Tabler::CirclePlus, title: 'Created By') do
|
87
|
+
context.div(class: "text-body-tertiary") do
|
88
|
+
context.render(InnerPlan::UserWithAvatarComponent.new(context.task.user))
|
89
|
+
context.plain " on "
|
90
|
+
context.plain context.task.created_at.strftime("%a, %b %e, %Y")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
@task_show_view_rows = InnerPlan::SmartArray.new([
|
96
|
+
InnerPlan::SmartArray::Item.new(:primary_row, {}, InnerPlan::SmartArray.new([
|
97
|
+
InnerPlan::SmartArray::Item.new(:due_on, { span: 6 }, due_on_item),
|
98
|
+
])),
|
99
|
+
|
100
|
+
InnerPlan::SmartArray::Item.new(:secondary_row, {}, InnerPlan::SmartArray.new([
|
101
|
+
InnerPlan::SmartArray::Item.new(:description, { span: 12 }, description_item)
|
102
|
+
])),
|
103
|
+
|
104
|
+
InnerPlan::SmartArray::Item.new(:tertiary_row, {}, InnerPlan::SmartArray.new([
|
105
|
+
InnerPlan::SmartArray::Item.new(:created_by, { span: 12 }, created_by_item)
|
106
|
+
]))
|
107
|
+
])
|
108
|
+
|
109
|
+
@task_row_addons = InnerPlan::SmartArray.new([
|
110
|
+
::InnerPlan::SmartArray::Item.new(:group, {}, 'InnerPlan::Tasks::Row::GroupAddonComponent'),
|
111
|
+
::InnerPlan::SmartArray::Item.new(:description, {}, 'InnerPlan::Tasks::Row::DescriptionAddonComponent'),
|
112
|
+
::InnerPlan::SmartArray::Item.new(:due_on, {}, 'InnerPlan::Tasks::Row::DueOnAddonComponent'),
|
113
|
+
])
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module InnerPlan
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace InnerPlan
|
4
|
+
|
5
|
+
initializer "inner_plan.importmap", before: "importmap" do |app|
|
6
|
+
# https://github.com/rails/importmap-rails#composing-import-maps
|
7
|
+
# app.config.importmap.paths << root.join("config/importmap.rb")
|
8
|
+
InnerPlan.importmap.draw(root.join("config/importmap.rb"))
|
9
|
+
|
10
|
+
# https://github.com/rails/importmap-rails#sweeping-the-cache-in-development-and-test
|
11
|
+
app.config.importmap.cache_sweepers << root.join("app/javascript")
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer "inner_plan.assets" do |app|
|
15
|
+
# my_engine/app/javascript needs to be in the asset path
|
16
|
+
app.config.assets.paths << root.join("app/javascript")
|
17
|
+
|
18
|
+
# manifest has to be precompiled
|
19
|
+
app.config.assets.precompile += %w[inner_plan/manifest.js]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module InnerPlan
|
2
|
+
class SmartArray < Array
|
3
|
+
class Item < Struct.new(:key, :options, :content); end
|
4
|
+
|
5
|
+
class KeyNotFoundError < StandardError; end
|
6
|
+
class WrongItemTypeError < StandardError; end
|
7
|
+
class ItemKeyNotUniqueError < StandardError; end
|
8
|
+
|
9
|
+
def add(item)
|
10
|
+
raise WrongItemTypeError unless item.is_a?(Item)
|
11
|
+
raise ItemKeyNotUniqueError if has_item_key?(item.key)
|
12
|
+
|
13
|
+
self.push(item)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_before(before_key, item)
|
17
|
+
raise WrongItemTypeError unless item.is_a?(Item)
|
18
|
+
raise ItemKeyNotUniqueError if has_item_key?(item.key)
|
19
|
+
|
20
|
+
index = find_item_index(before_key)
|
21
|
+
raise KeyNotFoundError unless index
|
22
|
+
|
23
|
+
self.insert(index, item)
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_after(after_key, item)
|
27
|
+
raise WrongItemTypeError unless item.is_a?(Item)
|
28
|
+
raise ItemKeyNotUniqueError if has_item_key?(item.key)
|
29
|
+
|
30
|
+
index = find_item_index(after_key)
|
31
|
+
raise KeyNotFoundError unless index
|
32
|
+
|
33
|
+
self.insert(index + 1, item)
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace(key, item)
|
37
|
+
raise WrongItemTypeError unless item.is_a?(Item)
|
38
|
+
|
39
|
+
index = find_item_index(key)
|
40
|
+
raise KeyNotFoundError unless index
|
41
|
+
|
42
|
+
self[index] = item
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(key)
|
48
|
+
index = find_item_index(key)
|
49
|
+
raise KeyNotFoundError unless index
|
50
|
+
|
51
|
+
self[index]
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def find_item_index(key)
|
57
|
+
self.find_index do |item|
|
58
|
+
item.key == key
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_item_key?(key)
|
63
|
+
find_item_index(key) != nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/inner_plan.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "inner_plan/version"
|
2
|
+
require "inner_plan/engine"
|
3
|
+
require "inner_plan/configuration"
|
4
|
+
require "inner_plan/smart_array"
|
5
|
+
|
6
|
+
require "turbo-rails"
|
7
|
+
require "positioning"
|
8
|
+
require "importmap-rails"
|
9
|
+
require "image_processing"
|
10
|
+
require "phlex"
|
11
|
+
require "phlex-rails"
|
12
|
+
require "phlex-icons-tabler"
|
13
|
+
require "trailblazer-rails"
|
14
|
+
require "stringex"
|
15
|
+
|
16
|
+
module InnerPlan
|
17
|
+
class << self
|
18
|
+
def configuration
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
yield(configuration)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
mattr_accessor :importmap, default: Importmap::Map.new
|
28
|
+
end
|