Add inline editing to series show page
Edit/Done toggle in the section header switches between static display
and inline editors for note, interval, and due date — same Alpine
components the old panel used. Urgency row highlighted in red when
overdue. Renamed "Due date" to "Next due date" for clarity.
Assisted-by: Claude Opus 4.6 via Claude Code
diff --git a/lib/ketchup/views/series/show.rb b/lib/ketchup/views/series/show.rb
index fcb932c..97f146b 100644
--- a/lib/ketchup/views/series/show.rb
+++ b/lib/ketchup/views/series/show.rb
@@ -19,11 +19,25 @@ module Views
render Layout.new(current_user: @current_user, title: "#{note_title} — Ketchup", active_view: nil) do
div(class: "dashboard") do
div(class: "main-column") do
- section(class: "section") do
+ section(class: "section", "x-data": "{ editing: false }") do
div(class: "section-header") do
h2(class: "section-title") do
span(class: "section-title-text") { "Series" }
end
+ button(
+ class: "section-edit-btn",
+ "x-show": "!editing",
+ "x-on:click": "editing = true; $dispatch('start-editing')"
+ ) do
+ plain "Edit"
+ end
+ button(
+ class: "section-edit-btn",
+ "x-show": "editing",
+ "x-on:click": "editing = false; $dispatch('stop-editing')"
+ ) do
+ plain "Done"
+ end
end
div(class: "series-note", id: "series-note-detail",
@@ -32,15 +46,53 @@ module Views
dl(class: "detail-fields") do
dt { "Repeat every" }
- dd { interval_text(@series.interval_count, @series.interval_unit) }
+ dd("x-show": "!editing") do
+ plain interval_text(@series.interval_count, @series.interval_unit)
+ end
+ dd(
+ class: "detail-edit-interval",
+ "x-show": "editing",
+ "x-cloak": true,
+ "x-data": "intervalEditor(#{@series.id}, #{@series.interval_count}, '#{@series.interval_unit}')"
+ ) do
+ input(
+ type: "number",
+ class: "detail-input detail-input-count",
+ min: 1,
+ "x-model.number": "count",
+ "x-on:change": "save()"
+ )
+ select(
+ class: "detail-input detail-input-unit",
+ "x-model": "unit",
+ "x-on:change": "save()"
+ ) do
+ INTERVAL_OPTIONS.each { |val, label| option(value: val) { label } }
+ end
+ end
if active_task
- dt { "Due date" }
- dd { active_task[:due_date].to_s }
+ dt { "Next due date" }
+ dd(
+ "x-show": "!editing",
+ "x-text": "new Date('#{active_task[:due_date]}T00:00').toLocaleDateString()"
+ ) { active_task[:due_date].to_s }
+ dd(
+ "x-show": "editing",
+ "x-cloak": true,
+ "x-data": "dueDateEditor(#{@series.id}, '#{active_task[:due_date]}')"
+ ) do
+ input(
+ type: "date",
+ class: "detail-input detail-input-date",
+ "x-model": "dueDate",
+ "x-on:change": "save()"
+ )
+ end
if active_task.urgency > 0
- dt { "Urgency" }
- dd { "#{format("%.1f", active_task.urgency)}×" }
+ dt(class: "detail-overdue") { "Urgency" }
+ dd(class: "detail-overdue") { "#{format("%.1f", active_task.urgency)}×" }
end
end
end
diff --git a/public/css/app.css b/public/css/app.css
index ab7bac5..5957715 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -161,6 +161,18 @@ h2 { font-size: var(--step-0); font-weight: 600; }
color: #666;
}
+.section-edit-btn {
+ all: unset;
+ order: 1;
+ font-size: var(--step--1);
+ color: #999;
+ cursor: pointer;
+}
+
+.section-edit-btn:hover {
+ color: #444;
+}
+
.section-header::after {
content: "";
flex: 1;
@@ -464,7 +476,7 @@ h2 { font-size: var(--step-0); font-weight: 600; }
.detail-fields {
display: grid;
grid-template-columns: auto 1fr;
- gap: var(--space-3xs) var(--space-s);
+ gap: var(--space-2xs) var(--space-s);
font-size: var(--step--2);
}
@@ -477,8 +489,12 @@ h2 { font-size: var(--step-0); font-weight: 600; }
color: #444;
}
+.detail-fields .detail-overdue {
+ color: #c00;
+}
+
.detail-input {
- font-size: var(--step--1);
+ font-size: inherit;
padding: 2px 6px;
margin: -3px -7px;
border: 1px solid #ddd;
@@ -503,12 +519,14 @@ h2 { font-size: var(--step-0); font-weight: 600; }
}
.detail-edit-interval {
- display: flex;
- gap: 4px;
+ display: grid;
+ grid-template-columns: 3.5em 1fr;
+ gap: var(--space-2xs);
}
.detail-input-count {
- width: 3.5em;
+ width: 100%;
+ min-width: 0;
-moz-appearance: textfield;
}
@@ -519,7 +537,7 @@ h2 { font-size: var(--step-0); font-weight: 600; }
}
.detail-input-unit {
- flex: 1;
+ width: 100%;
min-width: 0;
}