1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

Merge branch 'events' into dev

Conflicts:
	CHANGELOG.md
	config/locales/app.shared.en.yml
	config/locales/app.shared.fr.yml
This commit is contained in:
Sylvain 2016-07-13 18:32:30 +02:00
commit 6839e8db38
76 changed files with 1131 additions and 218 deletions

View File

@ -10,6 +10,11 @@
- Public gallery of trainings with ability to view details or to book a training on its own calendar
- Ability to switch back to all trainings booking view
- Fix a bug: project drafts are shown on public profiles
- Admin: Events can be associated with a theme and an age range
- Admin: Event categories, themes and age ranges can be customized
- Filter events by category, theme and age range in public view
- Statistics will include informations abouts events category, theme and age range
- [TODO DEPLOY] `rake fablab:es_add_event_filters`
- [TODO DEPLOY] `rake db:migrate`
## v2.3.0 2016 June 28
@ -27,7 +32,7 @@
## v2.2.2 2016 June 23
- Fix some bugs: users with uncompleted account (sso imported) won't appear in statistics, in listings and in searches. Moreover, they won't block statistics generation
- Fix a bug: unable to display next results in statistics tables
- Admin: Category is mandatory when creating a course/workshop (event)
- Admin: Category is mandatory when creating an event
## v2.2.1 2016 June 22
- Fix a bug: field User.merged_at should not be allowed to be mapped in SSO

View File

@ -7,7 +7,6 @@
# in the various events' admin controllers.
#
# Provides :
# - $scope.categories = [{Category}]
# - $scope.datePicker = {}
# - $scope.submited(content)
# - $scope.cancel()
@ -23,13 +22,7 @@
# - $state (Ui-Router) [ 'app.public.events_list' ]
##
class EventsController
constructor: ($scope, $state, Event, Category) ->
## Retrieve the list of categories from the server (stage, atelier, ...)
Category.query().$promise.then (data)->
$scope.categories = data.map (d) ->
id: d.id
name: d.name
constructor: ($scope, $state) ->
## default parameters for AngularUI-Bootstrap datepicker
$scope.datePicker =
@ -136,7 +129,8 @@ class EventsController
##
# Controller used in the events listing page (admin view)
##
Application.Controllers.controller "AdminEventsController", ["$scope", "$state", 'Event', 'eventsPromise', ($scope, $state, Event, eventsPromise) ->
Application.Controllers.controller "AdminEventsController", ["$scope", "$state", 'dialogs', 'growl', 'Event', 'Category', 'EventTheme', 'AgeRange', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', '_t'
, ($scope, $state, dialogs, growl, Event, Category, EventTheme, AgeRange, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, _t) ->
@ -151,6 +145,21 @@ Application.Controllers.controller "AdminEventsController", ["$scope", "$state",
## Current virtual page
$scope.page = 2
## Temporary datastore for creating new elements
$scope.inserted =
category: null
theme: null
age_range: null
## List of categories for the events
$scope.categories = categoriesPromise
## List of events themes
$scope.themes = themesPromise
## List of age ranges
$scope.ageRanges = ageRangesPromise
##
# Adds a bucket of events to the bottom of the page, grouped by month
##
@ -161,6 +170,71 @@ Application.Controllers.controller "AdminEventsController", ["$scope", "$state",
$scope.page += 1
##
# Saves a new element / Update an existing one to the server (form validation callback)
# @param model {string} model name
# @param data {Object} element name
# @param [id] {number} element id, in case of update
##
$scope.saveElement = (model, data, id) ->
if id?
getModel(model)[0].update {id: id}, data
else
getModel(model)[0].save data, (resp)->
getModel(model)[1][getModel(model)[1].length-1].id = resp.id
##
# Deletes the element at the specified index
# @param model {string} model name
# @param index {number} element index in the $scope[model] array
##
$scope.removeElement = (model, index) ->
if model == 'category' and getModel(model)[1].length == 1
growl.error(_t('at_least_one_category_is_required')+' '+_t('unable_to_delete_the_last_one'))
return false
if getModel(model)[1][index].related_to > 0
growl.error(_t('unable_to_delete_ELEMENT_already_in_use_NUMBER_times', {ELEMENT:model, NUMBER:getModel(model)[1][index].related_to}, "messageformat"))
return false
dialogs.confirm
resolve:
object: ->
title: _t('confirmation_required')
msg: _t('do_you_really_want_to_delete_this_ELEMENT', {ELEMENT:model}, "messageformat")
, -> # delete confirmed
getModel(model)[0].delete getModel(model)[1][index], null, ->
getModel(model)[1].splice(index, 1)
, ->
growl.error(_t('unable_to_delete_an_error_occured'))
##
# Creates a new empty entry in the $scope[model] array
# @param model {string} model name
##
$scope.addElement = (model) ->
$scope.inserted[model] =
name: ''
related_to: 0
getModel(model)[1].push($scope.inserted[model])
##
# Removes the newly inserted but not saved element / Cancel the current element modification
# @param model {string} model name
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
# @param index {number} element index in the $scope[model] array
##
$scope.cancelElement = (model, rowform, index) ->
if getModel(model)[1][index].id?
rowform.$cancel()
else
getModel(model)[1].splice(index, 1)
### PRIVATE SCOPE ###
@ -183,6 +257,17 @@ Application.Controllers.controller "AdminEventsController", ["$scope", "$state",
else
$scope.paginateActive = false
##
# Return the model and the datastore matching the given name
# @param name {string} 'category', 'theme' or 'age_range'
# @return {[Object, Array]} model and datastore
##
getModel = (name) ->
switch name
when 'category' then [Category, $scope.categories]
when 'theme' then [EventTheme, $scope.themes]
when 'age_range' then [AgeRange, $scope.ageRanges]
else [null, []]
# init the controller (call at the end !)
@ -209,8 +294,8 @@ Application.Controllers.controller "ShowEventReservationsController", ["$scope",
##
# Controller used in the event creation page
##
Application.Controllers.controller "NewEventController", ["$scope", "$state", "$locale", 'Event', 'Category', 'CSRF', '_t'
, ($scope, $state, $locale, Event, Category, CSRF, _t) ->
Application.Controllers.controller "NewEventController", ["$scope", "$state", "$locale", 'CSRF', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', '_t'
, ($scope, $state, $locale, CSRF, categoriesPromise, themesPromise, ageRangesPromise, _t) ->
CSRF.setMetaTags()
## API URL where the form will be posted
@ -219,6 +304,15 @@ Application.Controllers.controller "NewEventController", ["$scope", "$state", "$
## Form action on the above URL
$scope.method = 'post'
## List of categories for the events
$scope.categories = categoriesPromise
## List of events themes
$scope.themes = themesPromise
## List of age ranges
$scope.ageRanges = ageRangesPromise
## Default event parameters
$scope.event =
event_files_attributes: []
@ -243,7 +337,7 @@ Application.Controllers.controller "NewEventController", ["$scope", "$state", "$
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
## Using the EventsController
new EventsController($scope, $state, Event, Category)
new EventsController($scope, $state)
]
@ -251,8 +345,8 @@ Application.Controllers.controller "NewEventController", ["$scope", "$state", "$
##
# Controller used in the events edition page
##
Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", "$locale", 'Event', 'Category', 'CSRF', 'eventPromise'
, ($scope, $state, $stateParams, $locale, Event, Category, CSRF, eventPromise) ->
Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", "$locale", 'CSRF', 'eventPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise'
, ($scope, $state, $stateParams, $locale, CSRF, eventPromise, categoriesPromise, themesPromise, ageRangesPromise) ->
### PUBLIC SCOPE ###
@ -270,6 +364,15 @@ Application.Controllers.controller "EditEventController", ["$scope", "$state", "
## currency symbol for the current locale (cf. angular-i18n)
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
## List of categories for the events
$scope.categories = categoriesPromise
## List of events themes
$scope.themes = themesPromise
## List of age ranges
$scope.ageRanges = ageRangesPromise
### PRIVATE SCOPE ###
@ -287,7 +390,7 @@ Application.Controllers.controller "EditEventController", ["$scope", "$state", "
$scope.event.end_date = moment($scope.event.end_date).toDate()
## Using the EventsController
new EventsController($scope, $state, Event, Category)
new EventsController($scope, $state)

View File

@ -334,7 +334,7 @@ Application.Controllers.controller "GraphsController", ["$scope", "$state", "$ro
callback(results)
recursiveCb()
else # palmares (ranking)
queryElasticRanking index.es_type_key, $scope.ranking.groupCriterion, $scope.ranking.sortCriterion, index.graph.limit, (results, error) ->
queryElasticRanking index.es_type_key, $scope.ranking.groupCriterion, $scope.ranking.sortCriterion, (results, error) ->
if (error)
callback([], error)
else
@ -373,17 +373,18 @@ Application.Controllers.controller "GraphsController", ["$scope", "$state", "$ro
##
# For ranking displays, run the elasticSearch query to retreive the /stats/type aggregations
# @param esType {String} elasticSearch document type (subscription|machine|training|...)
# @param statType {String} statistics type (year|month|hour|booking|...)
# @param esType {string} elasticSearch document type (subscription|machine|training|...)
# @param groupKey {string} statistics subtype or custom field
# @param sortKey {string} statistics type or 'ca'
# @param callback {function} function be to run after results were retrieved,
# it will receive two parameters : results {Array}, error {String} (if any)
##
queryElasticRanking = (esType, groupKey, sortKey, limit, callback) ->
queryElasticRanking = (esType, groupKey, sortKey, callback) ->
# handle invalid callback
if typeof(callback) != "function"
console.error('[graphsController::queryElasticRanking] Error: invalid callback provided')
return
if !esType or !groupKey or !sortKey or typeof limit != 'number'
if !esType or !groupKey or !sortKey
callback([], '[graphsController::queryElasticRanking] Error: invalid parameters provided')
# run query

View File

@ -1,13 +1,7 @@
'use strict'
Application.Controllers.controller "EventsController", ["$scope", "$state", 'Event', ($scope, $state, Event) ->
### PRIVATE STATIC CONSTANTS ###
# Number of events added to the page when the user clicks on 'load next events'
EVENTS_PER_PAGE = 12
Application.Controllers.controller "EventsController", ["$scope", "$state", 'Event', 'categoriesPromise', 'themesPromise', 'ageRangesPromise'
, ($scope, $state, Event, categoriesPromise, themesPromise, ageRangesPromise) ->
@ -16,33 +10,40 @@ Application.Controllers.controller "EventsController", ["$scope", "$state", 'Eve
## The events displayed on the page
$scope.events = []
## By default, the pagination mode is activated to limit the page size
$scope.paginateActive = true
## The currently displayed page number
$scope.page = 1
## List of categories for the events
$scope.categories = categoriesPromise
## List of events themes
$scope.themes = themesPromise
## List of age ranges
$scope.ageRanges = ageRangesPromise
## Hide or show the 'load more' button
$scope.noMoreResults = false
## Active filters for the events list
$scope.filters =
category_id: null
theme_id: null
age_range_id: null
##
# Adds EVENTS_PER_PAGE events to the bottom of the page, grouped by month
# Adds a resultset of events to the bottom of the page, grouped by month
##
$scope.loadMoreEvents = ->
Event.query {page: $scope.page}, (data) ->
Event.query Object.assign({page: $scope.page}, $scope.filters), (data) ->
$scope.events = $scope.events.concat data
if data.length > 0
$scope.paginateActive = false if ($scope.page-2)*EVENTS_PER_PAGE+data.length >= data[0].nb_total_events
groupEvents($scope.events)
$scope.page += 1
$scope.eventsGroupByMonth = _.groupBy($scope.events, (obj) ->
_.map ['month', 'year'], (key, value) -> obj[key]
)
$scope.monthOrder = _.sortBy _.keys($scope.eventsGroupByMonth), (k)->
monthYearArray = k.split(',')
date = new Date()
date.setMonth(monthYearArray[0])
date.setYear(monthYearArray[1])
return -date.getTime()
else
$scope.paginateActive = false
$scope.page += 1
if (!data[0] || data[0].nb_total_events <= $scope.events.length)
$scope.noMoreResults = true
@ -55,13 +56,69 @@ Application.Controllers.controller "EventsController", ["$scope", "$state", 'Eve
##
# Callback to refresh the events list according to the filters set
##
$scope.filterEvents = ->
# reinitialize results datasets
$scope.page = 1
$scope.eventsGroupByMonth = {}
$scope.events = []
$scope.monthOrder = []
$scope.noMoreResults = false
# run a search query
Event.query Object.assign({page: $scope.page}, $scope.filters), (data) ->
$scope.events = data
groupEvents(data)
$scope.page += 1
if (!data[0] || data[0].nb_total_events <= $scope.events.length)
$scope.noMoreResults = true
##
# Test if the provided event occurs on a single day or on many days
# @param event {{start_date:Date, end_date:Date}} Event object as retreived from the API
# @return {boolean} false if the event occurs on many days
##
$scope.onSingleDay = (event) ->
moment(event.start_date).isSame(event.end_date, 'day')
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
$scope.loadMoreEvents()
$scope.filterEvents()
##
# Group the provided events by month/year and concat them with existing results
# Then compute the ordered list of months for the complete resultset.
# Affect the resulting events groups in $scope.eventsGroupByMonth and the ordered month keys in $scope.monthOrder.
# @param {Array} Events retrived from the API
##
groupEvents = (events) ->
if events.length > 0
eventsGroupedByMonth = _.groupBy(events, (obj) ->
_.map ['month', 'year'], (key, value) -> obj[key]
)
$scope.eventsGroupByMonth = Object.assign($scope.eventsGroupByMonth, eventsGroupedByMonth)
monthsOrder = _.sortBy _.keys($scope.eventsGroupByMonth), (k)->
monthYearArray = k.split(',')
date = new Date()
date.setMonth(monthYearArray[0])
date.setYear(monthYearArray[1])
return -date.getTime()
$scope.monthOrder = monthsOrder
@ -353,7 +410,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
# Create an hash map implementing the Reservation specs
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
# @param reserve {Object} Reservation parameters (places...)
# @param event {Object} Current event (Atelier/Stage)
# @param event {Object} Current event
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, nb_reserve_places:Number, nb_reserve_reduced_places:Number}}
##
mkReservation = (member, reserve, event) ->

View File

@ -25,7 +25,7 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
}
{
state: 'app.public.events_list'
linkText: 'courses_and_workshops_registrations'
linkText: 'events_registrations'
linkIcon: 'tags'
}
{
@ -73,7 +73,7 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
}
{
state: 'app.admin.events'
linkText: 'courses_and_workshops_monitoring'
linkText: 'manage_the_events'
linkIcon: 'tags'
}
{

View File

@ -468,6 +468,15 @@ angular.module('application.router', ['ui.router']).
templateUrl: '<%= asset_path "events/index.html" %>'
controller: 'EventsController'
resolve:
categoriesPromise: ['Category', (Category) ->
Category.query().$promise
]
themesPromise: ['EventTheme', (EventTheme) ->
EventTheme.query().$promise
]
ageRangesPromise: ['AgeRange', (AgeRange) ->
AgeRange.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query('app.public.events_list').$promise
]
@ -591,6 +600,15 @@ angular.module('application.router', ['ui.router']).
eventsPromise: ['Event', (Event)->
Event.query(page: 1).$promise
]
categoriesPromise: ['Category', (Category) ->
Category.query().$promise
]
themesPromise: ['EventTheme', (EventTheme) ->
EventTheme.query().$promise
]
ageRangesPromise: ['AgeRange', (AgeRange) ->
AgeRange.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query('app.admin.events').$promise
]
@ -601,6 +619,15 @@ angular.module('application.router', ['ui.router']).
templateUrl: '<%= asset_path "events/new.html" %>'
controller: 'NewEventController'
resolve:
categoriesPromise: ['Category', (Category) ->
Category.query().$promise
]
themesPromise: ['EventTheme', (EventTheme) ->
EventTheme.query().$promise
]
ageRangesPromise: ['AgeRange', (AgeRange) ->
AgeRange.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query(['app.admin.events_new', 'app.shared.event']).$promise
]
@ -614,6 +641,15 @@ angular.module('application.router', ['ui.router']).
eventPromise: ['Event', '$stateParams', (Event, $stateParams)->
Event.get(id: $stateParams.id).$promise
]
categoriesPromise: ['Category', (Category) ->
Category.query().$promise
]
themesPromise: ['EventTheme', (EventTheme) ->
EventTheme.query().$promise
]
ageRangesPromise: ['AgeRange', (AgeRange) ->
AgeRange.query().$promise
]
translations: [ 'Translations', (Translations) ->
Translations.query(['app.admin.events_edit', 'app.shared.event']).$promise
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'AgeRange', ["$resource", ($resource)->
$resource "/api/age_ranges/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -0,0 +1,8 @@
'use strict'
Application.Services.factory 'EventTheme', ["$resource", ($resource)->
$resource "/api/event_themes/:id",
{id: "@id"},
update:
method: 'PUT'
]

View File

@ -154,8 +154,11 @@
}
.article-thumbnail {
// max-height: 400px;
overflow: hidden;
img {
height: 400px;
}
}
}
@ -417,6 +420,7 @@
.event {
transition: all 0.07s linear;
overflow: hidden;
}
.event:hover {
@ -431,8 +435,8 @@ border-color: #eee;
}
.box-h-m {
height: 150px;
max-height: 150px;
height: 175px;
max-height: 175px;
}
.half-w {
@ -446,25 +450,31 @@ border-color: #d0d0d0;
padding: 10px;
}
.crop-130 {
height: 130px;
width: 130px;
max-width: 130px;
max-height: 130px;
.crop-155 {
height: 155px;
width: 155px;
max-width: 155px;
max-height: 155px;
overflow: hidden;
vertical-align: bottom;
}
.crop-130 img {
height: 130px;
.crop-155 img {
height: 155px;
width: auto;
}
@media only screen and (max-width: 1280px) and (min-width: 770px) {
.crop-130 {
@media only screen and (max-width: 1375px) and (min-width: 770px) {
.crop-155 {
height: 90px;
width: 90px;
margin-top: 25px;
margin-top: 35px;
}
}
@media only screen and (max-width: 1375px) and (min-width: 1125px) {
.half-w {
width: 60%;
}
}

View File

@ -0,0 +1,120 @@
<div class="m-t">
<h3 translate>{{ 'categories' }}</h3>
<p translate>{{ 'at_least_one_category_is_required' }}</p>
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('category')" translate>{{ 'add_a_category' }}</button>
<table class="table">
<thead>
<tr>
<th style="width:80%" translate>{{ 'name' }}</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="category in categories">
<td>
<span editable-text="category.name" e-cols="100" e-name="name" e-form="rowform" e-required>
{{ category.name }}
</span>
</td>
<td>
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveElement('category', $data, category.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted.category == category">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelElement('category', rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-default" ng-click="rowform.$show()">
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
</button>
<button class="btn btn-danger" ng-click="removeElement('category', $index)">
<i class="fa fa-trash-o"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<h3 translate>{{ 'themes' }}</h3>
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('theme')" translate>{{ 'add_a_theme' }}</button>
<table class="table">
<thead>
<tr>
<th style="width:80%" translate>{{ 'name' }}</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="theme in themes">
<td>
<span editable-text="theme.name" e-cols="100" e-name="name" e-form="rowform" e-required>
{{ theme.name }}
</span>
</td>
<td>
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveElement('theme', $data, theme.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted.theme == theme">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelElement('theme', rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-default" ng-click="rowform.$show()">
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
</button>
<button class="btn btn-danger" ng-click="removeElement('theme', $index)">
<i class="fa fa-trash-o"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<h3 translate>{{ 'age_ranges' }}</h3>
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('age_range')" translate>{{ 'add_a_range' }}</button>
<table class="table">
<thead>
<tr>
<th style="width:80%" translate>{{ 'name' }}</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="range in ageRanges">
<td>
<span editable-text="range.name" e-cols="100" e-name="name" e-form="rowform" e-required>
{{ range.name }}
</span>
</td>
<td>
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveElement('age_range', $data, range.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted.age_range == range">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelElement('age_range', rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-default" ng-click="rowform.$show()">
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
</button>
<button class="btn btn-danger" ng-click="removeElement('age_range', $index)">
<i class="fa fa-trash-o"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -7,7 +7,7 @@
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1 translate>{{ 'fablab_courses_and_workshops' }}</h1>
<h1 translate>{{ 'fablab_events' }}</h1>
</section>
</div>
@ -21,57 +21,66 @@
<section class="m-lg">
<div class="row">
<div class="col-md-12">
<uib-tabset justified="true">
<uib-tab heading="{{ 'events_monitoring' | translate }}">
<div class="col-md-6 m-b">
<select ng-model="selectedTimezone" class="form-control">
<option value="" translate>{{ 'all_events' }}</option>
<option value="passed" translate>{{ 'passed_events' }}</option>
<option value="future" translate>{{ 'events_to_come' }}</option>
</select>
</div>
<div class="col-md-6 m-b m-t">
<select ng-model="selectedTimezone" class="form-control">
<option value="" translate>{{ 'all_events' }}</option>
<option value="passed" translate>{{ 'passed_events' }}</option>
<option value="future" translate>{{ 'events_to_come' }}</option>
</select>
</div>
<table class="table">
<thead>
<tr>
<th style="width:30%" translate>{{ 'title' }}</th>
<th style="width:30%" translate>{{ 'dates' }}</th>
<th style="width:40%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="event in filtered = (events | eventsReservationsFilter:selectedTimezone)">
<td>
<a ui-sref="app.public.events_show({id: event.id})">{{ event.title }} </a>
</td>
<td>
<span> {{ 'from_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }} <span class="text-sm font-thin" translate>{{ 'to_date' }}</span> {{event.end_date | amDateFormat:'LL'}}</span>
<br/>
<span ng-if="event.all_day == 'true'" translate>{{ 'all_day' }}</span>
<span ng-if="event.all_day == 'false'">
{{ 'from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
<span class="text-sm font-thin" translate>{{ 'to_time' }}</span>
{{event.end_date | amDateFormat:'LT'}}
</span>
</td>
<td>
<div class="buttons">
<button class="btn btn-default" ui-sref="app.admin.event_reservations({id: event.id})">
<i class="fa fa-bookmark"></i> {{ 'view_reservations' | translate }}
</button>
<button class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
</button>
<table class="table">
<thead>
<tr>
<th style="width:30%" translate>{{ 'title' }}</th>
<th style="width:30%" translate>{{ 'dates' }}</th>
<th style="width:40%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="event in filtered = (events | eventsReservationsFilter:selectedTimezone)">
<td>
<a ui-sref="app.public.events_show({id: event.id})">{{ event.title }} </a>
</td>
<td>
<span> {{ 'from_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }} <span class="text-sm font-thin" translate>{{ 'to_date' }}</span> {{event.end_date | amDateFormat:'LL'}}</span>
<br/>
<span ng-if="event.all_day == 'true'" translate>{{ 'all_day' }}</span>
<span ng-if="event.all_day == 'false'">
{{ 'from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
<span class="text-sm font-thin" translate>{{ 'to_time' }}</span>
{{event.end_date | amDateFormat:'LT'}}
</span>
</td>
<td>
<div class="buttons">
<button class="btn btn-default" ui-sref="app.admin.event_reservations({id: event.id})">
<i class="fa fa-bookmark"></i> {{ 'view_reservations' | translate }}
</button>
<button class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-lg-12 text-center">
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive" translate>{{ 'load_the_next_events' }}</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</uib-tab>
<div class="row">
<div class="col-lg-12 text-center">
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive" translate>{{ 'load_the_next_courses_and_workshops' }}</a>
<uib-tab heading="{{ 'manage_filters' | translate }}">
<ng-include src="'<%= asset_path 'admin/events/filters.html' %>'"></ng-include>
</uib-tab>
</uib-tabset>
</div>
</div>
</section>

View File

@ -138,11 +138,11 @@
</div>
</uib-tab>
<uib-tab heading="{{ 'courses_and_workshops' | translate }}">
<uib-tab heading="{{ 'events' | translate }}">
<div class="col-md-6">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b">
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'next_courses_and_workshops' | translate }}</h4>
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'next_events' | translate }}</h4>
</div>
<div class="widget-content bg-light wrapper r-b">
<ul class="list-unstyled" ng-if="user.events_reservations.length > 0">
@ -158,14 +158,14 @@
</span>
</li>
</ul>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'future').length == 0" translate>{{ 'no_upcomning_courses_or_workshops'}}</div>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'future').length == 0" translate>{{ 'no_upcoming_events' }}</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b">
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'passed_courses_and_workshops' | translate }}</h4>
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'passed_events' | translate }}</h4>
</div>
<div class="widget-content bg-light auto wrapper r-b">
<ul class="list-unstyled" ng-if="user.events_reservations.length > 0">
@ -173,7 +173,7 @@
<span class="font-sbold">{{r.reservable.title}}</span> - <span class="label label-info text-white wrapper-sm">{{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}</span>
</li>
</ul>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'passed').length == 0" translate>{{ 'no_passed_courses_or_workshop' }}</div>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'passed').length == 0" translate>{{ 'no_passed_events' }}</div>
</div>
</div>
</div>

View File

@ -13,7 +13,7 @@
<div class="col-md-6">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b">
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'your_next_courses_and_workshops' | translate }}</h4>
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'your_next_events' | translate }}</h4>
</div>
<div class="widget-content bg-light wrapper r-b">
<ul class="list-unstyled" ng-if="user.events_reservations.length > 0">
@ -23,14 +23,14 @@
<br/><span translate translate-values="{NUMBER: r.nb_reserve_reduced_places}" translate-interpolation="messageformat">{{ 'NUMBER_reduced_fare_places_reserved' }}</span>
</li>
</ul>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'future').length == 0" translate>{{ 'no_courses_or_workshops_to_come' }}</div>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'future').length == 0" translate>{{ 'no_events_to_come' }}</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b">
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'your_previous_courses_and_workshops' | translate }}</h4>
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'your_previous_events' | translate }}</h4>
</div>
<div class="widget-content bg-light auto wrapper r-b">
<ul class="list-unstyled" ng-if="user.events_reservations.length > 0">
@ -38,7 +38,7 @@
<span class="font-sbold">{{r.reservable.title}}</span> - <span class="label label-info text-white wrapper-sm">{{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}</span>
</li>
</ul>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'passed').length == 0" translate>{{ 'no_passed_courses_or_workshops' }}</div>
<div ng-if="(user.events_reservations | eventsReservationsFilter:'passed').length == 0" translate>{{ 'no_passed_events' }}</div>
</div>
</div>
</div>

View File

@ -14,7 +14,7 @@
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.settings" translate>{{ 'my_settings' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.projects" translate>{{ 'my_projects' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.trainings" translate>{{ 'my_trainings' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.events" translate>{{ 'my_courses_and_workshops' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.events" translate>{{ 'my_events' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.invoices" translate>{{ 'my_invoices' }}</a></li>
</ul>
</section>

View File

@ -80,7 +80,7 @@
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3 translate>{{ 'event_type' }}</h3>
<h3 translate>{{ 'event_type' }} *</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="event[category_ids][]" value="" />
@ -96,6 +96,40 @@
</div>
</div>
<div class="widget panel b-a m m-t-lg" ng-show="themes.length > 0">
<div class="panel-heading b-b small">
<h3 translate>{{ 'event_theme' }}</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="event[event_theme_ids][]" value="" />
<ui-select ng-model="event.event_theme_ids" name="event[event_theme_ids][]">
<ui-select-match>
<span ng-bind="$select.selected.name"></span>
<input type="hidden" name="event[event_theme_ids][]" value="{{$select.selected.id}}" />
</ui-select-match>
<ui-select-choices repeat="t.id as t in (themes | filter: $select.search)">
<span ng-bind-html="t.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>
<div class="widget panel b-a m m-t-lg" ng-show="ageRanges.length > 0">
<div class="panel-heading b-b small">
<h3 translate>{{ 'age_range' }}</h3>
</div>
<div class="widget-content no-bg wrapper">
<ui-select ng-model="event.age_range_id" name="event[age_range_id][]">
<ui-select-match>
<span ng-bind="$select.selected.name"></span>
<input type="hidden" name="event[age_range_id]" value="{{$select.selected.id}}" />
</ui-select-match>
<ui-select-choices repeat="a.id as a in (ageRanges | filter: $select.search)">
<span ng-bind-html="a.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3 translate>{{ 'dates_and_opening_hours' }}</h3>

View File

@ -7,7 +7,7 @@
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1 translate>{{ 'the_fablab_s_courses_and_workshops' }}</h1>
<h1 translate>{{ 'the_fablab_s_events' }}</h1>
</section>
</div>
@ -20,38 +20,64 @@
<section class="m-lg">
<div ng-repeat="month in monthOrder">
<h1>{{month.split(',')[0]}}, {{month.split(',')[1]}}</h1>
<div class="row m-b-md">
<div class="col-md-3 m-b" ng-show="categories.length > 0">
<select ng-model="filters.category_id" ng-change="filterEvents()" class="form-control" ng-options="c.id as c.name for c in categories">
<option value="" translate>{{ 'all_categories' }}</option>
</select>
</div>
<div class="row" ng-repeat="event in (eventsGroupByMonth[month].length/3 | array)">
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="event in eventsGroupByMonth[month].slice(3*$index, 3*$index + 3)" ng-click="showEvent(event)">
<a class="block bg-white img-full p-sm p-l-m box-h-m event b b-light-dark m-t-sm" ui-sref="app.public.events_show({id: event.id})">
<div class="pull-left half-w m-t-n-sm">
<h5 class="text-xs">{{event.categories[0].name}}</h5>
<h4 class="m-n text-sm clear l-n">{{event.title}}</h4>
<h3 class="m-n">{{event.start_date | amDateFormat:'L'}} <span class="text-sm font-thin" translate> {{ 'to_date' }} </span> {{event.end_date | amDateFormat:'L'}}</h3>
<h6 class="m-n" ng-if="event.amount">{{ 'full_price_' | translate }} {{event.amount | currency}} <span ng-if="event.reduced_amount > 0">/ {{ 'reduced_rate_' | translate }} {{event.reduced_amount | currency}}</span></h6>
</div>
<!-- Event Image -->
<div class="pull-right crop-130">
<img class="pull-right" ng-src="{{event.event_image_small}}" title="{{event.title}}" ng-if="event.event_image">
<img class="pull-right img-responsive" src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!event.event_image">
</div>
</a>
<div class="col-md-3 m-b" ng-show="themes.length > 0">
<select ng-model="filters.theme_id" ng-change="filterEvents()" class="form-control" ng-options="t.id as t.name for t in themes">
<option value="" translate>{{ 'all_themes' }}</option>
</select>
</div>
<div class="col-md-3 m-b" ng-show="ageRanges.length > 0">
<select ng-model="filters.age_range_id" ng-change="filterEvents()" class="form-control" ng-options="a.id as a.name for a in ageRanges">
<option value="" translate>{{ 'for_all' }}</option>
</select>
</div>
</div>
<div ng-repeat="month in monthOrder">
<h1>{{month.split(',')[0]}}, {{month.split(',')[1]}}</h1>
<div class="row" ng-repeat="event in (eventsGroupByMonth[month].length/3 | array)">
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="event in eventsGroupByMonth[month].slice(3*$index, 3*$index + 3)" ng-click="showEvent(event)">
<a class="block bg-white img-full p-sm p-l-m box-h-m event b b-light-dark m-t-sm" ui-sref="app.public.events_show({id: event.id})">
<div class="pull-left half-w m-t-n-sm">
<h5 class="text-xs">{{event.categories[0].name}}</h5>
<h4 class="m-n text-sm clear l-n">{{event.title}}</h4>
<h3 class="m-n" ng-show="onSingleDay(event)">{{event.start_date | amDateFormat:'L'}}</h3>
<h3 class="m-n" ng-hide="onSingleDay(event)">{{event.start_date | amDateFormat:'L'}} <span class="text-sm font-thin" translate> {{ 'to_date' }} </span> {{event.end_date | amDateFormat:'L'}}</h3>
<h6 class="m-n" ng-if="event.amount">{{ 'full_price_' | translate }} {{event.amount | currency}} <span ng-if="event.reduced_amount > 0">/ {{ 'reduced_rate_' | translate }} {{event.reduced_amount | currency}}</span></h6>
<div>
<span class="text-black-light text-xs" ng-if="event.event_themes[0]"><i class="fa fa-tags" aria-hidden="true"></i> {{event.event_themes[0].name}}</span>
<span class="text-black-light text-xs" ng-if="event.age_range"><i class="fa fa-users" aria-hidden="true"></i> {{event.age_range.name}}</span>
</div>
</div>
<!-- Event Image -->
<div class="pull-right crop-155">
<img class="pull-right" ng-src="{{event.event_image_small}}" title="{{event.title}}" ng-if="event.event_image">
<img class="pull-right img-responsive" src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!event.event_image">
</div>
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 text-center m-t-md">
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive" translate>{{ 'load_the_next_courses_and_workshops' }}</a>
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-hide="noMoreResults" translate>{{ 'load_the_next_events' }}</a>
</div>
</div>

View File

@ -69,7 +69,11 @@
<h5>{{event.categories[0].name}}</h5>
<dl class="text-sm">
<dt><i class="fa fa-calendar"></i> {{ 'dates' | translate }}</dt>
<dt ng-if="event.event_themes.length > 0"><i class="fa fa-tags" aria-hidden="true"></i> {{event.event_themes[0].name}}</dt>
<dt ng-if="event.age_range"><i class="fa fa-users" aria-hidden="true"></i> {{event.age_range.name}}</dt>
</dl>
<dl class="text-sm">
<dt><i class="fa fa-calendar" aria-hidden="true"></i> {{ 'dates' | translate }}</dt>
<dd>{{ 'beginning' | translate }} <span class="text-u-l">{{event.start_date | amDateFormat:'L'}}</span><br>{{ 'ending' | translate }} <span class="text-u-l">{{event.end_date | amDateFormat:'L'}}</span></dd>
<dt><i class="fa fa-clock-o"></i> {{ 'opening_hours' | translate }}</dt>
<dd ng-if="event.all_day == 'true'"><span translate>{{ 'all_day' }}</span></dd>

View File

@ -80,7 +80,7 @@
</div>
<section class="col-lg-12 wrapper">
<h4 class="text-sm m-t-sm">{{ 'fablab_s_next_courses_and_workshops' | translate }} <a ui-sref="app.public.events_list" class="pull-right"><i class="fa fa-tags"></i> {{ 'every_events' | translate }}</a></h4>
<h4 class="text-sm m-t-sm">{{ 'fablab_s_next_events' | translate }} <a ui-sref="app.public.events_list" class="pull-right"><i class="fa fa-tags"></i> {{ 'every_events' | translate }}</a></h4>
<div class="row" ng-repeat="event in (upcomingEvents.length/3 | array)">

View File

@ -38,7 +38,7 @@
<li><a href="#" ui-sref="app.logged.dashboard.settings" translate>{{ 'my_settings' }}</a></li>
<li><a href="#" ui-sref="app.logged.dashboard.projects" translate>{{ 'my_projects' }}</a></li>
<li><a href="#" ui-sref="app.logged.dashboard.trainings" translate>{{ 'my_trainings' }}</a></li>
<li><a href="#" ui-sref="app.logged.dashboard.events" translate>{{ 'my_courses_and_workshops' }}</a></li>
<li><a href="#" ui-sref="app.logged.dashboard.events" translate>{{ 'my_events' }}</a></li>
<li><a href="#" ui-sref="app.logged.dashboard.invoices" translate>{{ 'my_invoices' }}</a></li>
<li class="divider"></li>

View File

@ -53,7 +53,7 @@
</li>
<li class="hidden-sm hidden-md hidden-lg">
<a href="#" ui-sref="app.logged.dashboard.events">
<i class="fa fa-calendar-o"></i> <span translate>{{ 'my_courses_and_workshops' }}</span>
<i class="fa fa-calendar-o"></i> <span translate>{{ 'my_events' }}</span>
</a>
</li>
<li class="hidden-sm hidden-md hidden-lg" ng-if-end>

View File

@ -0,0 +1,49 @@
class API::AgeRangesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_age_range, only: [:show, :update, :destroy]
def index
@age_ranges = AgeRange.all
end
def show
end
def create
authorize AgeRange
@age_range = AgeRange.new(age_range_params)
if @age_range.save
render :show, status: :created, location: @age_range
else
render json: @age_range.errors, status: :unprocessable_entity
end
end
def update
authorize AgeRange
if @age_range.update(age_range_params)
render :show, status: :ok, location: @age_range
else
render json: @age_range.errors, status: :unprocessable_entity
end
end
def destroy
authorize AgeRange
if @age_range.safe_destroy
head :no_content
else
render json: @age_range.errors, status: :unprocessable_entity
end
end
private
def set_age_range
@age_range = AgeRange.find(params[:id])
end
def age_range_params
params.require(:age_range).permit(:name)
end
end

View File

@ -1,5 +1,49 @@
class API::CategoriesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_category, only: [:show, :update, :destroy]
def index
@categories = Category.all
end
def show
end
def create
authorize Category
@category = Category.new(category_params)
if @category.save
render :show, status: :created, location: @category
else
render json: @category.errors, status: :unprocessable_entity
end
end
def update
authorize Category
if @category.update(category_params)
render :show, status: :ok, location: @category
else
render json: @category.errors, status: :unprocessable_entity
end
end
def destroy
authorize Category
if @category.safe_destroy
head :no_content
else
render json: @category.errors, status: :unprocessable_entity
end
end
private
def set_category
@category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:name)
end
end

View File

@ -0,0 +1,49 @@
class API::EventThemesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_event_theme, only: [:show, :update, :destroy]
def index
@event_themes = EventTheme.all
end
def show
end
def create
authorize EventTheme
@event_theme = EventTheme.new(event_theme_params)
if @event_theme.save
render :show, status: :created, location: @event_theme
else
render json: @event_theme.errors, status: :unprocessable_entity
end
end
def update
authorize EventTheme
if @event_theme.update(event_theme_params)
render :show, status: :ok, location: @event_theme
else
render json: @event_theme.errors, status: :unprocessable_entity
end
end
def destroy
authorize EventTheme
if @event_theme.safe_destroy
head :no_content
else
render json: @event_theme.errors, status: :unprocessable_entity
end
end
private
def set_event_theme
@event_theme = EventTheme.find(params[:id])
end
def event_theme_params
params.require(:event_theme).permit(:name)
end
end

View File

@ -3,9 +3,16 @@ class API::EventsController < API::ApiController
def index
@events = policy_scope(Event)
@total = @events.count
@page = params[:page]
# filters
@events = @events.joins(:categories).where('categories.id = :category', category: params[:category_id]) if params[:category_id]
@events = @events.joins(:event_themes).where('event_themes.id = :theme', theme: params[:theme_id]) if params[:theme_id]
@events = @events.where('age_range_id = :age_range', age_range: params[:age_range_id]) if params[:age_range_id]
# paginate
@events = @events.page(@page).per(12)
end
# GET /events/upcoming/:limit
@ -57,7 +64,8 @@ class API::EventsController < API::ApiController
def event_params
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
:amount, :reduced_amount, :nb_total_places, :availability_id,
:all_day, :recurrence, :recurrence_end_at, :category_ids, category_ids: [],
:all_day, :recurrence, :recurrence_end_at, :category_ids, :event_theme_ids,
:age_range_id, event_theme_ids: [], category_ids: [],
event_image_attributes: [:attachment], event_files_attributes: [:id, :attachment, :_destroy])
start_date = Time.zone.parse(event_preparams[:start_date])
end_date = Time.zone.parse(event_preparams[:end_date])

14
app/models/age_range.rb Normal file
View File

@ -0,0 +1,14 @@
class AgeRange < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
has_many :events, dependent: :nullify
def safe_destroy
if self.events.count == 0
destroy
else
false
end
end
end

View File

@ -1,3 +1,36 @@
class Category < ActiveRecord::Base
has_and_belongs_to_many :events, join_table: :events_categories
extend FriendlyId
friendly_id :name, use: :slugged
has_and_belongs_to_many :events, join_table: :events_categories, dependent: :destroy
after_create :create_statistic_subtype
after_update :update_statistic_subtype, if: :name_changed?
after_destroy :remove_statistic_subtype
def create_statistic_subtype
index = StatisticIndex.where(es_type_key: 'event')
StatisticSubType.create!({statistic_types: index.first.statistic_types, key: self.slug, label: self.name})
end
def update_statistic_subtype
index = StatisticIndex.where(es_type_key: 'event')
subtype = StatisticSubType.joins(statistic_type_sub_types: :statistic_type).where(key: self.slug, statistic_types: { statistic_index_id: index.first.id }).first
subtype.label = self.name
subtype.save!
end
def remove_statistic_subtype
subtype = StatisticSubType.where(key: self.slug).first
subtype.destroy!
end
def safe_destroy
if Category.count > 1 && self.events.count == 0
destroy
else
false
end
end
end

View File

@ -8,6 +8,9 @@ class Event < ActiveRecord::Base
has_and_belongs_to_many :categories, join_table: :events_categories
validates :categories, presence: true
has_many :reservations, as: :reservable, dependent: :destroy
has_and_belongs_to_many :event_themes, join_table: :events_event_themes, dependent: :destroy
belongs_to :age_range
belongs_to :availability, dependent: :destroy
accepts_nested_attributes_for :availability
@ -23,6 +26,10 @@ class Event < ActiveRecord::Base
title
end
def themes
self.event_themes
end
def recurrence_events
Event.includes(:availability).where('events.recurrence_id = ? AND events.id != ? AND availabilities.start_at >= ?', recurrence_id, id, Time.now).references(:availabilities)
end

14
app/models/event_theme.rb Normal file
View File

@ -0,0 +1,14 @@
class EventTheme < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
has_and_belongs_to_many :events, join_table: :events_event_themes, dependent: :destroy
def safe_destroy
if self.events.count == 0
destroy
else
false
end
end
end

View File

@ -6,5 +6,7 @@ module Stats
attribute :eventId, Integer
attribute :eventDate, String
attribute :ageRange, String
attribute :eventTheme, String
end
end

View File

@ -110,9 +110,9 @@ module PDF
### Training reservation
when 'Training'
details += I18n.t('invoices.training_reservation_DESCRIPTION', DESCRIPTION: item.description)
### courses and workshops reservation
### events reservation
when 'Event'
details += I18n.t('invoices.courses_and_workshops_reservation_DESCRIPTION', DESCRIPTION: item.description)
details += I18n.t('invoices.event_reservation_DESCRIPTION', DESCRIPTION: item.description)
# details of the number of tickets
details += "\n "+I18n.t('invoices.full_price_ticket', count: invoice.invoiced.nb_reserve_places) if invoice.invoiced.nb_reserve_places > 0
details += "\n "+I18n.t('invoices.reduced_rate_ticket', count: invoice.invoiced.nb_reserve_reduced_places) if invoice.invoiced.nb_reserve_reduced_places > 0

View File

@ -0,0 +1,7 @@
class AgeRangePolicy < ApplicationPolicy
%w(create update destroy show).each do |action|
define_method "#{action}?" do
user.is_admin?
end
end
end

View File

@ -0,0 +1,7 @@
class CategoryPolicy < ApplicationPolicy
%w(create update destroy show).each do |action|
define_method "#{action}?" do
user.is_admin?
end
end
end

View File

@ -0,0 +1,7 @@
class EventThemePolicy < ApplicationPolicy
%w(create update destroy show).each do |action|
define_method "#{action}?" do
user.is_admin?
end
end
end

View File

@ -68,7 +68,9 @@ class StatisticService
eventId: r.event_id,
name: r.event_name,
eventDate: r.event_date,
reservationId: r.reservation_id
reservationId: r.reservation_id,
eventTheme: r.event_theme,
ageRange: r.age_range
}.merge(user_info_stat(r)))
stat.stat = (type == 'booking' ? r.nb_places : r.nb_hours)
stat.save
@ -201,6 +203,8 @@ class StatisticService
event_type: r.reservable.categories.first.name,
event_name: r.reservable.name,
event_date: slot.start_at.to_date,
event_theme: (r.reservable.event_themes.first ? r.reservable.event_themes.first.name : ''),
age_range: (r.reservable.age_range_id ? r.reservable.age_range.name : ''),
nb_places: r.nb_reserve_places + r.nb_reserve_reduced_places,
nb_hours: difference_in_hours(slot.start_at, slot.end_at),
ca: calcul_ca(r.invoice)

View File

@ -0,0 +1,6 @@
user_is_admin = (current_user and current_user.is_admin?)
json.array!(@age_ranges) do |ar|
json.extract! ar, :id, :name
json.related_to ar.events.count if user_is_admin
end

View File

@ -0,0 +1 @@
json.extract! @age_range, :id, :name

View File

@ -1,3 +1,6 @@
user_is_admin = (current_user and current_user.is_admin?)
json.array!(@categories) do |category|
json.extract! category, :id, :name
json.related_to category.events.count if user_is_admin
end

View File

@ -0,0 +1 @@
json.extract! @category, :id, :name

View File

@ -0,0 +1,6 @@
user_is_admin = (current_user and current_user.is_admin?)
json.array!(@event_themes) do |theme|
json.extract! theme, :id, :name
json.related_to theme.events.count if user_is_admin
end

View File

@ -0,0 +1 @@
json.extract! @event_theme, :id, :name

View File

@ -1,4 +1,4 @@
json.extract! event, :id, :title, :description
json.extract! event, :id, :title, :description, :age_range_id
json.event_image event.event_image.attachment_url if event.event_image
json.event_files_attributes event.event_files do |f|
json.id f.id
@ -10,6 +10,14 @@ json.categories event.categories do |c|
json.id c.id
json.name c.name
end
json.event_theme_ids event.event_theme_ids
json.event_themes event.event_themes do |e|
json.name e.name
end
json.age_range_id event.age_range_id
json.age_range do
json.name event.age_range.name
end if event.age_range
json.start_date event.availability.start_at
json.start_time event.availability.start_at
json.end_date event.availability.end_at
@ -28,3 +36,4 @@ json.amount (event.amount / 100.0) if event.amount
json.reduced_amount (event.reduced_amount / 100.0) if event.reduced_amount
json.nb_total_places event.nb_total_places
json.nb_free_places event.nb_free_places || event.nb_total_places

View File

@ -1,8 +1,10 @@
total = @events.except(:offset, :limit, :order).count
json.cache! [@events, @page] do
json.array!(@events) do |event|
json.partial! 'api/events/event', event: event
json.event_image_small event.event_image.attachment.small.url if event.event_image
json.url event_url(event, format: :json)
json.nb_total_events @total
json.nb_total_events total
end
end

View File

@ -1,7 +1,7 @@
maxInvoices = @invoices.except(:offset, :limit, :order).count
max_invoices = @invoices.except(:offset, :limit, :order).count
json.array!(@invoices) do |invoice|
json.maxInvoices maxInvoices
json.maxInvoices max_invoices
json.extract! invoice, :id, :created_at, :reference, :invoiced_type, :user_id, :avoir_date
json.total (invoice.total / 100.00)
json.url invoice_url(invoice, format: :json)

View File

@ -1,8 +1,8 @@
user_is_admin = (current_user and current_user.is_admin?)
maxMembers = @query.except(:offset, :limit, :order).count
max_members = @query.except(:offset, :limit, :order).count
json.array!(@members) do |member|
json.maxMembers maxMembers
json.maxMembers max_members
json.id member.id
json.username member.username
json.slug member.slug

View File

@ -1,7 +1,7 @@
maxMembers = @query.except(:offset, :limit, :order).count
max_members = @query.except(:offset, :limit, :order).count
json.array!(@members) do |member|
json.maxMembers maxMembers
json.maxMembers max_members
json.id member.id
json.email member.email if current_user
json.profile do

View File

@ -70,17 +70,29 @@ en:
dont_forget_to_change_them_before_creating_slots_for_this_training: "Don't forget to change them before creating slots for this training."
events:
# courses and workshops tracking and management
fablab_courses_and_workshops: "Fablab courses and workshops"
# events tracking and management
events_monitoring: "Events monitoring"
manage_filters: "Manage filters"
fablab_events: "Fablab events"
all_events: "All events"
passed_events: "Passed events"
events_to_come: "Events to come"
from_DATE: "From {{DATE}}" # angular interpolation
from_TIME: "From {{TIME}}" # angular interpolation
view_reservations: "View reservations"
categories: "Categories"
add_a_category: "Add a category"
add_a_theme: "Add a theme"
age_ranges: "Age ranges"
add_a_range: "Add a range"
do_you_really_want_to_delete_this_ELEMENT: "Do you really want to delete this {ELEMENT, select, category{category} theme{theme} age_range{age range} other{element}}?" # messageFormat interpolation
unable_to_delete_ELEMENT_already_in_use_NUMBER_times: "Unable to delete this {ELEMENT, select, category{category} theme{theme} age_range{age range} other{element}} because it is already associated with {NUMBER, plural, =0{no events} one{one event} other{{NUMBER} events}}." # messageFormat interpolation
at_least_one_category_is_required: "At least one category is required."
unable_to_delete_the_last_one: "Unable to delete the last one."
unable_to_delete_an_error_occured: "Unable to delete: an error occurred."
events_new:
# add a new workshop/course
# add a new event
none: "None"
every_days: "Every days"
every_week: "Every week"
@ -319,13 +331,12 @@ en:
next_trainings: "Next trainings"
passed_trainings: "Passed trainings"
validated_trainings: "Validated trainings"
courses_and_workshops: "Courses and workshops"
next_courses_and_workshops: "Next courses and workshops"
no_upcomning_courses_or_workshops: "No upcoming courses and workshops"
events: "Events"
next_events: "Next events"
no_upcoming_events: "No upcoming events"
NUMBER_full_price_tickets_reserved: "{NUMBER, plural, =0{} one{1 full price ticket reserved} other{{NUMBER} full price tickets reserved}}" # messageFormat interpolation
NUMBER_reduced_rate_tickets_reserved: "{NUMBER, plural, =0{} one{1 reduced rate ticket reserved} other{{NUMBER} reduced rate tickets reserved}}" # messageFormat interpolation
passed_courses_and_workshops: "Passed courses and workshops"
no_passed_courses_or_workshop: "No passed courses or workshops"
passed_events: "Passed events"
invoices: "Invoices"
invoice_#: "Invoice #"
download_the_refund_invoice: "Download the refund invoice"

View File

@ -70,17 +70,29 @@ fr:
dont_forget_to_change_them_before_creating_slots_for_this_training: "Pensez à les modifier avant de créer des créneaux pour cette formation."
events:
# gestion et suivi des stages et ateliers
fablab_courses_and_workshops: "Les Stages et ateliers du Fab Lab"
# gestion et suivi des évènements
events_monitoring: "Suivi des évènements"
manage_filters: "Gérer les filtres"
fablab_events: "Les évènements du Fab Lab"
all_events: "Tous les évènements"
passed_events: "Les évènements déjà passés"
events_to_come: "Les évènements à venir"
from_DATE: "Du {{DATE}}" # angular interpolation
from_TIME: "De {{TIME}}" # angular interpolation
view_reservations: "Consulter les réservations"
categories: "Catégories"
add_a_category: "Ajouter une catégorie"
add_a_theme: "Ajouter une thématique"
age_ranges: "Tranches d'âge"
add_a_range: "Ajouter une tranche"
do_you_really_want_to_delete_this_ELEMENT: "Voulez-vous vraiment supprimer cette {ELEMENT, select, category{catégorie} theme{thématique} age_range{tranche d'âge} other{élément}} ?" # messageFormat interpolation
unable_to_delete_ELEMENT_already_in_use_NUMBER_times: "Impossible de supprimer cette {ELEMENT, select, category{catégorie} theme{thématique} age_range{tranche d'âge} other{élément}} car elle est actuellement associée à {NUMBER, plural, =0{aucun évènement} one{un évènement} other{{NUMBER} évènements}}." # messageFormat interpolation
at_least_one_category_is_required: "Au moins une catégorie est requise."
unable_to_delete_the_last_one: "Impossible de supprimer la dernière."
unable_to_delete_an_error_occured: "Impossible de supprimer : une erreur est survenue."
events_new:
# ajouter un nouveau atelier/stage
# ajouter un nouvel évènement
none: "Aucune"
every_days: "Tous les jours"
every_week: "Chaque semaine"
@ -319,13 +331,12 @@ fr:
next_trainings: "Les prochaines formations"
passed_trainings: "Les formations passées"
validated_trainings: "Les formations validées"
courses_and_workshops: "Ateliers et stages"
next_courses_and_workshops: "Les prochains stages et ateliers"
no_upcomning_courses_or_workshops: "Aucun stage ou atelier à venir"
events: "Évènements"
next_events: "Les prochains évènements"
no_upcoming_events: "Aucun évènement à venir"
NUMBER_full_price_tickets_reserved: "{NUMBER, plural, =0{} one{1 place plein tarif réservée} other{{NUMBER} places plein tarif réservées}}" # messageFormat interpolation
NUMBER_reduced_rate_tickets_reserved: "{NUMBER, plural, =0{} one{1 place à tarif réduit réservée} other{{NUMBER} places à tarif réduit réservées}}" # messageFormat interpolation
passed_courses_and_workshops: "Les stages et ateliers passés"
no_passed_courses_or_workshop: "Aucun stage ou atelier passé"
passed_events: "Les évènements passés"
invoices: "Factures"
invoice_#: "Facture n°"
download_the_refund_invoice: "Télécharger l'avoir"

View File

@ -59,10 +59,9 @@ en:
your_approved_trainings: "Your approved trainings"
events:
# dashboard: my events
your_next_courses_and_workshops: "Your next courses and workshops"
no_courses_or_workshops_to_come: "No courses or workshops to come"
your_previous_courses_and_workshops: "Your previous courses and workshops"
no_passed_courses_or_workshops: "No passed courses or workshops"
your_next_events: "Your next events"
no_events_to_come: "No events to come"
your_previous_events: "Your previous events"
NUMBER_normal_places_reserved: "{NUMBER} {NUMBER, plural, =1{normal place reserved}, other{normal places reserved}}" # messageFormat interpolation
NUMBER_reduced_fare_places_reserved: "{NUMBER} {NUMBER, plural, =1{reduced fare place reserved}, other{reduced fare places reserved}" # messageFormat interpolation
invoices:

View File

@ -59,10 +59,9 @@ fr:
your_approved_trainings: "Vos formations validées"
events:
# tableau de bord : mes évènements
your_next_courses_and_workshops: "Vos prochains stages et ateliers"
no_courses_or_workshops_to_come: "Aucun stage ou atelier à venir"
your_previous_courses_and_workshops: "Vos stages et ateliers passés"
no_passed_courses_or_workshops: "Aucun stage ou atelier passé"
your_next_events: "Vos prochains évènements"
no_events_to_come: "Aucun évènement à venir"
your_previous_events: "Vos évènements passés"
NUMBER_normal_places_reserved: "{NUMBER} {NUMBER, plural, =1{place normale réservée}, other{places normales réservées}}" # messageFormat interpolation
NUMBER_reduced_fare_places_reserved: "{NUMBER} {NUMBER, plural, =1{place réservée à tarif réduit}, other{places réservées à tarif réduit}" # messageFormat interpolation
invoices:

View File

@ -12,7 +12,7 @@ en:
my_settings: "My Settings"
my_projects: "My Projects"
my_trainings: "My Trainings"
my_courses_and_workshops: "My Courses and Workshops"
my_events: "My Events"
my_invoices: "My Invoices"
# login/logout
@ -29,7 +29,7 @@ en:
home: "Home"
reserve_a_machine: "Reserve a Machine"
trainings_registrations: "Trainings registrations"
courses_and_workshops_registrations: "Courses and Workshops registrations"
events_registrations: "Events registrations"
projects_gallery: "Projects gallery"
subscriptions: "Subscriptions"
@ -40,7 +40,7 @@ en:
manage_the_users: "Manage the Users"
manage_the_invoices: "Manage the invoices"
subscriptions_and_prices: "Subscriptions and Prices"
courses_and_workshops_monitoring: "Courses and Workshops monitoring"
manage_the_events: "Manage the events"
manage_the_machines: "Manage the Machines"
manage_the_projects_elements: "Manage the Projects Elements"
statistics: "Statistics"
@ -108,7 +108,7 @@ en:
discover_members: "Discover members"
# next events summary on the home page
fablab_s_next_courses_and_workshops: "Fablab's next courses and workshops"
fablab_s_next_events: "Fablab's next events"
every_events: "Every events"
from_date_to_date: "From {{START}} to {{END}}" # angular interpolation
on_the_date: "On the {{DATE}}" # angular interpolation
@ -131,7 +131,6 @@ en:
my_projects: "My projects"
projects_to_whom_i_take_part_in: "Projects to whom I take part in"
all_machines: "All machines"
all_themes: "All themes"
all_materials: "All materials"
load_next_projects: "Load next projects"
@ -205,7 +204,9 @@ en:
events_list:
# Fablab's events list
the_fablab_s_courses_and_workshops: "The Fablab's courses and workshops"
the_fablab_s_events: "The Fablab's events"
all_categories: "All categories"
for_all: "For all"
events_show:
# details and booking of an event

View File

@ -12,7 +12,7 @@ fr:
my_settings: "Mes paramètres"
my_projects: "Mes projets"
my_trainings: "Mes formations"
my_courses_and_workshops: "Mes stages et ateliers"
my_events: "Mes évènements"
my_invoices: "Mes factures"
# connexion / déconnexion
@ -29,7 +29,7 @@ fr:
home: "Accueil"
reserve_a_machine: "Réserver une machine"
trainings_registrations: "Inscriptions formations"
courses_and_workshops_registrations: "Inscriptions stages et ateliers"
events_registrations: "Inscriptions aux évènements"
projects_gallery: "Galerie de projets"
subscriptions: "Abonnements"
@ -40,7 +40,7 @@ fr:
manage_the_users: "Gérer les utilisateurs"
manage_the_invoices: "Gérer les factures"
subscriptions_and_prices: "Abonnements & Tarifs"
courses_and_workshops_monitoring: "Suivi stages et ateliers"
manage_the_events: "Gérer les évènements"
manage_the_machines: "Gérer les machines"
manage_the_projects_elements: "Gérer les éléments projets"
statistics: "Statistiques"
@ -108,7 +108,7 @@ fr:
discover_members: "Découvrir les membres"
# résumé des prochains évènements sur la page d'acceuil
fablab_s_next_courses_and_workshops: "Les prochains ateliers et stages du Fab Lab"
fablab_s_next_events: "Les prochains évènements du Fab Lab"
every_events: "Tous les évènements"
from_date_to_date: "Du {{START}} au {{END}}" # angular interpolation
on_the_date: "Le {{DATE}}" # angular interpolation
@ -131,7 +131,6 @@ fr:
my_projects: "Mes projets"
projects_to_whom_i_take_part_in: "Les projets auxquels je collabore"
all_machines: "Toutes les machines"
all_themes: "Toutes les thématiques"
all_materials: "Tous les matériaux"
load_next_projects: "Charger les projets suivants"
@ -207,7 +206,9 @@ fr:
events_list:
# liste des évènements du fablab
the_fablab_s_courses_and_workshops: "Les Stages et ateliers du Fab Lab"
the_fablab_s_events: "Les évènements du Fab Lab"
all_categories: "Toutes les catégories"
for_all: "Tout public"
events_show:
# détails d'un événement et réservation

View File

@ -54,7 +54,8 @@ en:
confirm_and_pay: "Confirm and pay"
your_invoice_will_be_available_soon_from_your_: "Your invoice will be available soon form your"
add_an_event: "Add an event"
load_the_next_courses_and_workshops: "Load the next courses and workshops..."
load_the_next_events: "Load the next events..."
no_passed_events: "No passed events"
dates: "Dates:"
thank_you_your_payment_has_been_successfully_registered: "Thank you. Your payment has been successfully registered !"
surname: "Surname"
@ -92,6 +93,7 @@ en:
book: "Book"
description_is_required: "Description is required."
name_is_required: "Name is required."
all_themes: "All themes"
messages:
you_will_lose_any_unsaved_modification_if_you_quit_this_page: "You will lose any unsaved modification if you quit this page"
@ -163,7 +165,7 @@ en:
here_is_the_summary_of_the_slots_to_book_for_the_current_user: "Here is the summary of the slots to book for the current user:"
event:
# event edition form (courses/workshops)
# event edition form
title_is_required: "Title is required."
matching_visual: "Matching visual"
choose_a_picture: "Choose a picture"
@ -182,6 +184,8 @@ en:
standard_rate: "Standard rate"
0_=_free: "0 = free"
tickets_available: "Tickets available"
event_theme: "Event theme"
age_range: "Age range"
plan:
# subscription plan edition form

View File

@ -54,7 +54,8 @@ fr:
confirm_and_pay: "Valider et payer"
your_invoice_will_be_available_soon_from_your_: "Votre facture sera bientôt disponible depuis votre"
add_an_event: "Ajouter un évènement"
load_the_next_courses_and_workshops: "Charger les stages et ateliers suivants ..."
load_the_next_events: "Charger les évènements suivants ..."
no_passed_events: "Aucun évènement passé"
dates: "Dates :"
thank_you_your_payment_has_been_successfully_registered: "Merci. Votre paiement a bien été pris en compte !"
surname: "Nom"
@ -92,6 +93,7 @@ fr:
book: "Réserver"
description_is_required: "La description est requise."
name_is_required: "Le nom est requis."
all_themes: "Toutes les thématiques"
messages:
you_will_lose_any_unsaved_modification_if_you_quit_this_page: "Vous perdrez les modifications non enregistrées si vous quittez cette page"
@ -163,7 +165,7 @@ fr:
here_is_the_summary_of_the_slots_to_book_for_the_current_user: "Voici le récapitulatif des créneaux à réserver pour l'utilisateur courant :"
event:
# formulaire d'édition d'un événement (stage/atelier)
# formulaire d'édition d'un événement
title_is_required: "Le titre est requis."
matching_visual: "Visuel associé"
choose_a_picture: "Choisir une image"
@ -182,6 +184,8 @@ fr:
standard_rate: "Tarif standard"
0_=_free: "0 = gratuit"
tickets_available: "Places disponibles"
event_theme: "Thème de l'évènement"
age_range: "Tranche d'âge"
plan:
# formulaire d'édition d'une formule d'abonnement

View File

@ -78,7 +78,7 @@ en:
subscription_NAME_from_START_to_END: "Subscription - From %{START} to %{END}"
machine_reservation_DESCRIPTION: "Machine reservation - %{DESCRIPTION}"
training_reservation_DESCRIPTION: "Training reservation - %{DESCRIPTION}"
courses_and_workshops_reservation_DESCRIPTION: "Courses and Workshops reservation - %{DESCRIPTION}"
event_reservation_DESCRIPTION: "Event reservation - %{DESCRIPTION}"
full_price_ticket:
one: "One full price ticket"
other: "%{count} full price tickets"
@ -244,6 +244,8 @@ en:
event_id: "Event ID"
event_date: "Event Date"
event_name: "Event Name"
event_theme: "Theme"
age_range: "Age Range"
themes: "Themes"
components: "Components"
machines: "Machines"
@ -251,4 +253,6 @@ en:
bookings: "Bookings"
hours_number: "Hours number"
tickets_number: "Tickets number"
revenue: "Revenue"
revenue: "Revenue"
account_creation: "Account creation"
project_publication: "Project publication"

View File

@ -78,7 +78,7 @@ fr:
subscription_NAME_from_START_to_END: "Abonnement - Du %{START} au %{END}"
machine_reservation_DESCRIPTION: "Réservation Machine - %{DESCRIPTION}"
training_reservation_DESCRIPTION: "Réservation Formation - %{DESCRIPTION}"
courses_and_workshops_reservation_DESCRIPTION: "Réservation Ateliers et Stages - %{DESCRIPTION}"
event_reservation_DESCRIPTION: "Réservation Évènement - %{DESCRIPTION}"
full_price_ticket:
one: "Une place plein tarif"
other: "%{count} places plein tarif"
@ -244,6 +244,8 @@ fr:
event_id: "ID Évènement"
event_date: "Date Évènement"
event_name: "Nom Évènement"
event_theme: "Thématique"
age_range: "Tranche d'âge"
themes: "Thèmes"
components: "Composants"
machines: "Machines"
@ -252,7 +254,5 @@ fr:
hours_number: "Nombre d'heures"
tickets_number: "Nombre de places"
revenue: "Chiffre d'affaires"
course: "Stage"
workshop: "Atelier"
account_creation: "Création de compte"
project_publication: "Publication de projet"
project_publication: "Publication de projet"

View File

@ -87,7 +87,9 @@ Rails.application.routes.draw do
# for admin
resources :trainings
resources :credits
resources :categories, only: [:index]
resources :categories
resources :event_themes
resources :age_ranges
resources :statistics, only: [:index]
resources :custom_assets, only: [:show, :create, :update]
resources :tags

View File

@ -0,0 +1,13 @@
class RenameCoursesWorkshopsToEvents < ActiveRecord::Migration
def up
execute "UPDATE statistic_indices
SET label='Évènements'
WHERE es_type_key='event';"
end
def down
execute "UPDATE statistic_indices
SET label='Ateliers/Stages'
WHERE es_type_key='event';"
end
end

View File

@ -0,0 +1,9 @@
class CreateEventThemes < ActiveRecord::Migration
def change
create_table :event_themes do |t|
t.string :name
t.timestamps null: false
end
end
end

View File

@ -0,0 +1,8 @@
class CreateEventsEventThemes < ActiveRecord::Migration
def change
create_table :events_event_themes do |t|
t.belongs_to :event, index: true, foreign_key: true
t.belongs_to :event_theme, index: true, foreign_key: true
end
end
end

View File

@ -0,0 +1,9 @@
class CreateAgeRanges < ActiveRecord::Migration
def change
create_table :age_ranges do |t|
t.string :range
t.timestamps null: false
end
end
end

View File

@ -0,0 +1,5 @@
class AddAgeRangeIdToEvent < ActiveRecord::Migration
def change
add_column :events, :age_range_id, :integer
end
end

View File

@ -0,0 +1,3 @@
class RenameRangeToNameFromAgeRange < ActiveRecord::Migration
rename_column :age_ranges, :range, :name
end

View File

@ -0,0 +1,6 @@
class AddSlugToCategories < ActiveRecord::Migration
def change
add_column :categories, :slug, :string
add_index :categories, :slug, unique: true
end
end

View File

@ -0,0 +1,6 @@
class AddSlugToAgeRange < ActiveRecord::Migration
def change
add_column :age_ranges, :slug, :string
add_index :age_ranges, :slug, unique: true
end
end

View File

@ -0,0 +1,6 @@
class AddSlugToEventTheme < ActiveRecord::Migration
def change
add_column :event_themes, :slug, :string
add_index :event_themes, :slug, unique: true
end
end

View File

@ -0,0 +1,6 @@
class AddEventThemeAndAgeRangeToStatisticField < ActiveRecord::Migration
def change
StatisticField.create!({key:'eventTheme', label:I18n.t('statistics.event_theme'), statistic_index_id: 4, data_type: 'text'})
StatisticField.create!({key:'ageRange', label:I18n.t('statistics.age_range'), statistic_index_id: 4, data_type: 'text'})
end
end

View File

@ -0,0 +1,13 @@
class AddSlugsToExistingCategories < ActiveRecord::Migration
def up
execute 'UPDATE categories
SET slug=name
WHERE slug IS NULL;'
end
def down
execute 'UPDATE categories
SET slug=NULL
WHERE slug=name;'
end
end

View File

@ -27,7 +27,9 @@ if StatisticField.count == 0
{key:'components', label:I18n.t('statistics.components'), statistic_index_id: 6, data_type: 'list'},
{key:'machines', label:I18n.t('statistics.machines'), statistic_index_id: 6, data_type: 'list'},
{key:'name', label:I18n.t('statistics.event_name'), statistic_index_id: 4, data_type: 'text'},
{key:'userId', label:I18n.t('statistics.user_id'), statistic_index_id: 7, data_type: 'index'}
{key:'userId', label:I18n.t('statistics.user_id'), statistic_index_id: 7, data_type: 'index'},
{key:'eventTheme', label:I18n.t('statistics.event_theme'), statistic_index_id: 4, data_type: 'text'},
{key:'ageRange', label:I18n.t('statistics.age_range'), statistic_index_id: 4, data_type: 'text'}
])
end
@ -55,9 +57,6 @@ end
if StatisticSubType.count == 0
StatisticSubType.create!([
{key: 'Stage', label:I18n.t('statistics.course'), statistic_types: StatisticIndex.find_by(es_type_key: 'event').statistic_types},
{key: 'Atelier', label:I18n.t('statistics.workshop'), statistic_types: StatisticIndex.find_by(es_type_key: 'event').statistic_types},
{key: 'created', label:I18n.t('statistics.account_creation'), statistic_types: StatisticIndex.find_by(es_type_key: 'account').statistic_types},
{key: 'published', label:I18n.t('statistics.project_publication'), statistic_types: StatisticIndex.find_by(es_type_key: 'project').statistic_types}
])

View File

@ -408,6 +408,14 @@ http://localhost:9200/stats/_mapping?pretty
},
"userId" : {
"type" : "long"
},
"ageRange" : {
"type" : "string",
"index" : "not_analyzed"
},
"eventTheme" : {
"type" : "string",
"index" : "not_analyzed"
}
}
}

View File

@ -72,6 +72,28 @@ namespace :fablab do
}
}';`
end
es_add_event_filters
end
desc 'add event filters to statistics'
task es_add_event_filters: :environment do
es_add_event_filters
end
def es_add_event_filters
`curl -XPUT http://#{ENV["ELASTICSEARCH_HOST"]}:9200/stats/event/_mapping -d '
{
"properties": {
"ageRange": {
"type": "string",
"index" : "not_analyzed"
},
"eventTheme": {
"type": "string",
"index" : "not_analyzed"
}
}
}';`
end
desc "sync all/one project in elastic search index"

13
test/fixtures/age_ranges.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
youngs:
id: 1
name: De 10 à 21 ans
adults:
id: 2
name: A partir de 18 ans
seniors:
id: 3
name: A partir de 65 ans

7
test/fixtures/event_themes.yml vendored Normal file
View File

@ -0,0 +1,7 @@
diy:
id: 1
name: Do it yourself
robot:
id: 2
name: Robot

View File

@ -12,6 +12,7 @@ event_2:
nb_total_places: 10
nb_free_places: 10
recurrence_id: 1
age_range_id: 2
event_3:
id: 3

5
test/fixtures/events_event_themes.yml vendored Normal file
View File

@ -0,0 +1,5 @@
events_event_themes_1:
id: 1
event_id: 1
event_theme_id: 1

View File

@ -88,3 +88,23 @@ statistic_field_10:
created_at: 2016-04-04 14:11:33.423307000 Z
updated_at: 2016-04-04 14:11:33.423307000 Z
data_type: text
statistic_field_11:
id: 11
statistic_index_id: 4
key: ageRange
label: Tranche d'âge
created_at: 2016-06-30 12:10:49.812226000 Z
updated_at: 2016-06-30 12:10:49.812226000 Z
data_type: text
statistic_field_12:
id: 12
statistic_index_id: 4
key: eventTheme
label: Thématique
created_at: 2016-06-30 12:10:49.814331000 Z
updated_at: 2016-06-30 12:10:49.814331000 Z
data_type: text

23
test/models/event_test.rb Normal file
View File

@ -0,0 +1,23 @@
require 'test_helper'
class EventTest < ActiveSupport::TestCase
test 'event must have a category' do
e = Event.first
assert_not_nil e.categories.first
end
test 'event must have a theme' do
e = Event.find(1)
assert_not_empty e.themes
end
test 'event must have an age range' do
e = Event.find(2)
assert_not_nil e.age_range.name
end
test 'event must not have any age range' do
e = Event.find(3)
assert_nil e.age_range
end
end