Skip to content

Commit

Permalink
Made price-lists table scrollable and made lists archivable (#911)
Browse files Browse the repository at this point in the history
* Made price-lists table scrollable and made lists archivable

* Fixed trailing whitespace

* Fixed scss linting issues
  • Loading branch information
Ellen-Wittingen authored Feb 7, 2024
1 parent 39d1dd0 commit 465dc99
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 62 deletions.
105 changes: 72 additions & 33 deletions app/assets/stylesheets/price_lists.scss
Original file line number Diff line number Diff line change
@@ -1,60 +1,99 @@
@import 'theme_sofia';

.center-text {
margin: 0 auto;
text-align: center;
}
.price-lists-table {
width: fit-content;
max-height: calc(100vh - 18rem);

.center-text {
margin: 0 auto;
text-align: center;
}

th {
&.products-id {
.products-id,
.products-cancel-edit {
left: 0;
width: 1.75rem;
}

&.products-actions {
width: 4rem;
.products-name {
left: 2.1rem;
width: 6rem;
}

.products-actions,
.products-new-edit-button,
.products-save-new {
right: 0;
}

.products-id,
.products-cancel-edit,
.products-name,
.products-new-edit-button,
.products-save-new {
position: sticky;
z-index: 1;
background-color: $body-bg;
}

.products-actions {
position: sticky;
z-index: 1;
}

thead {
position: sticky;
top: 0;
z-index: 2;
background-color: $body-bg;
}

th {
&.products-actions {
width: 4rem;
}
}
}

.price_lists_table {
tr {
&:last-child {
td {
border-bottom-width: 0;
}
}
}
}

td {
&.products-new-edit-button {
padding: .5rem 1rem;
}

&.products-new {
input {
&.form-control {
width: 6rem;
}
td {
&.products-new-edit-button {
padding: .5rem 1rem;
}

select {
&.form-control {
width: 4rem;
&.products-new {
input {
&.form-control {
width: 6rem;
}
}

select {
&.form-control {
width: 4rem;
}
}
}
}

&.products-new-price {
input {
&.form-control {
width: 2rem;
&.products-new-price {
input {
&.form-control {
width: 4rem;
}
}
}
}

&.products-cancel-new,
&.products-cancel-edit,
&.products-save-new {
vertical-align: middle;
&.products-cancel-new,
&.products-cancel-edit,
&.products-save-new {
vertical-align: middle;
}
}
}
36 changes: 33 additions & 3 deletions app/controllers/price_lists_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ class PriceListsController < ApplicationController
after_action :verify_authorized

def index
recent_price_lists = PriceList.order(created_at: :desc).limit(6)
price_lists = PriceList.order(created_at: :desc)
products = Product.all.order(:id).includes(:product_prices)

authorize recent_price_lists
authorize price_lists

@price_list = PriceList.new

@recent_price_lists_json = recent_price_lists.to_json(except: %i[created_at updated_at deleted_at])
@price_lists_json = price_lists.to_json(except: %i[created_at updated_at deleted_at])
@products_json = products.to_json(
include: { product_prices: { except: %i[created_at updated_at deleted_at] } },
methods: :t_category,
Expand Down Expand Up @@ -43,6 +43,36 @@ def update
redirect_to @price_list
end

def archive
@price_list = PriceList.find(params[:id])
authorize @price_list

@price_list.archived_at = Time.zone.now

respond_to do |format|
if @price_list.save
format.json { render json: @price_list.archived_at }
else
format.json { render json: @price_list.errors, status: :unprocessable_entity }
end
end
end

def unarchive
@price_list = PriceList.find(params[:id])
authorize @price_list

@price_list.archived_at = nil

respond_to do |format|
if @price_list.save
format.json { render json: @price_list.archived_at }
else
format.json { render json: @price_list.errors, status: :unprocessable_entity }
end
end
end

private

def permitted_attributes
Expand Down
29 changes: 27 additions & 2 deletions app/javascript/packs/price_lists.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Vue.use(VueResource);
document.addEventListener('turbolinks:load', () => {
Vue.http.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

var element = document.getElementById('price_lists_table');
var element = document.getElementById('pricelists-container');
if (element != null) {
var priceLists = JSON.parse(element.dataset.priceLists);
var products = JSON.parse(element.dataset.products);
Expand All @@ -19,7 +19,16 @@ document.addEventListener('turbolinks:load', () => {
new Vue({
el: element,
data: () => {
return { priceLists: priceLists, products: products };
return { priceLists: priceLists, products: products, showArchived: false };
},
computed: {
filteredPriceLists: function() {
if (this.showArchived) {
return priceLists;
} else {
return priceLists.filter(priceList => !priceList.archived_at);
}
}
},
methods: {
findPrice: function(product, priceList) {
Expand Down Expand Up @@ -124,6 +133,22 @@ document.addEventListener('turbolinks:load', () => {
return products;
},

archivePriceList: function(priceList) {
this.$http.post(`/price_lists/${priceList.id}/archive`, {}).then((response) => {
priceList.archived_at = response.data;
}, (response) => {
this.errors = response.data.errors;
});
},

unarchivePriceList: function(priceList) {
this.$http.post(`/price_lists/${priceList.id}/unarchive`, {}).then((response) => {
priceList.archived_at = response.data;
}, (response) => {
this.errors = response.data.errors;
});
},

productPriceToCurrency: function(productPrice) {
return (productPrice && productPrice.price) ? `€ ${parseFloat(productPrice.price).toFixed(2)}` : '';
},
Expand Down
8 changes: 8 additions & 0 deletions app/policies/price_list_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ def update?
create?
end

def archive?
create?
end

def unarchive?
create?
end

def search?
index?
end
Expand Down
52 changes: 33 additions & 19 deletions app/views/price_lists/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,43 @@
<%= render 'modal' %>
<% end %>
<div class="container footer-padding">
<%= content_tag :div, id: "pricelists-container", class: "container footer-padding",
data: {price_lists: @price_lists_json, products: @products_json} do %>
<div class="row">
<div class="col-sm-12 mt-4">
<h1>
Prijslijsten
</h1>
<div class="d-flex justify-content-between mb-3 align-items-center flex-wrap">
<span class="my-1">
Hier worden de vijf nieuwste prijslijsten en alle producten getoond.
Hier worden alle prijslijsten en alle producten getoond.
</span>
<button class="btn btn-sm btn-primary" data-bs-target="#editPriceListModal" data-bs-toggle="modal" role="button">
<%= fa_icon 'plus', class: 'me-1' %>
Nieuwe prijslijst
</button>
</div>

<%= content_tag :table, id: 'price_lists_table', class: 'price_lists_table table table-striped table-responsive-md',
data: {price_lists: @recent_price_lists_json, products: @products_json} do %>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="show-archived-check" v-model="showArchived">
<label class="form-check-label" for="show-archived-check">
Laat gearchieveerde prijslijsten zien
</label>
</div>

<table id='price-lists-table' class='price-lists-table table table-striped overflow-scroll mw-100 mx-auto d-block'>
<thead class="table-header-rotated products">
<tr>
<th class="products-id" scope="col">ID</th>
<th class="products-name" scope="col">Product</th>
<th class="products-category" scope="col">Categorie</th>
<th class="products-pricelist rotate" scope="col" v-for="priceList in priceLists">
<th class="products-pricelist rotate" scope="col" v-for="priceList in filteredPriceLists">
<div class="center-text" v-bind:title="priceList.name">
<span>
{{ priceList.name }}
</span>
</div>
</th>
<th class="products-actions" v-if="priceLists.length > 0">
<th class="products-actions" v-if="filteredPriceLists.length > 0">
</th>
</tr>
</thead>
Expand All @@ -48,12 +54,12 @@
<%= fa_icon 'times lg' %>
</span>
</td>
<td class="products-new">
<td class="products-new products-name">
<div class="d-flex">
<input class="form-control flex-grow-1" placeholder="Productnaam" type="text" v-model="product.name" :disabled="product.id"/>
</div>
</td>
<td class="products-new">
<td class="products-new products-category">
<div class="d-flex">
<select class="form-select flex-grow-1" v-model="product.category">
<% Product.categories.each do |category| %>
Expand All @@ -66,7 +72,7 @@
</select>
</div>
</td>
<td class="products-new-price" v-for="priceList in priceLists">
<td class="products-new-price" v-for="priceList in filteredPriceLists">
<div class="d-flex">
<input class="form-control flex-grow-1" placeholder="2.18" type="text" v-model="findPrice(product, priceList).price"/>
</div>
Expand All @@ -77,34 +83,42 @@
</span></td>
</template>
<template v-else="">
<th scope="row">{{ product.id }}</th>
<td>{{ product.name }}</td>
<td>{{ product.t_category }}</td>
<td class="product-price center-text" v-for="priceList in priceLists">
<span v-bind:title="product.name + ' in lijst ' + priceList.name">
<th class="products-id" scope="row">{{ product.id }}</th>
<td class="products-name">{{ product.name }}</td>
<td class="products-category">{{ product.t_category }}</td>
<td class="product-price center-text" v-for="priceList in filteredPriceLists">
<span class="text-nowrap" v-bind:title="product.name + ' in lijst ' + priceList.name">
{{ productPriceToCurrency(findPrice(product, priceList)) }}
</span>
</td>
<td class="products-new-edit-button" v-if="priceLists.length > 0">
<td class="products-new-edit-button" v-if="filteredPriceLists.length > 0">
<button class="btn btn-sm btn-outline-secondary" v-on:click="editProduct(product)">
<%= fa_icon 'pencil' %>
</button>
</td>
</template>
</tr>
<tr>
<td colspan="2">
<td colspan="3">
<div class="d-grid">
<button class="btn btn-secondary btn-sm products-button-new" v-on:click.prevent="newProduct">
Voeg een nieuw product toe
<%= fa_icon 'plus-square', class: 'ps-2' %>
</button>
</div>
</td>
<td colspan="8"></td>
<td class="products-archive-button center-text" v-for="priceList in filteredPriceLists">
<button class="btn btn-sm btn-outline-secondary" v-if="priceList.archived_at" v-on:click="unarchivePriceList(priceList)">
<%= fa_icon 'repeat' %>
</button>
<button class="btn btn-sm btn-outline-secondary" v-else="" v-on:click="archivePriceList(priceList)">
<%= fa_icon 'archive' %>
</button>
</td>
</tr>
</tbody>
<% end %>
</table>
</div>
</div>
</div>
<% end %>
7 changes: 6 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
end

resources :orders, only: %i[index create update destroy]
resources :price_lists, only: %i[index create update]
resources :price_lists, only: %i[index create update] do
member do
post :archive
post :unarchive
end
end

resources :users, only: %i[index show create update] do
collection do
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20240202124405_add_archived_at_to_price_list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddArchivedAtToPriceList < ActiveRecord::Migration[7.0]
def change
add_column :price_lists, :archived_at, :datetime
end
end
Loading

0 comments on commit 465dc99

Please sign in to comment.