Skip to content

Commit fc0d78a

Browse files
committed
quote date line item CRUD
(no turbo)
1 parent 44e3722 commit fc0d78a

21 files changed

+377
-6
lines changed

app/assets/stylesheets/application.sass.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
@import "components/flash";
1919
@import "components/empty_state";
2020
@import "components/line_item_date";
21+
@import "components/line_item";
2122

2223
// Layouts
2324
@import "layouts/container";
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
.line-item {
2+
display: flex;
3+
align-items: start;
4+
flex-wrap: wrap;
5+
background-color: var(--color-white);
6+
7+
gap: var(--space-xs);
8+
margin-bottom: var(--space-s);
9+
padding: var(--space-xs);
10+
border-radius: var(--border-radius);
11+
12+
>* {
13+
margin-bottom: 0;
14+
}
15+
16+
&__name {
17+
flex: 1 1 100%;
18+
font-weight: bold;
19+
20+
@include media(tabletAndUp) {
21+
flex: 1 1 0;
22+
}
23+
}
24+
25+
&__description {
26+
flex-basis: 100%;
27+
max-width: 100%;
28+
color: var(--color-text-muted);
29+
font-weight: normal;
30+
font-size: var(--font-size-s);
31+
}
32+
33+
&__quantity-price {
34+
flex: 0 0 auto;
35+
align-self: flex-end;
36+
justify-self: flex-end;
37+
order: 3;
38+
39+
font-weight: bold;
40+
41+
@include media(tabletAndUp) {
42+
display: none;
43+
}
44+
}
45+
46+
&__quantity {
47+
flex: 1;
48+
display: none;
49+
50+
@include media(tabletAndUp) {
51+
display: revert;
52+
flex: 0 0 7rem;
53+
}
54+
}
55+
56+
&__price {
57+
flex: 1;
58+
display: none;
59+
60+
@include media(tabletAndUp) {
61+
display: revert;
62+
flex: 0 0 9rem;
63+
}
64+
}
65+
66+
&__actions {
67+
display: flex;
68+
gap: var(--space-xs);
69+
order: 2;
70+
flex: 1 1 auto;
71+
72+
@include media(tabletAndUp) {
73+
order: revert;
74+
flex: 0 0 10rem;
75+
}
76+
}
77+
78+
&--form {
79+
box-shadow: var(--shadow-small);
80+
81+
.line-item__quantity,
82+
.line-item__price {
83+
display: block;
84+
}
85+
86+
.line-item__description {
87+
order: 2;
88+
}
89+
}
90+
91+
&--header {
92+
display: none;
93+
background-color: var(--color-light);
94+
margin-bottom: var(--space-s);
95+
96+
@include media(tabletAndUp) {
97+
display: flex;
98+
}
99+
100+
&>* {
101+
font-size: var(--font-size-s);
102+
font-weight: bold;
103+
letter-spacing: 1px;
104+
text-transform: uppercase;
105+
}
106+
}
107+
}

app/assets/stylesheets/components/_line_item_date.scss

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,28 @@
2121
display: flex;
2222
gap: var(--space-xs);
2323
}
24+
25+
&__body {
26+
border-radius: var(--border-radius);
27+
background-color: var(--color-white);
28+
box-shadow: var(--shadow-small);
29+
margin-top: var(--space-xs);
30+
padding: var(--space-xxs);
31+
padding-top: 0;
32+
33+
@include media(tabletAndUp) {
34+
padding: var(--space-m);
35+
}
36+
}
37+
38+
&__footer {
39+
border: dashed 2px var(--color-light);
40+
border-radius: var(--border-radius);
41+
text-align: center;
42+
padding: var(--space-xxs);
43+
44+
@include media(tabletAndUp) {
45+
padding: var(--space-m);
46+
}
47+
}
2448
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
class LineItemsController < ApplicationController
2+
before_action :set_quote
3+
before_action :set_line_item_date
4+
before_action :set_line_item, only: [:edit, :update, :destroy]
5+
6+
def new
7+
@line_item = @line_item_date.line_items.build
8+
end
9+
10+
def create
11+
@line_item = @line_item_date.line_items.build(line_item_params)
12+
13+
if @line_item.save
14+
redirect_to quote_path(@quote), notice: "Item was successfully created."
15+
else
16+
render :new, status: :unprocessable_entity
17+
end
18+
end
19+
20+
def edit
21+
end
22+
23+
def update
24+
if @line_item.update(line_item_params)
25+
redirect_to quote_path(@quote), notice: "Item was successfully updated."
26+
else
27+
render :edit, status: :unprocessable_entity
28+
end
29+
end
30+
31+
def destroy
32+
@line_item.destroy
33+
34+
redirect_to quote_path(@quote), notice: "Item was successfully destroyed."
35+
end
36+
37+
private
38+
39+
def line_item_params
40+
params.require(:line_item).permit(:name, :description, :quantity, :unit_price)
41+
end
42+
43+
def set_quote
44+
@quote = current_company.quotes.find(params[:quote_id])
45+
end
46+
47+
def set_line_item_date
48+
@line_item_date = @quote.line_item_dates.find(params[:line_item_date_id])
49+
end
50+
51+
def set_line_item
52+
@line_item = @line_item_date.line_items.find(params[:id])
53+
end
54+
end

app/controllers/quotes_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def index
77
end
88

99
def show
10-
@line_item_dates = @quote.line_item_dates.ordered
10+
@line_item_dates = @quote.line_item_dates.includes(:line_items).ordered
1111
end
1212

1313
def new

app/models/line_item.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class LineItem < ApplicationRecord
2+
belongs_to :line_item_date
3+
4+
validates :name, presence: true
5+
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
6+
validates :unit_price, presence: true, numericality: { greater_than: 0 }
7+
8+
delegate :quote, to: :line_item_date
9+
end

app/models/line_item_date.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
class LineItemDate < ApplicationRecord
22
belongs_to :quote
3+
has_many :line_items, dependent: :destroy #, strict_loading: true
34

45
validates :date, presence: true, uniqueness: { scope: :quote_id }
56

app/models/quote.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class Quote < ApplicationRecord
22
belongs_to :company
3-
has_many :line_item_dates, dependent: :destroy
3+
has_many :line_item_dates, dependent: :destroy #, strict_loading: true
44

55
validates :name, presence: true
66

app/views/line_item_dates/_line_item_date.html.erb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,22 @@
1717
class: "btn btn--light" %>
1818
</div>
1919
</div>
20+
<div class="line-item-date__body">
21+
<div class="line-item line-item--header">
22+
<div class="line-item__name">Article</div>
23+
<div class="line-item__quantity">Quantity</div>
24+
<div class="line-item__price">Price</div>
25+
<div class="line-item__actions"></div>
26+
</div>
27+
28+
<%= render line_item_date.line_items, quote: quote, line_item_date: line_item_date %>
29+
30+
<div class="line-item-date__footer">
31+
<%= link_to "Add item",
32+
[:new, quote, line_item_date, :line_item],
33+
data: { turbo_frame: "_top" },
34+
class: "btn btn--primary" %>
35+
</div>
36+
</div>
2037
</div>
2138
<% end %>

app/views/line_items/_form.html.erb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<%= simple_form_for [quote, line_item_date, line_item],
2+
html: { class: "form line-item line-item--form" } do |f| %>
3+
4+
<%= form_error_notification(line_item) %>
5+
6+
<%= f.input :name,
7+
wrapper_html: { class: "line-item__name" },
8+
input_html: { autofocus: true } %>
9+
<%= f.input :quantity,
10+
wrapper_html: { class: "line-item__quantity" } %>
11+
<%= f.input :unit_price,
12+
wrapper_html: { class: "line-item__price" } %>
13+
<%= f.input :description,
14+
wrapper_html: { class: "line-item__description" } %>
15+
16+
<div class="line-item__actions">
17+
<%= link_to "Cancel", quote_path(quote), class: "btn btn--light" %>
18+
<%= f.submit class: "btn btn--secondary" %>
19+
</div>
20+
<% end %>

0 commit comments

Comments
 (0)