Multilevel Category (CRUD admin+customer) + tree display + filters + global improvements

This commit is contained in:
Nicolas VARROT 2015-12-07 20:07:49 +01:00
parent 1fd0e4c624
commit d422a7843f
37 changed files with 413 additions and 102 deletions

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

View File

@ -0,0 +1,3 @@
// Place all the styles related to the admin/NeedCategories controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@ -69,19 +69,22 @@
}
span{
.top-left-info{
background-color: #ede8e8;
position:absolute;
padding:5px;
padding:4px;
padding-left:8px;
top:0px;
right:0px;
.info{
color:rgb(121, 120, 120);
text-align:right;
font-size:10px;
}
left:0px;
color: rgb(93, 93, 93);
text-align:left;
font-size:12px;
width:100%;
}
margin-bottom:50px;
h4{
margin-top:0px;

View File

@ -41,7 +41,6 @@ class Admin::AdminsController < ApplicationController
if @admin.update_attributes(admin_params)
else
render :action => "edit"
end

View File

@ -39,7 +39,7 @@ class Admin::CustomersController < ApplicationController
@customer = Customer.find(params[:id])
if @customer.update_attributes(params.require(:customer).permit!)
redirect_to admin_customers_path
else
render :action => "edit"
end

View File

@ -0,0 +1,65 @@
class Admin::NeedCategoriesController < ApplicationController
layout "admin"
before_action :build_tree, only:[:new, :edit, :index]
def index
end
def new
@category = NeedCategory.new()
end
def create
@category = NeedCategory.new(need_category_params)
if @category.save
flash[:notice] = "Catégorie créée avec succès."
redirect_to admin_need_categories_path
else
@tree = NeedCategory::create_tree
render "new"
end
end
def edit
@category = NeedCategory.find(params[:id])
end
def update
@category = NeedCategory.find(params[:id])
if @category.update_attributes(need_category_params)
flash[:notice] = "Catégorie modifiée avec succès."
redirect_to admin_need_categories_path
else
@tree = NeedCategory::create_tree
render :action => "edit"
end
end
def destroy
@category = NeedCategory.find(params[:id])
if @category.destroy
flash[:notice] = "Catégorie supprimée avec succès."
else
flash[:error] = "Impossible de supprimer cette catégorie."
end
@tree = NeedCategory::create_tree
render "index"
end
def need_category_params
params.require(:need_category).permit(:parent_id, :name)
end
private
def build_tree
@tree = NeedCategory::create_tree
end
end

View File

@ -1,5 +1,7 @@
class Admin::NeedsController < ApplicationController
layout "admin"
before_filter :auth_admin
before_action :build_category_tree, only:[:new, :update, :create, :edit]
def index
@ -56,8 +58,13 @@ class Admin::NeedsController < ApplicationController
redirect_to admin_needs_path
end
private
def need_params
params.require(:need).permit(:title, :description)
params.require(:need).permit(:title, :description, :category_id)
end
def build_category_tree
@tree = NeedCategory::create_tree
end
end

View File

@ -8,16 +8,18 @@ class Public::MessagesController < ApplicationController
def create
need = Need.find(params[:need_id])
@need = Need.find(params[:need_id])
@message = need.messages.create(comment_params)
@message = @need.messages.create(comment_params)
@message.customer = current_customer
if(@message.save)
flash[:notice] = "Commentaire envoyé."
else
flash[:error] = "Votre commentaire n'a pas pu être envoyé."
end
redirect_to :back
redirect_to public_need_path(need)
end

View File

@ -9,6 +9,7 @@ class Public::MyAccountController < ApplicationController
.page(params[:page])
.per(5)
@wishes = current_customer.needs.shared.page(params[:page]).per(5)
end

View File

@ -3,6 +3,7 @@ class Public::NeedsController < ApplicationController
layout "public"
before_filter :auth_customer
before_filter :build_category_tree, only:[:index,:new,:create,:edit,:update]
def index
@ -10,14 +11,20 @@ class Public::NeedsController < ApplicationController
@needs = Need.shared
# filters default value
params[:o] ||= 'created-desc'
params[:r] ||= 6
# Include search in the query
if(params[:q] != '')
@needs = @needs.search(params[:q])
end
if(params[:c] != '')
@category = NeedCategory.find(params[:c])
ids = @category.child_ids
@needs = @needs.where(category_id: ids)
end
# Include order in the query
case params[:o]
when 'alpha-asc'
@ -39,21 +46,16 @@ class Public::NeedsController < ApplicationController
end
# Paginate
@needs = @needs.page(params[:page]).per(6)
@needs = @needs.page(params[:page]).per(params[:r])
# Define order select options
@orders = {
"Les plus récents" => 'created-desc',
"Les plus anciens" => 'created-asc',
"Nombre d'intérêts décroissants" => 'wishes-desc',
"Nombre d'intérêts croissants" => 'wishes-asc' ,
"Nombre de commentaires décroissants" => 'comments-desc',
"Nombre de commentaires croissants" => 'comments-asc',
"Les plus populaires" => 'wishes-desc',
"Les plus commentés" => 'comments-desc',
"Alphabétique (de A à Z)" => 'alpha-asc',
"Alphabétique (de Z à A)" => 'alpha-desc'
}
@ -79,7 +81,6 @@ class Public::NeedsController < ApplicationController
@need = Need.find(params[:id])
@comment = Message.new()
@comments = @need.messages.order(created_at: :desc).page params[:page]
end
def update
@ -109,9 +110,7 @@ class Public::NeedsController < ApplicationController
end
end
def need_params
params.require(:need).permit(:title, :description)
end
def wish
@need = Need.find(params[:id])
@ -126,6 +125,14 @@ class Public::NeedsController < ApplicationController
redirect_to :back
end
private
def build_category_tree
@tree = NeedCategory::create_tree
end
def need_params
params.require(:need).permit(:title, :description, :category_id)
end
end

View File

@ -0,0 +1,2 @@
module Admin::NeedCategoriesHelper
end

View File

@ -2,8 +2,6 @@ require 'elasticsearch/model'
class Need < ActiveRecord::Base
include Workflow
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
scope :shared, -> {
where(state: ["verified", "negociating", "negociated", "failed"])
@ -25,13 +23,13 @@ class Need < ActiveRecord::Base
workflow_column :state
acts_as_paranoid
has_many :wishes, dependent: :destroy
has_many :customers, -> { uniq }, through: :wishes
has_many :messages, dependent: :destroy
belongs_to :category, class_name: "NeedCategory"
validates :title, :presence => true, length: {within: 4..128}
validates :description, presence: true, length: {maximum: 65535}
@ -76,4 +74,25 @@ class Need < ActiveRecord::Base
end
end
def categories
collection = []
parent_category = self.category
if(parent_category)
collection << parent_category
parent_category.ancestors.each do |c|
collection << c
end
end
collection
end
def category_path
if !@category_path
@category_path = self.categories.map { |c| c.name }.reverse.join(' \ ')
end
@category_path
end
end

View File

@ -0,0 +1,56 @@
class NeedCategory < ActiveRecord::Base
attr_accessor :level
scope :top, -> {
where(parent_id: nil)
}
belongs_to :parent, class_name: "NeedCategory", foreign_key: :parent_id
has_many :categories, class_name: "NeedCategory", foreign_key: :parent_id, dependent: :destroy
validates :name, :presence => true, length: {within: 1..64}
def level=(level)
@level = level
end
def ancestors
node, ancestors = self, []
ancestors << node = NeedCategory.find(node.parent_id) while node.parent_id?
ancestors
end
def self.create_tree
collection = []
level = 0
top_level_categories = NeedCategory.order(name: :asc).top
self.populate_collection(collection, top_level_categories, level)
collection
end
def self.populate_collection(collection, categories, level)
categories.each do |c|
c.level = level
childs = c.categories.order(name: :asc)
collection << c
self.populate_collection(collection, childs, level + 1)
end
end
def self.populate_ids(ids, categories)
categories.each do |c|
ids << c.id
childs = c.categories
self.populate_ids(ids,childs)
end
end
def child_ids
ids = []
ids << self.id
NeedCategory.populate_ids(ids, self.categories)
ids
end
end

View File

@ -0,0 +1,9 @@
=semantic_form_for [:admin, @category] do |f|
.content
=f.inputs do
=f.input :parent_id, :as => :select, :collection => @tree.map{|c| [(c.level > 0 ? ('&nbsp;&nbsp;&nbsp;' * (c.level - 1)) + "|- ": "").html_safe + c.name, c.id]}, label: "Catégorie parente"
=f.input :name, :label => "Nom de la catégorie : "
.actions= f.submit "Sauvegarder", :class => "btn btn-primary"

View File

@ -0,0 +1,7 @@
%tr{:id => need_category.id}
%td
=(need_category.level > 0 ? ('&nbsp;&nbsp;&nbsp;' * (need_category.level - 1)) + "|- ": "").html_safe + need_category.name
%td.actions{:style => "width:150px;text-align:right"}
= link_to i(:"trash-o"), [:admin, need_category], :data => {:confirm => 'Voulez-vous vraiment supprimer cette catégorie ?'}, :method => :delete
= link_to i(:pencil), edit_admin_need_category_path(need_category)

View File

@ -0,0 +1,2 @@
%h1 Modifier une catégorie
=render :partial => "form"

View File

@ -0,0 +1,16 @@
%h1 Gestion des catégories
%table.table.admin_table.table-hover.table-striped
%thead.rows_header
%tr
%th
Nom
%th{:style => "width:100px"}
&nbsp;
%tbody.rows
=render @tree
= link_to "Créer une nouvelle catégorie", new_admin_need_category_path, class:"btn btn-primary"

View File

@ -0,0 +1,2 @@
%h1 Création d'une catégorie
=render :partial => "admin/need_categories/form"

View File

@ -4,6 +4,7 @@
=f.inputs do
=f.input :title, :label => "Titre : "
=f.input :category, :as => :select, :collection => @tree.map{|c| [(c.level > 0 ? ('&nbsp;&nbsp;&nbsp;' * (c.level - 1)) + "|- ": "").html_safe + c.name, c.id]}, label: "Catégorie de besoin"
=f.input :description, :label => "Description : ", :rows => 5, :input_html => {:style => "height:100px;"}
.actions= f.submit "Sauvegarder", :class => "btn btn-primary"

View File

@ -8,7 +8,7 @@
= csrf_meta_tags
= stylesheet_link_tag :admin, :media => :all
= javascript_include_tag "admin"
=javascript_include_tag "https://maps.google.com/maps/api/js?sensor=false&region=FR"
= javascript_include_tag "https://maps.google.com/maps/api/js?sensor=false&region=FR"
@ -46,6 +46,7 @@
-else
%li= link_to " Gestion des besoins", admin_needs_path
%li= link_to " Gestion des catégories", admin_need_categories_path

View File

@ -45,7 +45,15 @@
#{ current_customer.full_address}
%p
=link_to "Modifier mes infos", public_edit_infos_path, :class => "btn btn-primary"
.padding.center.white
%h3
Ma liste de souhait
-if @wishes.length > 0
=render "public/needs/wishes_index", wishes: @wishes
.pagination= paginate @wishes
-else
%p
Vous n'êtes encore intéressé par aucun besoin
.padding.center.white
%h3
Mes propositions de besoin
@ -62,6 +70,3 @@
%p
Vous n'avez pas encore proposer de besoin
=link_to "Proposer un besoin", new_public_need_path, :class => "btn btn-primary"
.padding.center.white
%h3
Ma liste de souhait

View File

@ -1,6 +1,7 @@
= semantic_form_for [:public, @need] do |f|
=f.inputs do
= f.input :title, :label => "Titre de votre besoin"
=f.input :category, :as => :select, :collection => @tree.map{|c| [(c.level > 0 ? ('&nbsp;&nbsp;&nbsp;' * (c.level - 1)) + "|- ": "").html_safe + c.name, c.id]}, label: "Catégorie de besoin"
= f.input :description, :label => "Description", :rows => 5, :input_html => {:style => "height:100px;"}
%br
=f.submit "Sauvegarder", :class => "btn btn-primary"

View File

@ -9,4 +9,4 @@
&nbsp;
%tbody
=render @needs
=render needs

View File

@ -2,7 +2,7 @@
%tr{:id => need.id, class: css_class}
%td
=need.title
=link_to need.title, public_need_path(need)
%td
=need.human_state
%td.actions{:style => "width:150px;text-align:right"}

View File

@ -4,10 +4,15 @@
%h4
=link_to need.title.upcase, public_need_path(need)
%p.info=i(:"clock-o") + " Ajouté il y a #{time_ago_in_words(need.created_at)} par #{need.author.anonyme_nick}"
%span
.info=i(:"info-circle") + " " + need.human_state
-if need.category
.top-left-info
=i(:"tag") + " " + need.category_path

View File

@ -0,0 +1,12 @@
%tr{:id => wish.id}
%td
=link_to wish.title, public_need_path(wish)
%td
=wish.human_state
%td{style: 'text-align:center' }
=i(:"hand-paper-o") + " " + wish.wishes.length.to_s
%td{style: 'text-align:center'}
=i(:"comment-o") + " " + wish.messages.length.to_s
%td.actions{:style => "width:150px;text-align:right"}
=link_to i(:"remove"), wish_public_need_path(wish),title:"Je ne suis plus intéressé", class: 'btn btn-danger btn-square'

View File

@ -0,0 +1,18 @@
%table.table.public-table.table-striped
%thead
%tr
%th
Titre
%th
État
%th{style: 'text-align:center'}
Intérêts
%th{style: 'text-align:center'}
Commentaires
%th
%tbody
=render partial: "public/needs/wish", collection: @wishes, as: :wish

View File

@ -1,28 +1,39 @@
.center.row
.row.col-md-9.gutter
.center.row.need-container
.row.col-md-9.gutter
=render collection: @needs, partial: 'need_item', as: :need
.clear
-if @needs.num_pages > 1
.pagination= paginate @needs
.row.col-md-3
.white.side-menu
= semantic_form_for :search, :html => {id: :search_form, :method => :get } do |f|
%h4 Recherche
= f.inputs do
= f.input :q, :as => :search, label: false, input_html: {value: params[:q], :name => 'q' }, placeholder: "Rechercher un besoin"
=f.submit "Rechercher", :class => "btn btn-primary pull-right"
.clear
%h4 Ordonner par
= f.inputs do
= f.input :o, as: :order, selected: params[:o], input_html: {:name => 'o' }, label: false, :include_blank => false , :as => :select, :collection => @orders
=f.input :q, :as => :search, label: "Recherche", input_html: {value: params[:q], :name => 'q' }, placeholder: "Rechercher un besoin"
.clear
= f.inputs do
= f.input :o, as: :order, selected: params[:o], input_html: {:name => 'o' }, label: "Ordre d'affichage", :include_blank => false , :as => :select, :collection => @orders
.clear
= f.inputs do
= f.input :r, as: :result, selected: params[:r], input_html: {:name => 'r' }, label: 'Résultats par page', :include_blank => false , :as => :select, :collection => [6,12,24,48]
.clear
= f.inputs do
= f.input :c, as: :category, selected: params[:c], input_html: {:name => 'c' }, label: 'Filtrer par catégorie', :include_blank => true , :as => :select, :collection => @tree.map{|c| [(c.level > 0 ? ('&nbsp;&nbsp;&nbsp;' * (c.level - 1)) + "|- ": "").html_safe + c.name, c.id]}
.clear
:javascript
$('#search_o').change(function(){$('#search_form').submit()})
$('#search_r').change(function(){$('#search_form').submit()})
$('#search_c').change(function(){$('#search_form').submit()})

View File

@ -31,7 +31,7 @@
%h3= i(:"comment-o") + " #{pluralize(@need.messages.count, 'Commentaire')} pour ce besoin"
%h4= i(:"comment-o") + " #{pluralize(@need.messages.count, 'Commentaire')} pour ce besoin"
=render collection: @comments, partial: 'message'

View File

@ -240,6 +240,7 @@ Rails.application.routes.draw do
end
end
resources :need_categories
resources :needs do
member do
get :validate

View File

@ -0,0 +1,10 @@
class CreateNeedCategories < ActiveRecord::Migration
def change
create_table :need_categories do |t|
t.timestamps null: false
t.string :name
t.references :need_categories, :parent, index: true
end
end
end

View File

@ -0,0 +1,5 @@
class AddCategoryToNeed < ActiveRecord::Migration
def change
add_reference :needs, :category, index: true
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151203185210) do
ActiveRecord::Schema.define(version: 20151207162817) do
create_table "admins", force: :cascade do |t|
t.string "name", limit: 255
@ -390,6 +390,17 @@ ActiveRecord::Schema.define(version: 20151203185210) do
t.text "content", limit: 65535
end
create_table "need_categories", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name", limit: 255
t.integer "need_categories_id", limit: 4
t.integer "parent_id", limit: 4
end
add_index "need_categories", ["need_categories_id"], name: "index_need_categories_on_need_categories_id", using: :btree
add_index "need_categories", ["parent_id"], name: "index_need_categories_on_parent_id", using: :btree
create_table "needs", force: :cascade do |t|
t.string "title", limit: 255
t.text "description", limit: 65535
@ -398,9 +409,11 @@ ActiveRecord::Schema.define(version: 20151203185210) do
t.integer "author_id", limit: 4
t.datetime "deleted_at"
t.string "state", limit: 255
t.integer "category_id", limit: 4
end
add_index "needs", ["author_id"], name: "index_needs_on_author_id", using: :btree
add_index "needs", ["category_id"], name: "index_needs_on_category_id", using: :btree
add_index "needs", ["deleted_at"], name: "index_needs_on_deleted_at", using: :btree
add_index "needs", ["title"], name: "index_needs_on_title", using: :btree

View File

@ -0,0 +1,7 @@
require 'test_helper'
class Admin::NeedCategoriesControllerTest < ActionController::TestCase
# test "the truth" do
# assert true
# end
end

11
test/fixtures/need_categories.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
# This model initially had no columns defined. If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
one: {}
# column: value
#
two: {}
# column: value

View File

@ -0,0 +1,7 @@
require 'test_helper'
class NeedCategoryTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end