ideasbugs 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/app/controllers/ideasbugs/application_controller.rb +4 -0
- data/app/controllers/ideasbugs/feedbacks_controller.rb +3 -1
- data/app/views/ideasbugs/feedbacks/_feedback_panel.html.erb +65 -0
- data/app/views/ideasbugs/feedbacks/index.html.erb +89 -72
- data/app/views/ideasbugs/feedbacks/show.html.erb +3 -58
- data/app/views/layouts/ideasbugs/application.html.erb +1 -1
- data/lib/generators/ideasbugs/install/templates/initializer.rb +4 -0
- data/lib/ideasbugs/configuration.rb +5 -0
- data/lib/ideasbugs/dashboard.css +83 -1
- data/lib/ideasbugs/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 557db9d9cbc47fc16e7f9ff26ebf1d95d77914c3796b0a4260368ca00905e3ae
|
|
4
|
+
data.tar.gz: e6d9b54315f9168159c77c60c08a13e2847d2edfb2e6d31ac6311619808bbad5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c7df315ef2d7bd4f3b2cb83f52bb927b922b484648061e82f1a9a8b61469f127c0009a4bdc401199c8203269720ca605324e278cc61eb7de3490dbbc03e1f27
|
|
7
|
+
data.tar.gz: 861a573a6bd3c751ebe2a8fa19e2b860bf95e650103729d9ae8a9d75c03c8c5ba35198ef7baced02f91b1ec19bbd28e7ff76521d64baf0506ac66b6383ace91c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.5 (2026-07-31)
|
|
4
|
+
|
|
5
|
+
- Added `config.admin_layout`, letting host apps render the Ideasbugs dashboard
|
|
6
|
+
inside their own admin layout while keeping the standalone gem layout as the
|
|
7
|
+
default.
|
|
8
|
+
|
|
9
|
+
## 0.7.4 (2026-07-31)
|
|
10
|
+
|
|
11
|
+
- Redesigned the admin feedback dashboard into a two-column triage layout with
|
|
12
|
+
status filters, a selected-record pane, and refreshed before/after
|
|
13
|
+
screenshots.
|
|
14
|
+
- Kept standalone feedback show pages working independently, including
|
|
15
|
+
scrollable detail content on narrow screens.
|
|
16
|
+
|
|
3
17
|
## 0.7.3 (2026-07-31)
|
|
4
18
|
|
|
5
19
|
- Added `mount_ideasbugs at: "/feedback"` as the install-time route helper,
|
|
@@ -4,7 +4,7 @@ module Ideasbugs
|
|
|
4
4
|
class FeedbacksController < ApplicationController
|
|
5
5
|
PER_PAGE = 50
|
|
6
6
|
|
|
7
|
-
layout
|
|
7
|
+
layout :ideasbugs_admin_layout, except: :create
|
|
8
8
|
|
|
9
9
|
# The widget posts here; everything else is the triage dashboard.
|
|
10
10
|
before_action :require_enabled, only: :create
|
|
@@ -36,6 +36,8 @@ module Ideasbugs
|
|
|
36
36
|
@more = @feedbacks.size > PER_PAGE
|
|
37
37
|
@feedbacks = @feedbacks.first(PER_PAGE)
|
|
38
38
|
|
|
39
|
+
@selected_feedback = tenant_scope.find_by(id: params[:feedback_id]) if params[:feedback_id].present?
|
|
40
|
+
|
|
39
41
|
# Only surface the Section column when it can carry information: the host
|
|
40
42
|
# configured sections, or some record already has one (e.g. sections were
|
|
41
43
|
# configured historically). Otherwise it's a permanently blank column.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<section class="feedback-panel">
|
|
2
|
+
<div class="panel-head">
|
|
3
|
+
<h1>
|
|
4
|
+
<span class="badge kind-<%= feedback.kind.to_s.parameterize %>">
|
|
5
|
+
<%= t("ideasbugs.kinds.#{feedback.kind}", default: feedback.kind.humanize) %>
|
|
6
|
+
</span>
|
|
7
|
+
<span class="badge status-<%= feedback.status %>">
|
|
8
|
+
<%= t("ideasbugs.statuses.#{feedback.status}", default: feedback.status.humanize) %>
|
|
9
|
+
</span>
|
|
10
|
+
<span class="case-id">#<%= feedback.id %></span>
|
|
11
|
+
</h1>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="detail-scroll">
|
|
15
|
+
<div class="card pad">
|
|
16
|
+
<dl>
|
|
17
|
+
<% if feedback.section.present? %>
|
|
18
|
+
<dt><%= t('ideasbugs.section', default: 'Section') %></dt>
|
|
19
|
+
<dd><%= feedback.section %></dd>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% if feedback.author_label.present? || feedback.author_id.present? %>
|
|
22
|
+
<dt><%= t('ideasbugs.dashboard.from', default: 'From') %></dt>
|
|
23
|
+
<dd><%= feedback.author_label.presence || "##{feedback.author_id}" %></dd>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% if feedback.page_url.present? %>
|
|
26
|
+
<dt><%= t('ideasbugs.dashboard.page', default: 'Page') %></dt>
|
|
27
|
+
<dd><%= link_to feedback.page_url, feedback.page_url, target: '_blank', rel: 'noopener' %></dd>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% if feedback.user_agent.present? %>
|
|
30
|
+
<dt><%= t('ideasbugs.dashboard.browser', default: 'Browser') %></dt>
|
|
31
|
+
<dd class="muted"><%= feedback.user_agent %></dd>
|
|
32
|
+
<% end %>
|
|
33
|
+
<dt><%= t('ideasbugs.dashboard.filed', default: 'Filed') %></dt>
|
|
34
|
+
<dd><%= feedback.created_at %></dd>
|
|
35
|
+
</dl>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="card pad">
|
|
39
|
+
<p class="message"><%= feedback.message %></p>
|
|
40
|
+
|
|
41
|
+
<% if feedback.screenshots? %>
|
|
42
|
+
<div class="shots">
|
|
43
|
+
<% feedback.screenshots.each do |screenshot| %>
|
|
44
|
+
<%= link_to feedback_screenshot_path(feedback, screenshot.id), target: '_blank', rel: 'noopener' do %>
|
|
45
|
+
<%= image_tag feedback_screenshot_path(feedback, screenshot.id), alt: screenshot.filename %>
|
|
46
|
+
<% end %>
|
|
47
|
+
<% end %>
|
|
48
|
+
</div>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="actions">
|
|
53
|
+
<% (Ideasbugs::Feedback::STATUSES - [feedback.status]).each do |status| %>
|
|
54
|
+
<%= button_to t("ideasbugs.dashboard.move_to_#{status}", default: "Mark #{status.humanize.downcase}"),
|
|
55
|
+
feedback_path(feedback),
|
|
56
|
+
method: :patch, params: { feedback: { status: status } },
|
|
57
|
+
class: ('primary' if status == 'resolved') %>
|
|
58
|
+
<% end %>
|
|
59
|
+
<%= button_to t('ideasbugs.dashboard.delete', default: 'Delete'),
|
|
60
|
+
feedback_path(feedback),
|
|
61
|
+
method: :delete, class: 'danger',
|
|
62
|
+
form: { onsubmit: "return confirm('#{t('ideasbugs.dashboard.delete_confirm', default: 'Delete this feedback?')}')" } %>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
@@ -1,82 +1,99 @@
|
|
|
1
|
+
<% content_for :body_class, 'ib-index' %>
|
|
2
|
+
|
|
1
3
|
<h1><%= t('ideasbugs.dashboard.title', default: 'IdeasBugs') %></h1>
|
|
2
4
|
|
|
3
|
-
<div class="
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
<div class="triage-shell <%= 'has-selected' if @selected_feedback %>">
|
|
6
|
+
<aside class="triage-sidebar" aria-label="<%= t('ideasbugs.dashboard.feedback', default: 'Feedback') %>">
|
|
7
|
+
<div class="tabs">
|
|
8
|
+
<% Ideasbugs::Feedback::STATUSES.each do |status| %>
|
|
9
|
+
<%= link_to feedbacks_path(status: status, kind: @kind, q: @query),
|
|
10
|
+
class: ('active' if status == @status) do %>
|
|
11
|
+
<%= t("ideasbugs.statuses.#{status}", default: status.humanize) %>
|
|
12
|
+
<span class="count"><%= @counts.fetch(status, 0) %></span>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
12
16
|
|
|
13
|
-
<form class="filters" method="get">
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
</form>
|
|
17
|
+
<form class="filters" method="get">
|
|
18
|
+
<input type="hidden" name="status" value="<%= @status %>">
|
|
19
|
+
<select name="kind" onchange="this.form.submit()">
|
|
20
|
+
<option value=""><%= t('ideasbugs.dashboard.all_kinds', default: 'All types') %></option>
|
|
21
|
+
<% Ideasbugs.config.kinds.each do |kind| %>
|
|
22
|
+
<option value="<%= kind %>" <%= 'selected' if kind.to_s == @kind %>>
|
|
23
|
+
<%= t("ideasbugs.kinds.#{kind}", default: kind.to_s.humanize) %>
|
|
24
|
+
</option>
|
|
25
|
+
<% end %>
|
|
26
|
+
</select>
|
|
27
|
+
<input type="search" name="q" value="<%= @query %>"
|
|
28
|
+
placeholder="<%= t('ideasbugs.dashboard.search_placeholder', default: 'Search message, author, section…') %>"
|
|
29
|
+
aria-label="<%= t('ideasbugs.dashboard.search', default: 'Search') %>">
|
|
30
|
+
<button><%= t('ideasbugs.dashboard.search', default: 'Search') %></button>
|
|
31
|
+
</form>
|
|
28
32
|
|
|
29
|
-
<div class="
|
|
30
|
-
|
|
31
|
-
<table>
|
|
32
|
-
<thead>
|
|
33
|
-
<tr>
|
|
34
|
-
<th><%= t('ideasbugs.kind', default: 'Type') %></th>
|
|
35
|
-
<th><%= t('ideasbugs.dashboard.message', default: 'Message') %></th>
|
|
36
|
-
<% if @show_section %>
|
|
37
|
-
<th><%= t('ideasbugs.section', default: 'Section') %></th>
|
|
38
|
-
<% end %>
|
|
39
|
-
<th><%= t('ideasbugs.dashboard.from', default: 'From') %></th>
|
|
40
|
-
<th><%= t('ideasbugs.dashboard.filed', default: 'Filed') %></th>
|
|
41
|
-
</tr>
|
|
42
|
-
</thead>
|
|
43
|
-
<tbody>
|
|
33
|
+
<div class="feedback-list">
|
|
34
|
+
<% if @feedbacks.any? %>
|
|
44
35
|
<% @feedbacks.each do |feedback| %>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
<%= link_to feedbacks_path(status: @status, kind: @kind, q: @query, page: @page,
|
|
37
|
+
feedback_id: feedback.id),
|
|
38
|
+
class: ['feedback-row', ('active' if @selected_feedback&.id == feedback.id)].compact do %>
|
|
39
|
+
<span class="feedback-main">
|
|
40
|
+
<span class="feedback-topline">
|
|
41
|
+
<span class="badge kind-<%= feedback.kind.to_s.parameterize %>">
|
|
42
|
+
<%= t("ideasbugs.kinds.#{feedback.kind}", default: feedback.kind.humanize) %>
|
|
43
|
+
</span>
|
|
44
|
+
<span class="badge status-<%= feedback.status %>">
|
|
45
|
+
<%= t("ideasbugs.statuses.#{feedback.status}", default: feedback.status.humanize) %>
|
|
46
|
+
</span>
|
|
47
|
+
<span class="muted feedback-time" title="<%= feedback.created_at %>">
|
|
48
|
+
<%= time_ago_in_words(feedback.created_at) %>
|
|
49
|
+
</span>
|
|
50
|
+
</span>
|
|
51
|
+
<span class="feedback-message"><%= truncate(feedback.message, length: 120) %></span>
|
|
52
|
+
<span class="muted feedback-meta">
|
|
53
|
+
<span>#<%= feedback.id %></span>
|
|
54
|
+
<% if @show_section && feedback.section.present? %>
|
|
55
|
+
<span class="feedback-section"><%= feedback.section %></span>
|
|
56
|
+
<% end %>
|
|
57
|
+
<% if feedback.author_label.present? %>
|
|
58
|
+
<span><%= feedback.author_label %></span>
|
|
59
|
+
<% end %>
|
|
49
60
|
</span>
|
|
50
|
-
</
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<% if feedback.screenshots? %>
|
|
54
|
-
<div class="muted">📎 <%= feedback.screenshots.count %></div>
|
|
55
|
-
<% end %>
|
|
56
|
-
</td>
|
|
57
|
-
<% if @show_section %>
|
|
58
|
-
<td><%= feedback.section %></td>
|
|
61
|
+
</span>
|
|
62
|
+
<% if feedback.screenshots? %>
|
|
63
|
+
<span class="muted attachment-count">📎 <%= feedback.screenshots.count %></span>
|
|
59
64
|
<% end %>
|
|
60
|
-
|
|
61
|
-
<td class="muted" title="<%= feedback.created_at %>">
|
|
62
|
-
<%= time_ago_in_words(feedback.created_at) %>
|
|
63
|
-
</td>
|
|
64
|
-
</tr>
|
|
65
|
+
<% end %>
|
|
65
66
|
<% end %>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
<% else %>
|
|
68
|
+
<div class="empty"><%= t('ideasbugs.dashboard.empty', default: 'Nothing here yet.') %></div>
|
|
69
|
+
<% end %>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="pager">
|
|
73
|
+
<% if @page > 1 %>
|
|
74
|
+
<%= link_to t('ideasbugs.dashboard.newer', default: '← Newer'),
|
|
75
|
+
feedbacks_path(status: @status, kind: @kind, q: @query, page: @page - 1) %>
|
|
76
|
+
<% end %>
|
|
77
|
+
<% if @more %>
|
|
78
|
+
<%= link_to t('ideasbugs.dashboard.older', default: 'Older →'),
|
|
79
|
+
feedbacks_path(status: @status, kind: @kind, q: @query, page: @page + 1) %>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
82
|
+
</aside>
|
|
72
83
|
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
<main class="triage-detail" aria-label="<%= t('ideasbugs.dashboard.current_feedback', default: 'Current feedback') %>">
|
|
85
|
+
<% if @selected_feedback %>
|
|
86
|
+
<p class="mobile-back">
|
|
87
|
+
<%= link_to t('ideasbugs.dashboard.back', default: '← All feedback'),
|
|
88
|
+
feedbacks_path(status: @status, kind: @kind, q: @query, page: @page) %>
|
|
89
|
+
</p>
|
|
90
|
+
<%= render 'feedback_panel', feedback: @selected_feedback %>
|
|
91
|
+
<% else %>
|
|
92
|
+
<div class="empty-state">
|
|
93
|
+
<h2><%= t('ideasbugs.dashboard.select_feedback', default: 'Select feedback') %></h2>
|
|
94
|
+
<p class="muted"><%= t('ideasbugs.dashboard.select_feedback_hint',
|
|
95
|
+
default: 'Open an item from the list to triage it without leaving the queue.') %></p>
|
|
96
|
+
</div>
|
|
97
|
+
<% end %>
|
|
98
|
+
</main>
|
|
82
99
|
</div>
|
|
@@ -1,63 +1,8 @@
|
|
|
1
|
+
<% content_for :body_class, 'ib-show' %>
|
|
2
|
+
|
|
1
3
|
<div class="breadcrumb">
|
|
2
4
|
<%= link_to t('ideasbugs.dashboard.title', default: 'IdeasBugs'), root_path %>
|
|
3
5
|
/ #<%= @feedback.id %>
|
|
4
6
|
</div>
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
<span class="badge kind-<%= @feedback.kind.to_s.parameterize %>">
|
|
8
|
-
<%= t("ideasbugs.kinds.#{@feedback.kind}", default: @feedback.kind.humanize) %>
|
|
9
|
-
</span>
|
|
10
|
-
<span class="badge status-<%= @feedback.status %>">
|
|
11
|
-
<%= t("ideasbugs.statuses.#{@feedback.status}", default: @feedback.status.humanize) %>
|
|
12
|
-
</span>
|
|
13
|
-
</h1>
|
|
14
|
-
|
|
15
|
-
<div class="card" style="padding: 16px; margin-bottom: 16px;">
|
|
16
|
-
<p class="message"><%= @feedback.message %></p>
|
|
17
|
-
|
|
18
|
-
<% if @feedback.screenshots? %>
|
|
19
|
-
<div class="shots">
|
|
20
|
-
<% @feedback.screenshots.each do |screenshot| %>
|
|
21
|
-
<%= link_to feedback_screenshot_path(@feedback, screenshot.id), target: '_blank', rel: 'noopener' do %>
|
|
22
|
-
<%= image_tag feedback_screenshot_path(@feedback, screenshot.id), alt: screenshot.filename %>
|
|
23
|
-
<% end %>
|
|
24
|
-
<% end %>
|
|
25
|
-
</div>
|
|
26
|
-
<% end %>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<div class="card" style="padding: 16px;">
|
|
30
|
-
<dl>
|
|
31
|
-
<% if @feedback.section.present? %>
|
|
32
|
-
<dt><%= t('ideasbugs.section', default: 'Section') %></dt>
|
|
33
|
-
<dd><%= @feedback.section %></dd>
|
|
34
|
-
<% end %>
|
|
35
|
-
<% if @feedback.author_label.present? || @feedback.author_id.present? %>
|
|
36
|
-
<dt><%= t('ideasbugs.dashboard.from', default: 'From') %></dt>
|
|
37
|
-
<dd><%= @feedback.author_label.presence || "##{@feedback.author_id}" %></dd>
|
|
38
|
-
<% end %>
|
|
39
|
-
<% if @feedback.page_url.present? %>
|
|
40
|
-
<dt><%= t('ideasbugs.dashboard.page', default: 'Page') %></dt>
|
|
41
|
-
<dd><%= link_to @feedback.page_url, @feedback.page_url, target: '_blank', rel: 'noopener' %></dd>
|
|
42
|
-
<% end %>
|
|
43
|
-
<% if @feedback.user_agent.present? %>
|
|
44
|
-
<dt><%= t('ideasbugs.dashboard.browser', default: 'Browser') %></dt>
|
|
45
|
-
<dd class="muted"><%= @feedback.user_agent %></dd>
|
|
46
|
-
<% end %>
|
|
47
|
-
<dt><%= t('ideasbugs.dashboard.filed', default: 'Filed') %></dt>
|
|
48
|
-
<dd><%= @feedback.created_at %></dd>
|
|
49
|
-
</dl>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
<div class="actions">
|
|
53
|
-
<% (Ideasbugs::Feedback::STATUSES - [@feedback.status]).each do |status| %>
|
|
54
|
-
<%= button_to t("ideasbugs.dashboard.move_to_#{status}", default: "Mark #{status.humanize.downcase}"),
|
|
55
|
-
feedback_path(@feedback),
|
|
56
|
-
method: :patch, params: { feedback: { status: status } },
|
|
57
|
-
class: ('primary' if status == 'resolved') %>
|
|
58
|
-
<% end %>
|
|
59
|
-
<%= button_to t('ideasbugs.dashboard.delete', default: 'Delete'),
|
|
60
|
-
feedback_path(@feedback),
|
|
61
|
-
method: :delete, class: 'danger',
|
|
62
|
-
form: { onsubmit: "return confirm('#{t('ideasbugs.dashboard.delete_confirm', default: 'Delete this feedback?')}')" } %>
|
|
63
|
-
</div>
|
|
8
|
+
<%= render 'feedback_panel', feedback: @feedback %>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<%= stylesheet_link_tag "#{dashboard_stylesheet_path}?v=#{Ideasbugs::Widget.dashboard_stylesheet_fingerprint}",
|
|
9
9
|
'data-turbo-track': 'reload' %>
|
|
10
10
|
</head>
|
|
11
|
-
<body>
|
|
11
|
+
<body class="<%= content_for?(:body_class) ? yield(:body_class) : '' %>">
|
|
12
12
|
<div class="container">
|
|
13
13
|
<%= yield %>
|
|
14
14
|
</div>
|
|
@@ -8,6 +8,10 @@ Ideasbugs.configure do |config|
|
|
|
8
8
|
# Who can browse and triage feedback at the mount path. Defaults to
|
|
9
9
|
# development only — override before deploying.
|
|
10
10
|
# config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
|
|
11
|
+
#
|
|
12
|
+
# Render the dashboard inside your app's admin layout. Default: the gem's
|
|
13
|
+
# standalone dashboard layout.
|
|
14
|
+
# config.admin_layout = "admin/application"
|
|
11
15
|
|
|
12
16
|
# Attribute feedback to a user (optional). Return an object responding to
|
|
13
17
|
# #id, or nil. Receives the request.
|
|
@@ -16,6 +16,10 @@ module Ideasbugs
|
|
|
16
16
|
# deploying, e.g. with an admin check.
|
|
17
17
|
attr_accessor :authorize_admin
|
|
18
18
|
|
|
19
|
+
# Layout used by the built-in dashboard. Override this to render Ideasbugs
|
|
20
|
+
# inside your app's admin shell, e.g. "admin/application".
|
|
21
|
+
attr_accessor :admin_layout
|
|
22
|
+
|
|
19
23
|
# Resolve the current user for attribution (optional). Return an object
|
|
20
24
|
# responding to #id, or nil. Receives the request.
|
|
21
25
|
attr_accessor :current_user
|
|
@@ -79,6 +83,7 @@ module Ideasbugs
|
|
|
79
83
|
def initialize
|
|
80
84
|
@enabled = ->(_request) { true }
|
|
81
85
|
@authorize_admin = ->(_request) { Rails.env.development? }
|
|
86
|
+
@admin_layout = 'ideasbugs/application'
|
|
82
87
|
@current_user = ->(_request) {}
|
|
83
88
|
@tenant = ->(_request) {}
|
|
84
89
|
@author_label = ->(user) { user.respond_to?(:email) ? user.email : user&.to_s }
|
data/lib/ideasbugs/dashboard.css
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
border-radius: 8px; background: var(--surface); color: var(--text); font: inherit; }
|
|
31
31
|
.filters input[type=search] { flex: 1; max-width: 320px; }
|
|
32
32
|
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
|
|
33
|
+
.card.pad { padding: 16px; }
|
|
33
34
|
table { width: 100%; border-collapse: collapse; }
|
|
34
35
|
th, td { padding: 10px 14px; text-align: left; vertical-align: top; }
|
|
35
36
|
th { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
background: var(--surface); color: var(--text); font: inherit; }
|
|
52
53
|
button.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
|
|
53
54
|
button.danger { color: var(--bug); }
|
|
54
|
-
.message { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
55
|
+
.message { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
55
56
|
dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; }
|
|
56
57
|
dt { color: var(--muted); }
|
|
57
58
|
dd { margin: 0; overflow-wrap: anywhere; }
|
|
@@ -59,3 +60,84 @@
|
|
|
59
60
|
.shots img { max-width: 280px; max-height: 200px; border: 1px solid var(--border); border-radius: 8px; }
|
|
60
61
|
.empty { padding: 48px 16px; text-align: center; color: var(--muted); }
|
|
61
62
|
.breadcrumb { margin-bottom: 12px; font-size: 13px; }
|
|
63
|
+
.case-id { color: var(--muted); font-weight: 400; font-size: 15px; }
|
|
64
|
+
.panel-head { flex: 0 0 auto; }
|
|
65
|
+
.panel-head h1 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
66
|
+
.detail-scroll { display: flex; flex-direction: column; gap: 12px; min-height: 0; }
|
|
67
|
+
.feedback-panel { min-width: 0; display: flex; flex-direction: column; }
|
|
68
|
+
.feedback-panel .actions { flex: 0 0 auto; }
|
|
69
|
+
.feedback-panel .detail-scroll > .actions { margin: 0; }
|
|
70
|
+
|
|
71
|
+
/* Desktop triage: list on the left, selected feedback on the right. */
|
|
72
|
+
.ib-index, .ib-index .container { height: 100vh; height: 100dvh; }
|
|
73
|
+
.ib-index { overflow: hidden; }
|
|
74
|
+
.ib-index .container { max-width: 1280px; display: flex; flex-direction: column; padding-bottom: 16px; }
|
|
75
|
+
.ib-index .container > h1 { flex: 0 0 auto; margin-bottom: 14px; }
|
|
76
|
+
.triage-shell { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(320px, 400px) 1fr;
|
|
77
|
+
gap: 16px; }
|
|
78
|
+
.triage-sidebar { min-width: 0; min-height: 0; display: flex; flex-direction: column;
|
|
79
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
80
|
+
border-radius: 8px; overflow: hidden; }
|
|
81
|
+
.triage-sidebar .tabs { flex: 0 0 auto; margin: 12px 12px 0; padding: 0; background: transparent;
|
|
82
|
+
border: 0; border-radius: 0; gap: 6px; flex-wrap: nowrap; }
|
|
83
|
+
.triage-sidebar .tabs a { flex: 1 1 0; position: relative; display: flex; align-items: center;
|
|
84
|
+
justify-content: center; gap: 6px; min-height: 34px; padding: 5px 10px 8px;
|
|
85
|
+
border-radius: 8px; color: var(--muted); font-weight: 600; }
|
|
86
|
+
.triage-sidebar .tabs a.active { color: var(--text); border: 0; background: transparent; box-shadow: none; margin: 0; }
|
|
87
|
+
.triage-sidebar .tabs a.active::after { content: ""; position: absolute; left: 20px; right: 20px; bottom: 0;
|
|
88
|
+
height: 2px; border-radius: 999px; background: var(--accent); }
|
|
89
|
+
.triage-sidebar .tabs a:not(.active):hover { text-decoration: none; color: var(--text); background: var(--bg); }
|
|
90
|
+
.triage-sidebar .tabs .count { margin-left: 0; background: color-mix(in srgb, var(--muted) 14%, transparent); }
|
|
91
|
+
.triage-sidebar .tabs a.active .count { background: var(--border); }
|
|
92
|
+
.triage-sidebar .filters { flex: 0 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) auto;
|
|
93
|
+
margin: 0; padding: 12px; border-bottom: 1px solid var(--border); }
|
|
94
|
+
.triage-sidebar .filters select { grid-column: 1 / -1; width: 100%; }
|
|
95
|
+
.triage-sidebar .filters input[type=search] { min-width: 0; max-width: none; }
|
|
96
|
+
.feedback-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
97
|
+
.feedback-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 13px 14px;
|
|
98
|
+
color: var(--text); border-bottom: 1px solid var(--border); }
|
|
99
|
+
.feedback-row:hover { text-decoration: none; background: var(--bg); }
|
|
100
|
+
.feedback-row.active { background: color-mix(in srgb, var(--accent) 9%, var(--surface));
|
|
101
|
+
box-shadow: inset 3px 0 0 var(--accent); }
|
|
102
|
+
.feedback-main { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
103
|
+
.feedback-topline, .feedback-meta { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
104
|
+
.feedback-message, .feedback-meta span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
105
|
+
.feedback-message { display: block; font-weight: 600; }
|
|
106
|
+
.feedback-time { margin-left: auto; white-space: nowrap; }
|
|
107
|
+
.attachment-count { align-self: start; white-space: nowrap; }
|
|
108
|
+
.triage-sidebar .pager { flex: 0 0 auto; margin: 0; padding: 10px 12px; border-top: 1px solid var(--border); }
|
|
109
|
+
.triage-detail { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
110
|
+
.triage-detail .feedback-panel { flex: 1 1 auto; min-height: 0; }
|
|
111
|
+
.triage-detail .detail-scroll { flex: 1 1 auto; overflow-y: auto; padding-right: 2px; }
|
|
112
|
+
.triage-detail .actions { margin-bottom: 0; }
|
|
113
|
+
.empty-state { margin: auto; max-width: 320px; padding: 24px; text-align: center; }
|
|
114
|
+
.empty-state h2 { margin: 0 0 6px; font-size: 18px; }
|
|
115
|
+
.empty-state p { margin: 0; }
|
|
116
|
+
.mobile-back { display: none; }
|
|
117
|
+
|
|
118
|
+
/* Standalone show pages keep normal page scrolling. */
|
|
119
|
+
.ib-show { min-height: 100vh; overflow: auto; }
|
|
120
|
+
.ib-show .feedback-panel { gap: 0; }
|
|
121
|
+
.ib-show .detail-scroll { overflow: visible; }
|
|
122
|
+
@media (max-width: 760px) {
|
|
123
|
+
body { font-size: 14px; }
|
|
124
|
+
.container { padding: 14px 10px 24px; }
|
|
125
|
+
.ib-index, .ib-show { overflow-x: hidden; }
|
|
126
|
+
.feedback-panel, .detail-scroll, .card, dl, dd { min-width: 0; max-width: 100%; }
|
|
127
|
+
.feedback-panel .card { width: calc(100vw - 20px); }
|
|
128
|
+
.message, dd, .muted { max-width: calc(100vw - 54px); }
|
|
129
|
+
.ib-index { overflow-y: auto; overflow-x: hidden; }
|
|
130
|
+
.ib-index, .ib-index .container { min-height: 100vh; height: auto; }
|
|
131
|
+
.ib-index .container { padding-bottom: 10px; }
|
|
132
|
+
.triage-shell { display: block; min-height: calc(100vh - 62px); }
|
|
133
|
+
.triage-sidebar, .triage-detail { min-height: calc(100vh - 62px); }
|
|
134
|
+
.triage-shell.has-selected .triage-sidebar { display: none; }
|
|
135
|
+
.triage-shell:not(.has-selected) .triage-detail { display: none; }
|
|
136
|
+
.feedback-list { overflow: visible; }
|
|
137
|
+
.feedback-row { padding: 12px; }
|
|
138
|
+
.feedback-meta { display: none; }
|
|
139
|
+
.triage-detail .feedback-panel { min-height: calc(100vh - 64px); }
|
|
140
|
+
.mobile-back { display: block; flex: 0 0 auto; margin: 10px 10px 0; font-size: 13px; }
|
|
141
|
+
.panel-head h1 { font-size: 18px; }
|
|
142
|
+
dl { grid-template-columns: 1fr; gap: 3px; }
|
|
143
|
+
}
|
data/lib/ideasbugs/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ideasbugs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- app/helpers/ideasbugs/widget_helper.rb
|
|
46
46
|
- app/models/ideasbugs/application_record.rb
|
|
47
47
|
- app/models/ideasbugs/feedback.rb
|
|
48
|
+
- app/views/ideasbugs/feedbacks/_feedback_panel.html.erb
|
|
48
49
|
- app/views/ideasbugs/feedbacks/index.html.erb
|
|
49
50
|
- app/views/ideasbugs/feedbacks/show.html.erb
|
|
50
51
|
- app/views/layouts/ideasbugs/application.html.erb
|