From aa0821d0aed49bb536d4dea9d0eaa88a5833a0a5 Mon Sep 17 00:00:00 2001 From: Benjamin ter Kuile Date: Thu, 30 Aug 2012 18:32:30 +0200 Subject: [PATCH] implement joining table --- app/assets/images/.DS_Store | Bin 6148 -> 6148 bytes app/assets/images/frames/.DS_Store | Bin 6148 -> 6148 bytes app/assets/images/spinner.gif | Bin 0 -> 723 bytes app/assets/javascripts/qrammer.js.coffee | 3 - app/assets/javascripts/user/application.js | 38 +++++++ .../javascripts/{ => user}/quser.js.coffee | 101 ++++++++++++++---- app/controllers/user_controller.rb | 74 ++++++++++++- app/models/list.rb | 8 ++ app/models/table.rb | 12 +-- app/views/layouts/phone.html.slim | 2 +- app/views/user/join_occupied_table.html.slim | 7 ++ app/views/user/list_products.html.slim | 2 +- .../user/list_products_for_table.html.slim | 2 +- config/locales/en.yml | 8 ++ config/routes.rb | 6 ++ 15 files changed, 228 insertions(+), 35 deletions(-) create mode 100644 app/assets/images/spinner.gif create mode 100644 app/assets/javascripts/user/application.js rename app/assets/javascripts/{ => user}/quser.js.coffee (64%) create mode 100644 app/views/user/join_occupied_table.html.slim diff --git a/app/assets/images/.DS_Store b/app/assets/images/.DS_Store index 1c767137623b445aa828cc2cce535da026e49921..5b14cfb5dcf8ff9cae8e321631c3b645641e385c 100644 GIT binary patch delta 346 zcmZoMXfc=|#>B!ku~2NHo}wTZ0|Nsi1A_nqLn=eDXHI_d#Dvw01(=w1K@w~XX$(aS zi43_g$)ue8B%nA@HG>-vYy1ZT28PKzjIWGY88R7?8S)wOAO<0+J%g+k$cC$&EX8=B z9?oMZE(>T_Yz!2Na$n>3gGQWtW0MKn9=Ya?&5WP7< HWDPR_m0wlQ delta 96 zcmZoMXfc=|#>B`mu~2NHo}wTV0|Nsi1A_nqLn=dYQh9N~#Dvw84MbQbTeCc$+`!(x sS%HI{Wn)7#<7Rdaeh#3%&4L`?nJ4p$SOT?xwSr6l8NsqSLSzjy08%s*wEzGB diff --git a/app/assets/images/frames/.DS_Store b/app/assets/images/frames/.DS_Store index 987a514359dcebe5315af0a1c3ec4682c7fd0c54..71e58fc603ead4df5965ef2ce3a418f0e31be415 100644 GIT binary patch delta 78 zcmZoMXfc=|#>B)qu~2NHo+2ab#DLw41(+BaSts)_uAkh_C@?vhQF!tfM#0Uy86{XZ hvvcrs099{hWctoLnP0?`gOPy&h!_|)2Z(H81^^+`5_kXr delta 66 zcmZoMXfc=|#>B`mu~2NHo+2a5#DLw5ER%Vd)^C2#B*D6|!Gv)$I|n}pP{n3P=I_jt U`9&-_7=VD0fq`jrfXEhR07*a(%m4rY diff --git a/app/assets/images/spinner.gif b/app/assets/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..09d621ede9dbe610877292e554c858d60573359e GIT binary patch literal 723 zcmZ?wbhEHb6ky3UH!ouR`=lAT{vl};VeEIUl!ouRtojadCecHBd+kyoPPMtdS z?%g|}ArPSWpWDwhB-q(8z|~04fSC~}s`yXJxhOTUBsE2$JhLQ2Au%hlA}2pFMK3ci zEuTU0CkrPxP?ZiyKgf{`thx#ceJPpqmMq|Ubc$)6gFv&z8PkHx)%R}in9s-lM6i9! zBZm__dd2Imak;LY=7h}zm4rkI)`WR0GG*E)?mKYn(8@QLmiIn?v-kDx2z`w?tc})! zof1sgG@1de?oQNs+@Zp$B*l}wBx#YxN$K-jdS4q!9hlC1plNUP#}nRyjb|@j-XZKa z^H3#V%=s`!CV{ zGZTv~S)?jgU+dnbH8)5Rn@L8H@VK@q;+ad}wo4-xl69peI z>rMAn>7QZ36tBx*W_EhPz6U`Z*qo#fG3yD?Ju5c7TU8{Z8nlJ?f$93++hq!-bARhP dGOMsv=2x69g9)2+ num = 0.0 if isNaN(num) || num == '' || num == null '€ ' + parseFloat(num).toFixed(2) - clear_active_list: -> - window.active_products_list = {} - $('#active-order-table').hide() load_active_orders: () -> $.get('/supplier/active_orders.json', (res) -> body = $('#active-orders-table tbody') diff --git a/app/assets/javascripts/user/application.js b/app/assets/javascripts/user/application.js new file mode 100644 index 00000000..de94066b --- /dev/null +++ b/app/assets/javascripts/user/application.js @@ -0,0 +1,38 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require jquery +//= require jquery_ujs +//= require jquery-ui +//= require twitter/bootstrap +//= require_directory . +//= require_self +var path_mapping = { + user_root: '/user', + join_occupied_table: '/user/join_occupied_table', + list_products_for_table: '/user/list_products_for_table', + list_products: '/user/list_products' +} +function redirect_to(mapping, variables){ + variables || (variables = {}); + var vars = [] + for(var name in variables){ + vars.push(name + '=' +variables[name]) + } + window.location = path_mapping[mapping] + '?' + vars.join('&') +} +function currency(num) { + if (isNaN(num) || num === '' || num === null) { + num = 0.0; + } + return '€ ' + parseFloat(num).toFixed(2); +} diff --git a/app/assets/javascripts/quser.js.coffee b/app/assets/javascripts/user/quser.js.coffee similarity index 64% rename from app/assets/javascripts/quser.js.coffee rename to app/assets/javascripts/user/quser.js.coffee index c62918df..e373cfb3 100644 --- a/app/assets/javascripts/quser.js.coffee +++ b/app/assets/javascripts/user/quser.js.coffee @@ -9,10 +9,40 @@ window.Quser= Quser.handle_active_list_default_actions(res) ) handle_active_list_default_actions: (response)-> - if typeof(response) == 'object' && response.table_number + response ||= {} + if response.table_number $('.table-number').text(response.table_number) Quser.list_needs_payment_default_action() Quser.list_needs_help_default_action() + # join_request_active is to ensure that there are no more modals loaded when one is still active + console.log('join_request_active=' + window.join_request_active) + if !window.join_request_active && response.join_requests && response.join_requests.length + window.join_request_active = true + for join_request in response.join_requests + wrapper = $('') + join_callback = ( (request)-> + -> + $.post('/user/approve_join_request', {user_id: request.user_id}, -> window.join_request_active = false; wrapper.modal('hide') ) + )(join_request) + reject_callback = ( (request)-> + -> + $.post('/user/reject_join_request', {user_id: request.user_id}, -> window.join_request_active = false; wrapper.modal('hide' )) + )(join_request) + header = $('') + .append('') + .append('

Join request

').appendTo(wrapper) + + body = $('') + body.append(join_request.user_email + ' wants to join the table') + + body.appendTo(wrapper) + + footer = $('') + .append($('Reject').click(reject_callback)) + .append($('Approve').click(join_callback)) + .appendTo(wrapper) + wrapper.modal() + list_needs_help_default_action: -> needs_help_container = $('#list-needs-help-button') if needs_help_container.length @@ -57,10 +87,11 @@ window.Quser= for product in order.products order_txts.push(product.name + ' (' + product['number'] + ')') row.append($('').text(order_txts.join(', '))) - row.append($('').html(Qrammer.currency(order.total_amount))) - foot.append(''+Qrammer.currency(res.total_amount)+'') + row.append($('').html(currency(order.total_amount))) + foot.append(''+currency(res.total_amount)+'') ) order_selected_products: (h)-> + return if $.isEmptyObject(window.active_products_list) h ||= {} for product_id, info of window.active_products_list h['products['+product_id+']'] = info.number @@ -86,10 +117,10 @@ window.Quser= row = $('').attr('id', 'active-order-row-'+product_id).appendTo(tbody) row.append(''+info.product.name+'') row.append(''+info.number+'') - row.append(''+Qrammer.currency(info.product.price * info.number)+'') + row.append(''+currency(info.product.price * info.number)+'') x_btn = $('').click(-> delete(window.active_products_list[product_id]) && Quser.build_product_list() ) row.append($('').append(x_btn)) - $('#active-order-total').html(Qrammer.currency(total)) + $('#active-order-total').html(currency(total)) table.show() load_active_list_products: -> @@ -131,36 +162,68 @@ window.Quser= val_holder.text(val + 1) ) row.append($('').append(order_count_minus).append(' ').append(order_product_count).append(' ').append(order_count_plus)) - row.append(''+Qrammer.currency(product.price)+'') + row.append(''+currency(product.price)+'') row.append($('').append(button)) if include_order_buttons body.append(row) ) actions_for_table: (table)-> table = JSON.parse(table) if typeof(table) == 'string' $.get('/user/table_info.json?table_id='+table.table_id, (res)-> - if res.occupied - alert('Table is occupied') - else + if res.current_table_id if res.other_supplier #TODO cannot do something with other supplier when list is active - else if res.current_table_id && res.current_table_id == table.table_id + else if res.current_table_id == table.table_id #nothing has changed, show product list window.location = '/user/list_products' - else if res.current_table_id && res.current_table_id != table.table_id - #TODO Offer to move table - $.post('/user/move_table', {table_id: table.table_id}, (res2)-> - if res2.occupied - alert('Cannot move to occupied table') - else - window.location = '/user/list_products' - ) + else if res.current_table_id != table.table_id + if res.occupied + redirect_to 'user_root', {message: 'table_is_occupied'} + else if res.reserved + redirect_to 'user_root', {message: 'table_is_reserved'} + else if table.closed + redirect_to 'user_root', {message: 'table_is_closed'} + else if table.supplier_closed + redirect_to 'user_root', {message: 'supplier_is_closed'} + else + #TODO Offer to move table + $.post('/user/move_table', {table_id: table.table_id}, (res2)-> + if res2.occupied + alert('Cannot move to occupied table') + else + window.location = '/user/list_products' + ) + else + if res.occupied + redirect_to 'join_occupied_table', {table_id: table.table_id} else #$.post('/user/create_list.json', {table_id: table.table_id}, (res)-> Quser.handle_response(res)) - window.location = '/user/list_products_for_table?table_id='+table.table_id + redirect_to 'list_products_for_table', {table_id: table.table_id} , 'json') + + join_occupied_table: (table_id) -> + $('.form-actions').remove() + cont = $('#join-occupied-table-progress-container') + cont.html('') + cont.append $('') + cont.append $('

Waiting for approval of the person on this table

') + $.post('/user/join_occupied_table', {table_id: table_id}) + setInterval('Quser.check_if_can_join_occupied_table("'+table_id+'")', 7500) + check_if_can_join_occupied_table: (table_id)-> + $.post('/user/check_table_join_status', {table_id: table_id}, (res) -> + res ||= {} + if res.approved + redirect_to 'list_products' + else if res.waiting + # do nothing, keep waiting.... + else + redirect_to 'user_root', {message: 'join_request_rejected'} + ) add_product: (product, count) -> count ||= 1 window.active_products_list = {} unless window.active_products_list window.active_products_list[product._id] = {product: product, number: 0} unless window.active_products_list[product._id] window.active_products_list[product._id].number += count Quser.build_product_list() + clear_selected_products: -> + window.active_products_list = {} + $('#active-order-table').hide() diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index e761f2bb..ceb218b7 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -1,10 +1,12 @@ class UserController < ApplicationController before_filter :authenticate_user! + layout 'phone' alias :list :active_list def home flash.now[:notice] = t('messages.the_list_has_been_closed', list: List.model_name.human) if params[:list_closed].present? + flash.now[:notice] = t("messages.#{params[:message]}") if params[:message].present? && params[:message] =~ /^\w+$/ render layout: 'phone' end @@ -30,7 +32,7 @@ class UserController < ApplicationController def table_info @table = Table.find(params[:table_id]) res = {} - res[:ocupied] = @table.occupied? + res[:occupied] = @table.occupied? res[:reserved] = @table.reserved? res[:supplier_closed] = @table.supplier.closed? if list.present? @@ -62,6 +64,62 @@ class UserController < ApplicationController end end + def join_occupied_table + @table = Table.find(params[:table_id]) + end + + def request_to_join_occupied_table + @table = Table.find(params[:table_id]) + if @list = @table.active_list + unless @list.join_requests.include?(current_user.id) + @list.join_requests << current_user.id + @list.is_dirty + @list.save + end + + end + render nothing: true + end + + def reject_join_request + return unless params[:user_id] + if list && list.join_requests.include?(params[:user_id]) + list.join_requests.delete(params[:user_id]) + list.is_dirty + list.save + end + render js: '' + end + def approve_join_request + return unless params[:user_id] + @user = User.find(params[:user_id]) + if list && list.join_requests.include?(params[:user_id]) + list.join_requests.delete(params[:user_id]) + @user.active_list_id = list.id + list.add_user(@user) + @user.save + list.is_dirty + list.save + end + render nothing: true + end + + # POST /user/check_table_join_status table_id:12345 + def check_table_join_status + @table = Table.find(params[:table_id]) + if @list = @table.active_list + if @list.user_ids.include?(current_user.id) + render json: {approved: true} + elsif @list.join_requests.include?(current_user.id) + render json: {waiting: true} + else + render json: {rejected: true} + end + else + render json: {rejected: true} + end + end + def list_products_for_table @table = Table.find(params[:table_id]) respond_to do |format| @@ -115,6 +173,7 @@ class UserController < ApplicationController # GET /user/list_info.json # Information about the currently active list # Fast version to verify wether the is is still currently active + # for handle_active_list def list_info respond_to do |format| format.json do @@ -122,8 +181,17 @@ class UserController < ApplicationController current_user.list_is_closed! render json: {list_active: false} return - else - render json: list.as_json.merge(list_active: list.active? ) + else + list_obj = list.as_json.merge(list_active: list.active? ) + + # Handle join requests + if list.join_requests.any? + list_obj[:join_requests] = [] + for user in CouchPotato.database.load_document(list.join_requests) + list_obj[:join_requests] << {user_id: user.id, user_email: user.email} + end + end + render json: list_obj end end end diff --git a/app/models/list.rb b/app/models/list.rb index bb3fe32d..cba92324 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -5,6 +5,8 @@ class List property :needs_help, type: :boolean, default: false property :needs_payment, type: :boolean, default: false property :closed_at, type: Time + property :join_requests, type: Array, default: [] + has_many :orders, dependent: :destroy belongs_to :table belongs_to :supplier @@ -16,6 +18,12 @@ class List view :by_supplier_id_and_id, key: [:supplier_id, :_id] + view :active_by_table_id, type: :custom, map_function: %|function(doc){ + if(doc.ruby_class == 'List' && doc.state == 'active'){ + emit(doc.table_id, 1); + } + }|, reduce_function: '_sum' + def self.from_table table, user return if user.has_active_list? list = new table: table, supplier_id: table.supplier_id, section_id: table.section_id diff --git a/app/models/table.rb b/app/models/table.rb index 4a9e48a2..8a7a4fe4 100644 --- a/app/models/table.rb +++ b/app/models/table.rb @@ -16,15 +16,13 @@ class Table validates :number, numericality: {greater_than: 0} validates_uniqueness_of :number - view :active_lists, type: :custom, map_function: %|function(doc){ - if(doc.ruby_class == 'List' && doc.state == 'active'){ - emit(doc.table_id, 1); - } - }|, reduce_function: '_sum' - def occupied? return @is_occupied if instance_variable_defined?(:'@is_occupied') - @is_occupied = !self.class.database.view(self.class.active_lists(key: id, reduce: true)).zero? + @is_occupied = !self.class.database.view(List.active_by_table_id(key: id, reduce: true)).zero? + end + + def active_list + @active_list ||= self.class.database.view(List.active_by_table_id(key: id, include_docs: true, reduce: false, limit: 1)).try(:first) end def reserved? diff --git a/app/views/layouts/phone.html.slim b/app/views/layouts/phone.html.slim index cfc9e34a..d0de1195 100644 --- a/app/views/layouts/phone.html.slim +++ b/app/views/layouts/phone.html.slim @@ -56,5 +56,5 @@ html lang="en" Javascripts \================================================== /! Placed at the end of the document so the pages load faster - = javascript_include_tag "application" + = javascript_include_tag "user/application" = yield :footer diff --git a/app/views/user/join_occupied_table.html.slim b/app/views/user/join_occupied_table.html.slim new file mode 100644 index 00000000..1ddeaafe --- /dev/null +++ b/app/views/user/join_occupied_table.html.slim @@ -0,0 +1,7 @@ +.page-header + h4= t('user.join_occupied_table.title') +.form-actions + = link_to t('user.join_occupied_table.show_the_products'), user_list_products_for_table_path(table_id: @table.id), class: [:btn, 'btn-primary'] + ' + button.btn.btn-warning{onClick="Quser.join_occupied_table('#{@table.id}')"} = t('user.join_occupied_table.join_this_table') +#join-occupied-table-progress-container diff --git a/app/views/user/list_products.html.slim b/app/views/user/list_products.html.slim index 388541af..1c0f9a8b 100644 --- a/app/views/user/list_products.html.slim +++ b/app/views/user/list_products.html.slim @@ -18,7 +18,7 @@ table#active-order-table.table.table-striped.hide td colspan=2 button class="btn btn-primary" onClick="Quser.handle_active_list(function(){Quser.order_selected_products()})" Bestellen |  - button class="btn btn btn-warning" onClick="Qrammer.clear_active_list()" Clear + button class="btn btn btn-warning" onClick="Quser.clear_selected_products()" Clear td.currency strong#active-order-total td diff --git a/app/views/user/list_products_for_table.html.slim b/app/views/user/list_products_for_table.html.slim index 092cbef1..487558b4 100644 --- a/app/views/user/list_products_for_table.html.slim +++ b/app/views/user/list_products_for_table.html.slim @@ -16,7 +16,7 @@ table#active-order-table.table.table-striped.hide td colspan=2 button class="btn btn-primary" onClick="Quser.order_selected_products({table_id: '#{@table.id}'})" Bestellen |  - button class="btn btn btn-warning" onClick="Qrammer.clear_active_list()" Clear + button class="btn btn btn-warning" onClick="Quser.clear_selected_products()" Clear td.currency strong#active-order-total td diff --git a/config/locales/en.yml b/config/locales/en.yml index 9689e058..4e30997d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -16,6 +16,10 @@ en: the_list_has_been_closed: The %{list} has been closed illegal_history_list_attempt: The list you want to access is not yours table_is_occupied: The table you want to sit on is already occupied + table_is_reserved: The table you want to sit on is reserved by someone else + table_is_closed: The table you want to sit on is not available for service + supplier_is_closed: The owner of this table is currently not handling orders + join_request_rejected: Your request to join the table has been rejected action: index: label: Listing %{models} @@ -71,5 +75,9 @@ en: show_products: # The title gets products: Product.model_name.human_plural that can be used: e.g.: Showing %{products} title: Menu + join_occupied_table: + title: This table is occupied + join_this_table: Join this table + show_the_products: Show me the menu section: first_section_title: Room diff --git a/config/routes.rb b/config/routes.rb index b200c8de..4a95728a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -48,6 +48,12 @@ Qrammer::Application.routes.draw do post '/user/order_selected_products' => 'user#order_selected_products', as: :user_order_selected_products post '/user/move_table' => 'user#move_table', as: :user_move_table get '/user/table_info' => 'user#table_info', as: :user_table_info + get '/user/join_occupied_table' => 'user#join_occupied_table', as: :user_join_occupied_table + post '/user/join_occupied_table' => 'user#request_to_join_occupied_table' + post '/user/reject_join_request' => 'user#reject_join_request' + post '/user/approve_join_request' => 'user#approve_join_request' + post '/user/check_table_join_status' => 'user#check_table_join_status' + match '/show_products' => 'dashboard#show_products', as: :user_products