diff --git a/back-office/app/models/brand.rb b/back-office/app/models/brand.rb new file mode 100644 index 0000000..c7ebc71 --- /dev/null +++ b/back-office/app/models/brand.rb @@ -0,0 +1,3 @@ +class Brand < ActiveRecord::Base + has_many :items +end diff --git a/back-office/app/models/item.rb b/back-office/app/models/item.rb index 016389e..a59816b 100644 --- a/back-office/app/models/item.rb +++ b/back-office/app/models/item.rb @@ -3,6 +3,7 @@ class Item < ActiveRecord::Base has_many :multi_media_descriptions belongs_to :sub_category belongs_to :supplier + belongs_to :brand has_and_belongs_to_many :item_groups, :join_table => 'item_item_groups' validates_presence_of :name, :description, :list_price, :current_input_price, :tags, :unit_id, :code, :sub_category_id, :weight, :supplier_id diff --git a/front-api/controllers/item.rb b/front-api/controllers/item.rb index d491405..4cdfc54 100644 --- a/front-api/controllers/item.rb +++ b/front-api/controllers/item.rb @@ -4,7 +4,8 @@ def prepare_items_for_mass_display(items) :include => [ :unit , :multi_media_descriptions , - :sub_category + :sub_category, + :brand ]) end @@ -94,4 +95,4 @@ get '/item/item_group/:item_group_id/offset/:offset/limit/:limit' do |item_group items = ItemGroup.find(item_group_id).all_items(offset, limit) prepare_items_for_mass_display(items) -end \ No newline at end of file +end diff --git a/front-api/db/migrate/20150328132019_create_brands.rb b/front-api/db/migrate/20150328132019_create_brands.rb new file mode 100644 index 0000000..200961b --- /dev/null +++ b/front-api/db/migrate/20150328132019_create_brands.rb @@ -0,0 +1,9 @@ +class CreateBrands < ActiveRecord::Migration + def change + create_table :brands do |t| + t.string :name + end + + add_column :items, :brand_id, :integer + end +end diff --git a/front-api/db/schema.rb b/front-api/db/schema.rb index 59ad6a6..f4a1995 100644 --- a/front-api/db/schema.rb +++ b/front-api/db/schema.rb @@ -11,11 +11,15 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150324044314) do +ActiveRecord::Schema.define(version: 20150328132019) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "brands", force: :cascade do |t| + t.string "name" + end + create_table "carts", force: :cascade do |t| t.integer "user_id" t.boolean "ordered", default: false @@ -125,6 +129,7 @@ ActiveRecord::Schema.define(version: 20150324044314) do t.decimal "weight", precision: 5, scale: 3 t.integer "supplier_id" t.integer "delivery_time_estimation_id" + t.integer "brand_id" end create_table "link_banners", force: :cascade do |t| diff --git a/front-api/models/brand.rb b/front-api/models/brand.rb new file mode 100644 index 0000000..c7ebc71 --- /dev/null +++ b/front-api/models/brand.rb @@ -0,0 +1,3 @@ +class Brand < ActiveRecord::Base + has_many :items +end diff --git a/front-api/models/item.rb b/front-api/models/item.rb index 25a6904..dbba0c2 100644 --- a/front-api/models/item.rb +++ b/front-api/models/item.rb @@ -1,5 +1,6 @@ class Item < ActiveRecord::Base belongs_to :unit + belongs_to :brand has_many :multi_media_descriptions belongs_to :sub_category diff --git a/front-ui/app/actions/menuItemActions.js b/front-ui/app/actions/menuItemActions.js new file mode 100644 index 0000000..d07768a --- /dev/null +++ b/front-ui/app/actions/menuItemActions.js @@ -0,0 +1,24 @@ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var MenuItemConstants = require('../constants/menuItemConstants'); + +// Define action methods +var MenuItemActions = { + loadMenuItems: function() { + AppDispatcher.handleAction({ + actionType: MenuItemConstants.LOAD_MENU_ITEMS, + }) + }, + setMenuItemHover: function(menuItem) { + AppDispatcher.handleAction({ + actionType: MenuItemConstants.SET_MENU_ITEM_HOVER, + menuItem: menuItem + }); + }, + unsetMenuItemHover: function() { + AppDispatcher.handleAction({ + actionType: MenuItemConstants.UNSET_MENU_ITEM_HOVER + }); + } +}; + +module.exports = MenuItemActions; diff --git a/front-ui/app/actions/navigationActions.js b/front-ui/app/actions/navigationActions.js index 17a7d4a..be3c001 100644 --- a/front-ui/app/actions/navigationActions.js +++ b/front-ui/app/actions/navigationActions.js @@ -91,6 +91,19 @@ var NavigationActions = { actionType: NavigationConstants.CHANGE_URL, url: '/pretraga?q=' + q }); + }, + goToMenuItem: function(menuItem) { + var url = ''; + if (menuItem.get) { + url = menuItem.get('url'); + } else { + url = menuItem.url; + } + + AppDispatcher.handleAction({ + actionType: NavigationConstants.CHANGE_URL, + url: url + }); } }; diff --git a/front-ui/app/components/account/login.js b/front-ui/app/components/account/login.js index d3335a4..fd72fa8 100644 --- a/front-ui/app/components/account/login.js +++ b/front-ui/app/components/account/login.js @@ -52,8 +52,11 @@ var Login = React.createClass({ renderErrorMessage: function(message){ return (
{message}
) }, - doLogin: function(e) { - + onLoginClick: function(e) { + this.doLogin(); + e.preventDefault(); + }, + doLogin: function() { if(this.validate()) { var loginInfo = new LoginModel({ email: this.state.email, @@ -61,10 +64,8 @@ var Login = React.createClass({ }); UserActions.userLogin(loginInfo); - - } - e.preventDefault(); + } }, renderLoginFailure: function() { @@ -75,6 +76,12 @@ var Login = React.createClass({ return (
) }, + onKeyPress: function(e) { + var enterKeyCode = 13; + if(e.which == enterKeyCode) { + this.doLogin(); + } + }, render : function() { return (
@@ -86,18 +93,18 @@ var Login = React.createClass({
- + {this.getValidationMessages('email').map(this.renderErrorMessage)}
- + {this.getValidationMessages('password').map(this.renderErrorMessage)}
- +
diff --git a/front-ui/app/components/account/register.js b/front-ui/app/components/account/register.js index adc364e..8b809db 100644 --- a/front-ui/app/components/account/register.js +++ b/front-ui/app/components/account/register.js @@ -32,6 +32,12 @@ var Register = React.createClass({ myBabyOnTheWay: (e.currentTarget.value === "1" ? true: false) }); }, + onKeyPress: function(e) { + var enterKeyCode = 13; + if(e.which == enterKeyCode) { + this.doRegister(); + } + }, renderMonthSelector: function() { var months = ['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Juni', 'Juli','August','Septembar', 'Oktobar','Novembar','Decembar']; @@ -194,7 +200,11 @@ var Register = React.createClass({ loginState : UserStore.getLoginState() }); }, - register: function(e) { + onRegisterClick: function(e) { + this.doRegister(); + e.preventDefault(); + }, + doRegister: function() { if(this.validate()) { var children = []; if (this.state.myBabyDetailsVisible) { @@ -231,8 +241,6 @@ var Register = React.createClass({ UserActions.registerUser(user); } - - e.preventDefault(); }, successContinue: function(e) { NavigationActions.goToHome(); @@ -284,7 +292,7 @@ var Register = React.createClass({
- + {this.getValidationMessages('firstName').map(this.renderErrorMessage)}
@@ -292,28 +300,28 @@ var Register = React.createClass({
- + {this.getValidationMessages('lastName').map(this.renderErrorMessage)}
- + {this.getValidationMessages('email').map(this.renderErrorMessage)}
- + {this.getValidationMessages('password').map(this.renderErrorMessage)}
- + {this.getValidationMessages('passwordConfirmation').map(this.renderErrorMessage)} @@ -335,7 +343,7 @@ var Register = React.createClass({
-
diff --git a/front-ui/app/components/items/itemWithDetailsPage.js b/front-ui/app/components/items/itemWithDetailsPage.js index 3361fc5..63ef7f1 100644 --- a/front-ui/app/components/items/itemWithDetailsPage.js +++ b/front-ui/app/components/items/itemWithDetailsPage.js @@ -29,6 +29,7 @@ var ItemWithDetailsPage = React.createClass({

{this.state.item.get('name')}

+
{this.state.item.get('brand').name}
{this.state.item.get('list_price')} KM
{this.state.item.get('pricePerUnit')}
{this.state.item.get('description')}
diff --git a/front-ui/app/components/items/singleItem.js b/front-ui/app/components/items/singleItem.js index 4a56217..0aeb1bb 100644 --- a/front-ui/app/components/items/singleItem.js +++ b/front-ui/app/components/items/singleItem.js @@ -25,6 +25,7 @@ var SingleItem = React.createClass({

{ this.props.item.get('name') }

+
{ this.props.item.get('brand')? this.props.item.get('brand').name : '' }
{ this.props.item.get('list_price') } KM
); diff --git a/front-ui/app/components/rootApp.js b/front-ui/app/components/rootApp.js index f3d8ae2..a75328b 100644 --- a/front-ui/app/components/rootApp.js +++ b/front-ui/app/components/rootApp.js @@ -1,5 +1,5 @@ var React = require('react'), - SectionsListComponent = require('./shared/sectionsListComponent'), + MenuItemListComponent = require('./shared/menuItemListComponent'), Router = require('react-router'), Link = Router.Link, RouteHandler = Router.RouteHandler, @@ -57,7 +57,7 @@ var RootApp = React.createClass({
diff --git a/front-ui/app/components/shared/menuItemListComponent.js b/front-ui/app/components/shared/menuItemListComponent.js new file mode 100644 index 0000000..43d421b --- /dev/null +++ b/front-ui/app/components/shared/menuItemListComponent.js @@ -0,0 +1,93 @@ +var React = require('react'), + MenuItemCollection = require('../../models/menuItemCollection'), + MenuItem = require('../../models/menuItem'), + Backbone = require('backbone'), + NavigationStore = require('../../stores/navigationStore'), + MenuItemStore = require('../../stores/menuItemStore'), + MenuItemActions = require('../../actions/menuItemActions'), + NavigationActions = require('../../actions/navigationActions'); + +Backbone.$ = $; + +var MenuItemListComponent = React.createClass({ + + _onChange: function () { + if (this.isMounted()) { + this.setState(MenuItemStore.getState()); + } + }, + + getInitialState: function() { + return MenuItemStore.getState(); + }, + + componentDidMount: function() { + MenuItemStore.addChangeListener(this._onChange); + MenuItemActions.loadMenuItems(); + }, + onMouseOver: function(menuItem) { + MenuItemActions.setMenuItemHover(menuItem); + }, + onMouseOut: function() { + MenuItemActions.unsetMenuItemHover(); + }, + onMouseLeave: function() { + MenuItemActions.unsetMenuItemHover(); + }, + onMenuItemClick: function(menuItem) { + MenuItemActions.unsetMenuItemHover(); + NavigationActions.goToMenuItem(menuItem); + event.preventDefault(); + }, + //onCategoryClick: function(category, section) { + //MenuItemActions.unsetSectionHover(); + //NavigationActions.goToCategory(new Category(category), section); + //event.preventDefault(); + //}, + //onSubcategoryClick: function(subcategory) { + //// implement in navigation actions + //// and call + //// when ready + //return false; + //}, + render: function() { + var self = this; + var style = { + position: 'relative' + }; + var abStyle = { + position: 'absolute' + }; + return ( +
+ +
+ ); + } +}); + +module.exports = MenuItemListComponent; diff --git a/front-ui/app/constants/menuItemConstants.js b/front-ui/app/constants/menuItemConstants.js new file mode 100644 index 0000000..72ac8bc --- /dev/null +++ b/front-ui/app/constants/menuItemConstants.js @@ -0,0 +1,8 @@ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD_MENU_ITEMS: null, + SET_MENU_ITEM_HOVER: null, + UNSET_MENU_ITEM_HOVER: null +}); diff --git a/front-ui/app/models/item.js b/front-ui/app/models/item.js index 7534dfe..861df83 100644 --- a/front-ui/app/models/item.js +++ b/front-ui/app/models/item.js @@ -2,8 +2,10 @@ var Backbone = require('backbone'); var Globals = require('../globals'); var Item = Backbone.Model.extend({ - urlRoot : Globals.ApiUrl + '/item', - + urlRoot : Globals.ApiUrl + '/item', + defaults : { + brand: {} + } diff --git a/front-ui/app/models/itemWithDetails.js b/front-ui/app/models/itemWithDetails.js index 8840940..56648fa 100644 --- a/front-ui/app/models/itemWithDetails.js +++ b/front-ui/app/models/itemWithDetails.js @@ -15,6 +15,9 @@ var ItemWithDetails = Backbone.Model.extend({ var descriptionSuffix = this.get('unit').description_suffix; return (+pricePerUnit).toString() + " KM " + descriptionSuffix; } + }, + defaults : { + brand: {} } }); diff --git a/front-ui/app/models/menuItemCollection.js b/front-ui/app/models/menuItemCollection.js index 674eb36..bd66009 100644 --- a/front-ui/app/models/menuItemCollection.js +++ b/front-ui/app/models/menuItemCollection.js @@ -4,7 +4,7 @@ var Backbone = require('backbone'), var MenuItemCollection = Backbone.Collection.extend({ model: MenuItem, - url: Globals.ApiUrl + '/menu_item' + url: Globals.ApiUrl + '/menuitem' }); module.exports = MenuItemCollection; diff --git a/front-ui/app/stores/menuItemStore.js b/front-ui/app/stores/menuItemStore.js new file mode 100644 index 0000000..252bc00 --- /dev/null +++ b/front-ui/app/stores/menuItemStore.js @@ -0,0 +1,86 @@ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var MenuItemCollection = require('../models/menuItemCollection'); +var MenuItem = require('../models/menuItem'); +var MenuItemConstants = require('../constants/menuItemConstants'); +var _ = require('underscore'); + +var menuItemState = { + menuItems : [], + hoveredMenuItem : '' +}; + + +var loadMenuItems = function() { + var menuItems = new MenuItemCollection(); + menuItems.fetch({success: function() { + menuItemState.menuItems = menuItems.models; + // change will be called automatically when + // action is run but we need to emit it again + // when the data arive + // it's a bit "unfluxy" but convenient. + // "true philosophy" would be to run another "data arrived" action + MenuItemStore.emitChange(); + }}); +}; + +var setHovered = function(id) { + menuItemState.hoveredMenuItem = id; +} + + +// Extend MenuItemStore with EventEmitter to add eventing capabilities +var MenuItemStore = _.extend({}, EventEmitter.prototype, { + + // Return Single Item With Details + getState: function() { + return menuItemState; + }, + // Emit Change event + emitChange: function() { + console.log("Emmiting MenuItemStore change!"); + this.emit('change'); + }, + + // Add change listener + addChangeListener: function(callback) { + this.on('change', callback); + }, + + // Remove change listener + removeChangeListener: function(callback) { + this.removeListener('change', callback); + } +}); + + +// Register callback with AppDispatcher +AppDispatcher.register(function(payload) { + var action = payload.action; + var text; + + switch(action.actionType) { + + // Respond to SELECT_ITEM action + case MenuItemConstants.LOAD_MENU_ITEMS: + loadMenuItems(); + break; + + case MenuItemConstants.SET_MENU_ITEM_HOVER: + setHovered(action.menuItem.get('id')); + break; + + case MenuItemConstants.UNSET_MENU_ITEM_HOVER: + setHovered(''); + break; + default: + return true; + } + + // If action was responded to, emit change event + MenuItemStore.emitChange(); + return true; + +}); + +module.exports = MenuItemStore;