mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
Merge branch 'dev' for release 2.2.0
This commit is contained in:
commit
6e7d5504a6
3
.gitignore
vendored
3
.gitignore
vendored
@ -32,3 +32,6 @@
|
||||
.DS_Store
|
||||
|
||||
.vagrant
|
||||
.docker
|
||||
|
||||
/plugins/*
|
||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,5 +1,18 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## v2.2.0 2016 June 16
|
||||
- Built-in support for extensions plug-ins
|
||||
- User profile form: social networks links, personal website link, job and change profile visibility (public / private)
|
||||
- User public profile: UI re-design with possible admin's customization
|
||||
- Admin: Invoices list and users list are now loaded per 10 items to improve pages load time
|
||||
- Admin: select member (eg. to buy a subscription for a member) is now loading the user's list dynamically when you type
|
||||
- Project collaborators selection is now using a list dynamically loaded as you type
|
||||
- Admin: select a training before monitoring its reservations -> improves page load time
|
||||
- API: GET /api/trainings do not load nor send the associated availabilities until they are requested
|
||||
- List of members is now loaded 10 members by 10, to improve page load time
|
||||
- [TODO DEPLOY] Regenerate the theme stylesheet (easy way: Customization/General/Main colour -> "Save")
|
||||
- [TODO DEPLOY] `bundle install` and `rake db:migrate`
|
||||
|
||||
## v2.1.2 2016 May 24
|
||||
- Fix a bug: Google Analytics was not loaded and did not report any stats
|
||||
|
||||
|
16
Gemfile.lock
16
Gemfile.lock
@ -155,8 +155,8 @@ GEM
|
||||
ffi (1.9.8)
|
||||
figaro (1.1.0)
|
||||
thor (~> 0.14)
|
||||
font-awesome-rails (4.3.0.0)
|
||||
railties (>= 3.2, < 5.0)
|
||||
font-awesome-rails (4.6.3.0)
|
||||
railties (>= 3.2, < 5.1)
|
||||
foreman (0.78.0)
|
||||
thor (~> 0.19.1)
|
||||
forgery (0.6.0)
|
||||
@ -207,8 +207,8 @@ GEM
|
||||
twitter_cldr (~> 3.1)
|
||||
mime-types (2.99)
|
||||
mini_magick (4.2.0)
|
||||
mini_portile (0.6.2)
|
||||
minitest (5.8.3)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (5.8.4)
|
||||
minitest-reporters (1.1.8)
|
||||
ansi
|
||||
builder
|
||||
@ -226,8 +226,8 @@ GEM
|
||||
net-ssh-gateway (1.2.0)
|
||||
net-ssh (>= 2.6.5)
|
||||
netrc (0.10.3)
|
||||
nokogiri (1.6.6.4)
|
||||
mini_portile (~> 0.6.0)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
notify_with (0.0.2)
|
||||
jbuilder (~> 2.0)
|
||||
rails (>= 4.2.0)
|
||||
@ -283,7 +283,7 @@ GEM
|
||||
activesupport (>= 4.2.0.beta, < 5.0)
|
||||
nokogiri (~> 1.6.0)
|
||||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.2)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails-observers (0.1.2)
|
||||
activemodel (~> 4.0)
|
||||
@ -298,7 +298,7 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.13.0)
|
||||
rake (10.4.2)
|
||||
rake (11.1.2)
|
||||
rb-fsevent (0.9.4)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
|
39
README.md
39
README.md
@ -17,7 +17,8 @@ FabManager is the FabLab management solution. It is web-based, open-source and t
|
||||
6. [ElasticSearch](#elasticsearch)<br/>
|
||||
6.1. [Install ElasticSearch on Ubuntu/Debian](#elasticsearch-on-debian)<br/>
|
||||
6.2. [Install ElasticSearch on MacOS X](#elasticsearch-on-macosx)<br/>
|
||||
6.3. [Setup ElasticSearch for the FabManager](#setup-fabmanager-in-elasticsearch)
|
||||
6.3. [Setup ElasticSearch for the FabManager](#setup-fabmanager-in-elasticsearch)<br/>
|
||||
6.4. [Backup and Restore](#backup-and-restore-elasticsearch)
|
||||
7. [Internationalization (i18n)](#i18n)<br/>
|
||||
7.1. [Translation](#i18n-translation)<br/>
|
||||
7.1.1. [Front-end translations](#i18n-translation-front)<br/>
|
||||
@ -26,8 +27,9 @@ FabManager is the FabLab management solution. It is web-based, open-source and t
|
||||
7.2.1. [Settings](#i18n-settings)<br/>
|
||||
7.2.2. [Applying changes](#i18n-apply)
|
||||
8. [Open Projects](#open-projects)
|
||||
9. [Known issues](#known-issues)
|
||||
10. [Related Documentation](#related-documentation)
|
||||
9. [Plugins](#plugins)
|
||||
10. [Known issues](#known-issues)
|
||||
11. [Related Documentation](#related-documentation)
|
||||
|
||||
|
||||
|
||||
@ -398,6 +400,14 @@ brew install homebrew/versions/elasticsearch17
|
||||
end
|
||||
```
|
||||
|
||||
<a name="backup-and-restore-elasticsearch"></a>
|
||||
### Backup and Restore
|
||||
|
||||
To backup and restore the ElasticSearch database, use the [elasticsearch-dump](https://github.com/taskrabbit/elasticsearch-dump) tool.
|
||||
|
||||
Dump the database with: `elasticdump --input=http://localhost:9200/stats --output=fablab_stats.json`.
|
||||
Restore it with: `elasticdump --input=fablab_stats.json --output=http://localhost:9200/stats`.
|
||||
|
||||
<a name="i18n"></a>
|
||||
## Internationalization (i18n)
|
||||
|
||||
@ -541,8 +551,25 @@ To start using this awesome feature, there are a few steps:
|
||||
- start your fab-manager app
|
||||
- export your projects to open-projects (if you already have projects created on your fab-manager, unless you can skip that part) executing this command: `bundle exec rake fablab:openlab:bulk_export`
|
||||
|
||||
**IMPORTANT: please run your server in production mode.**
|
||||
|
||||
Go to your projects gallery and enjoy seeing your projects available from everywhere ! That's all.
|
||||
|
||||
<a name="plugins"></a>
|
||||
## Plugins
|
||||
|
||||
Fab-manager has a system of plugins mainly inspired by [Discourse](https://github.com/discourse/discourse) architecture.
|
||||
|
||||
It enables you to write plugins which can:
|
||||
- have its proper models and database tables
|
||||
- have its proper assets (js & css)
|
||||
- override existing behaviours of Fab-manager
|
||||
- add features by adding views, controllers, ect...
|
||||
|
||||
To install a plugin, you just have to copy the plugin folder which contains its code into the folder `plugins` of Fab-manager.
|
||||
|
||||
You can see an example on the [repo of navinum gamification plugin](https://github.com/LaCasemate/navinum-gamification)
|
||||
|
||||
<a name="known-issues"></a>
|
||||
## Known issues
|
||||
|
||||
@ -581,6 +608,12 @@ Go to your projects gallery and enjoy seeing your projects available from everyw
|
||||
|
||||
DO NOT do this in a production environment, as this would lead to a serious security issue.
|
||||
|
||||
- Using another DBMS than PostgreSQL is not supported, because of some PostgreSQL specific instructions:
|
||||
- `app/controllers/api/members_controllers.rb@list` is using `ILIKE`
|
||||
- `app/controllers/api/invoices_controllers.rb@list` is using `ILIKE` and `date_trunc()`
|
||||
- `db/migrate/20160613093842_create_unaccent_function.rb` is using [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html) modules and defines a PL/pgSQL function (`f_unaccent()`)
|
||||
- `app/controllers/api/members_controllers.rb@search` is using `f_unaccent()` (see above) and `regexp_replace()`
|
||||
|
||||
<a name="related-documentation"></a>
|
||||
## Related Documentation
|
||||
|
||||
|
BIN
app/assets/images/social/dailymotion.png
Normal file
BIN
app/assets/images/social/dailymotion.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1015 B |
BIN
app/assets/images/social/echosciences.png
Normal file
BIN
app/assets/images/social/echosciences.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -72,3 +72,7 @@
|
||||
//= require_tree ./services
|
||||
//= require_tree ./directives
|
||||
//= require_tree ./filters
|
||||
|
||||
<%
|
||||
PluginRegistry.javascripts.each { |js| require_asset(js) }
|
||||
%>
|
@ -3,15 +3,34 @@
|
||||
##
|
||||
# Controller used in the admin invoices listing page
|
||||
##
|
||||
Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'Invoice', '$uibModal', "growl", "$filter", 'Setting', 'settings', '_t'
|
||||
, ($scope, $state, Invoice, $uibModal, growl, $filter, Setting, settings, _t) ->
|
||||
Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'Invoice', 'invoices', '$uibModal', "growl", "$filter", 'Setting', 'settings', '_t'
|
||||
, ($scope, $state, Invoice, invoices, $uibModal, growl, $filter, Setting, settings, _t) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# number of invoices loaded each time we click on 'load more...'
|
||||
INVOICES_PER_PAGE = 20
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## List of all users invoices
|
||||
$scope.invoices = Invoice.query()
|
||||
$scope.invoices = invoices
|
||||
|
||||
# Invoices filters
|
||||
$scope.searchInvoice =
|
||||
date: null
|
||||
name: ''
|
||||
reference: ''
|
||||
|
||||
# currently displayed page of invoices (search results)
|
||||
$scope.page = 1
|
||||
|
||||
# true when all invoices are loaded
|
||||
$scope.noMoreResults = false
|
||||
|
||||
## Default invoices ordering/sorting
|
||||
$scope.orderInvoice = '-reference'
|
||||
@ -61,6 +80,9 @@ Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'I
|
||||
else
|
||||
$scope.orderInvoice = orderBy
|
||||
|
||||
resetSearchInvoice()
|
||||
invoiceSearch()
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -323,12 +345,35 @@ Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'I
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when any of the filters changes.
|
||||
# Full reload the results list
|
||||
##
|
||||
$scope.handleFilterChange = ->
|
||||
resetSearchInvoice()
|
||||
invoiceSearch()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback for the 'load more' button.
|
||||
# Will load the next results of the current search, if any
|
||||
##
|
||||
$scope.showNextInvoices = ->
|
||||
$scope.page += 1
|
||||
invoiceSearch(true)
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
if (!invoices[0] || invoices[0].maxInvoices <= $scope.invoices.length)
|
||||
$scope.noMoreResults = true
|
||||
|
||||
# retrieve settings from the DB through the API
|
||||
$scope.invoice.legals.content = settings['invoice_legals']
|
||||
$scope.invoice.text.content = settings['invoice_text']
|
||||
@ -381,6 +426,40 @@ Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'I
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Reinitialize the context of invoices' search to display new results set
|
||||
##
|
||||
resetSearchInvoice = ->
|
||||
$scope.page = 1
|
||||
$scope.noMoreResults = false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Run a search query with the current parameters set concerning invoices, then affect or concat the results
|
||||
# to $scope.invoices
|
||||
# @param concat {boolean} if true, the result will be append to $scope.invoices instead of being affected
|
||||
##
|
||||
invoiceSearch = (concat) ->
|
||||
Invoice.list {
|
||||
query:
|
||||
number: $scope.searchInvoice.reference
|
||||
customer: $scope.searchInvoice.name
|
||||
date: $scope.searchInvoice.date
|
||||
order_by: $scope.orderInvoice
|
||||
page: $scope.page
|
||||
size: INVOICES_PER_PAGE
|
||||
}, (invoices) ->
|
||||
if concat
|
||||
$scope.invoices = $scope.invoices.concat(invoices)
|
||||
else
|
||||
$scope.invoices = invoices
|
||||
|
||||
if (!invoices[0] || invoices[0].maxInvoices <= $scope.invoices.length)
|
||||
$scope.noMoreResults = true
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
||||
|
@ -105,8 +105,15 @@ class MembersController
|
||||
##
|
||||
# Controller used in the members/groups management page
|
||||
##
|
||||
Application.Controllers.controller "AdminMembersController", ["$scope", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t'
|
||||
, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t) ->
|
||||
Application.Controllers.controller "AdminMembersController", ["$scope", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member'
|
||||
, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# number of users loaded each time we click on 'load more...'
|
||||
USERS_PER_PAGE = 20
|
||||
|
||||
|
||||
|
||||
@ -115,12 +122,19 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
|
||||
## members list
|
||||
$scope.members = membersPromise
|
||||
|
||||
$scope.member =
|
||||
## Members plain-text filtering. Default: not filtered
|
||||
searchText: ''
|
||||
## Members ordering/sorting. Default: not sorted
|
||||
order: 'id'
|
||||
## currently displayed page of members
|
||||
page: 1
|
||||
## true when all members where loaded
|
||||
noMore: false
|
||||
|
||||
## admins list
|
||||
$scope.admins = adminsPromise.admins
|
||||
|
||||
## Members ordering/sorting. Default: not sorted
|
||||
$scope.orderMember = null
|
||||
|
||||
## Admins ordering/sorting. Default: not sorted
|
||||
$scope.orderAdmin = null
|
||||
|
||||
@ -131,10 +145,13 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
|
||||
# @param orderBy {string} ordering criterion
|
||||
##
|
||||
$scope.setOrderMember = (orderBy)->
|
||||
if $scope.orderMember == orderBy
|
||||
$scope.orderMember = '-'+orderBy
|
||||
if $scope.member.order == orderBy
|
||||
$scope.member.order = '-'+orderBy
|
||||
else
|
||||
$scope.orderMember = orderBy
|
||||
$scope.member.order = orderBy
|
||||
|
||||
resetSearchMember()
|
||||
memberSearch()
|
||||
|
||||
|
||||
|
||||
@ -170,8 +187,34 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback for the 'load more' button.
|
||||
# Will load the next results of the current search, if any
|
||||
##
|
||||
$scope.showNextMembers = ->
|
||||
$scope.member.page += 1
|
||||
memberSearch(true)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when the search field content changes: reload the search results
|
||||
##
|
||||
$scope.updateTextSearch = ->
|
||||
resetSearchMember()
|
||||
memberSearch()
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
if (!membersPromise[0] || membersPromise[0].maxMembers <= $scope.members.length)
|
||||
$scope.member.noMore = true
|
||||
|
||||
##
|
||||
# Iterate through the provided array and return the index of the requested admin
|
||||
# @param admins {Array} full list of users with role 'admin'
|
||||
@ -182,6 +225,35 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
|
||||
(admins.map (admin)->
|
||||
admin.id
|
||||
).indexOf(id)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Reinitialize the context of members's search to display new results set
|
||||
##
|
||||
resetSearchMember = ->
|
||||
$scope.member.noMore = false
|
||||
$scope.member.page = 1
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Run a search query with the current parameters set ($scope.member[searchText,order,page])
|
||||
# and affect or append the result in $scope.members, depending on the concat parameter
|
||||
# @param concat {boolean} if true, the result will be append to $scope.members instead of being affected
|
||||
##
|
||||
memberSearch = (concat) ->
|
||||
Member.list { query: { search: $scope.member.searchText, order_by: $scope.member.order, page: $scope.member.page, size: USERS_PER_PAGE } }, (members) ->
|
||||
if concat
|
||||
$scope.members = $scope.members.concat(members)
|
||||
else
|
||||
$scope.members = members;
|
||||
|
||||
if (!members[0] || members[0].maxMembers <= $scope.members.length)
|
||||
$scope.member.noMore = true
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
||||
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
##
|
||||
# Controller used in the prices edition page
|
||||
##
|
||||
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', 'Training', 'TrainingsPricing', 'Machine', '$filter', 'Credit', 'Pricing', 'Plan', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', '_t'
|
||||
, ($scope, $state, $uibModal, Training, TrainingsPricing, Machine, $filter, Credit, Pricing, Plan, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, _t) ->
|
||||
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', 'TrainingsPricing', '$filter', 'Credit', 'Pricing', 'Plan', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', '_t'
|
||||
, ($scope, $state, $uibModal, TrainingsPricing, $filter, Credit, Pricing, Plan, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, _t) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
## List of machines prices (not considering any plan)
|
||||
@ -20,19 +20,19 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.groups = groups
|
||||
|
||||
## Associate free machine hours with subscriptions
|
||||
$scope.machineCredits = []
|
||||
$scope.machineCredits = machineCreditsPromise
|
||||
|
||||
## Array of associations (plan <-> training)
|
||||
$scope.trainingCredits = []
|
||||
$scope.trainingCredits = trainingCreditsPromise
|
||||
|
||||
## Associate a plan with all its trainings ids
|
||||
$scope.trainingCreditsGroups = {}
|
||||
|
||||
## List of trainings
|
||||
$scope.trainings = []
|
||||
$scope.trainings = trainingsPromise
|
||||
|
||||
## List of machines
|
||||
$scope.machines = []
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## The plans list ordering. Default: by group
|
||||
$scope.orderPlans = 'group_id'
|
||||
@ -320,23 +320,13 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
##
|
||||
initialize = ->
|
||||
|
||||
Credit.query({creditable_type: 'Training'}).$promise.then (data)->
|
||||
$scope.trainingCredits = data
|
||||
$scope.trainingCreditsGroups = groupCreditsByPlan(data)
|
||||
$scope.trainingCreditsGroups = groupCreditsByPlan($scope.trainingCredits)
|
||||
|
||||
## adds empty array for plan which hasn't any credits yet
|
||||
for plan in $scope.plans
|
||||
unless $scope.trainingCreditsGroups[plan.id]?
|
||||
$scope.trainingCreditsGroups[plan.id] = []
|
||||
## adds empty array for plan which hasn't any credits yet
|
||||
for plan in $scope.plans
|
||||
unless $scope.trainingCreditsGroups[plan.id]?
|
||||
$scope.trainingCreditsGroups[plan.id] = []
|
||||
|
||||
Credit.query({creditable_type: 'Machine'}).$promise.then (data)->
|
||||
$scope.machineCredits = data
|
||||
|
||||
Training.query().$promise.then (data)->
|
||||
$scope.trainings = data
|
||||
|
||||
Machine.query().$promise.then (data)->
|
||||
$scope.machines = data
|
||||
|
||||
|
||||
##
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "SettingsController", ["$scope", 'Setting', 'growl', 'settingsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'CSRF', '_t'
|
||||
($scope, Setting, growl, settingsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, CSRF, _t) ->
|
||||
Application.Controllers.controller "SettingsController", ["$scope", 'Setting', 'growl', 'settingsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'profileImageFile', 'CSRF', '_t'
|
||||
($scope, Setting, growl, settingsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, profileImageFile, CSRF, _t) ->
|
||||
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
logo: "/api/custom_assets"
|
||||
logoBlack: "/api/custom_assets"
|
||||
favicon: "/api/custom_assets"
|
||||
profileImage: "/api/custom_assets"
|
||||
|
||||
## Form actions on the above URL
|
||||
$scope.methods =
|
||||
@ -27,6 +28,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
logo: "post"
|
||||
logoBlack: "post"
|
||||
favicon: "post"
|
||||
profileImage: "post"
|
||||
|
||||
## Are we uploading the files currently (if so, display the loader)
|
||||
$scope.loader =
|
||||
@ -55,6 +57,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
$scope.customLogo = logoFile.custom_asset
|
||||
$scope.customLogoBlack = logoBlackFile.custom_asset
|
||||
$scope.customFavicon = faviconFile.custom_asset
|
||||
$scope.profileImage = profileImageFile.custom_asset
|
||||
|
||||
$scope.enableMove =
|
||||
name: 'booking_move_enable'
|
||||
@ -136,17 +139,21 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
$scope.actionUrl.cgv += '/cgv-file' unless $scope.actionUrl.cgv.indexOf('/cgv-file') > 0
|
||||
$scope.loader.cgv = false
|
||||
else if content.custom_asset.name is 'logo-file'
|
||||
$scope.logoFile = content.custom_asset
|
||||
$scope.customLogo = content.custom_asset
|
||||
$scope.methods.logo = 'put'
|
||||
$scope.actionUrl.logo += '/logo-file' unless $scope.actionUrl.logo.indexOf('/logo-file') > 0
|
||||
else if content.custom_asset.name is 'logo-black-file'
|
||||
$scope.logoBlackFile = content.custom_asset
|
||||
$scope.customLogoBlack = content.custom_asset
|
||||
$scope.methods.logoBlack = 'put'
|
||||
$scope.actionUrl.logoBlack += '/logo-black-file' unless $scope.actionUrl.logoBlack.indexOf('/logo-black-file') > 0
|
||||
else if content.custom_asset.name is 'favicon-file'
|
||||
$scope.faviconFile = content.custom_asset
|
||||
$scope.customFavicon = content.custom_asset
|
||||
$scope.methods.favicon = 'put'
|
||||
$scope.actionUrl.favicon += '/favicon-file' unless $scope.actionUrl.favicon.indexOf('/favicon-file') > 0
|
||||
else if content.custom_asset.name is 'profile-image-file'
|
||||
$scope.profileImage = content.custom_asset
|
||||
$scope.methods.profileImage = 'put'
|
||||
$scope.actionUrl.profileImage += '/profile-image-file' unless $scope.actionUrl.profileImage.indexOf('/profile-image-file') > 0
|
||||
|
||||
|
||||
|
||||
@ -188,6 +195,9 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
if faviconFile.custom_asset
|
||||
$scope.methods.favicon = 'put'
|
||||
$scope.actionUrl.favicon += '/favicon-file'
|
||||
if profileImageFile.custom_asset
|
||||
$scope.methods.profileImage = 'put'
|
||||
$scope.actionUrl.profileImage += '/profile-image-file'
|
||||
|
||||
|
||||
# init the controller (call at the end !)
|
||||
|
@ -13,6 +13,10 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
|
||||
## simplified list of machines
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## Training to monitor, binded with drop-down selection
|
||||
$scope.monitoring =
|
||||
training: null
|
||||
|
||||
## list of training availabilies, grouped by date
|
||||
$scope.groupedAvailabilities = {}
|
||||
|
||||
@ -188,16 +192,18 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when the drop-down selection is changed.
|
||||
# The selected training details will be loaded from the API and rendered into the accordions.
|
||||
##
|
||||
$scope.selectTrainingToMonitor = ->
|
||||
Training.get {id: $scope.monitoring.training.id}, (training) ->
|
||||
$scope.groupedAvailabilities = groupAvailabilities([training])
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
$scope.groupedAvailabilities = groupAvailabilities($scope.trainings)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Group the trainings availabilites by trainings and by dates and return the resulting tree
|
||||
# @param trainings {Array} $scope.trainings is expected here
|
||||
@ -223,8 +229,4 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
|
||||
tree[training.name][start.year()][start.month()][start.date()].push( availability )
|
||||
tree
|
||||
|
||||
|
||||
|
||||
# init the controller (call at the end !)
|
||||
initialize()
|
||||
]
|
||||
|
@ -1,7 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "DashboardController", ["$scope", 'memberPromise', ($scope, memberPromise) ->
|
||||
Application.Controllers.controller "DashboardController", ["$scope", 'memberPromise', 'SocialNetworks', ($scope, memberPromise, SocialNetworks) ->
|
||||
|
||||
## Current user's profile
|
||||
$scope.user = memberPromise
|
||||
|
||||
## List of social networks associated with this user and toggle 'show all' state
|
||||
$scope.social =
|
||||
showAllLinks: false
|
||||
networks: SocialNetworks
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
$scope.social.networks = filterNetworks()
|
||||
|
||||
##
|
||||
# Filter social network or website that are associated with the profile of the user provided in promise
|
||||
# and return the filtered networks
|
||||
# @return {Array}
|
||||
##
|
||||
filterNetworks = ->
|
||||
networks = [];
|
||||
for network in SocialNetworks
|
||||
if $scope.user.profile[network] && $scope.user.profile[network].length > 0
|
||||
networks.push(network);
|
||||
networks
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
|
||||
]
|
||||
|
@ -157,9 +157,6 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
$scope.reserve.toReserve = !$scope.reserve.toReserve
|
||||
if user.role isnt 'admin'
|
||||
$scope.ctrl.member = user
|
||||
else
|
||||
Member.query (members) ->
|
||||
$scope.members = members
|
||||
else
|
||||
$scope.reserve.toReserve = !$scope.reserve.toReserve
|
||||
|
||||
@ -173,7 +170,9 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
resetEventReserve()
|
||||
$scope.reserveSuccess = false
|
||||
if $scope.ctrl.member
|
||||
getReservations($scope.event.id, 'Event', $scope.ctrl.member.id)
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
getReservations($scope.event.id, 'Event', $scope.ctrl.member.id)
|
||||
|
||||
|
||||
|
||||
@ -323,9 +322,6 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
if $scope.currentUser
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
else
|
||||
Member.query (members) ->
|
||||
$scope.members = members
|
||||
|
||||
# check that the event's reduced rate is initialized
|
||||
if !$scope.event.reduced_amount
|
||||
|
@ -339,9 +339,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.ctrl =
|
||||
member: {}
|
||||
|
||||
## fablab users list
|
||||
$scope.members = []
|
||||
|
||||
## current machine to reserve
|
||||
$scope.machine = {}
|
||||
|
||||
@ -491,7 +488,9 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
updateCartPrice()
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
@ -637,9 +636,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
else
|
||||
Member.query {requested_attributes:'[subscription,credits]'}, (members) ->
|
||||
$scope.members = members
|
||||
|
||||
$scope.machine = Machine.get {id: $stateParams.id}
|
||||
, ->
|
||||
|
@ -44,7 +44,8 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
})
|
||||
|
||||
|
||||
$scope.adminNavLinks = [
|
||||
Fablab.adminNavLinks = Fablab.adminNavLinks || []
|
||||
Fablab.adminNavLinks = [
|
||||
{
|
||||
state: 'app.admin.trainings'
|
||||
linkText: 'trainings_monitoring'
|
||||
@ -95,5 +96,7 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
linkText: 'customization'
|
||||
linkIcon: 'gear'
|
||||
}
|
||||
]
|
||||
].concat(Fablab.adminNavLinks)
|
||||
|
||||
$scope.adminNavLinks = Fablab.adminNavLinks
|
||||
]
|
||||
|
@ -3,11 +3,58 @@
|
||||
##
|
||||
# Controller used in the members listing page
|
||||
##
|
||||
Application.Controllers.controller "MembersController", ["$scope", 'membersPromise', ($scope, membersPromise) ->
|
||||
Application.Controllers.controller "MembersController", ["$scope", 'Member', 'membersPromise', ($scope, Member, membersPromise) ->
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# number of invoices loaded each time we click on 'load more...'
|
||||
MEMBERS_PER_PAGE = 10
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## currently displayed page of members
|
||||
$scope.page = 1
|
||||
|
||||
## members list
|
||||
$scope.members = membersPromise
|
||||
|
||||
# true when all members are loaded
|
||||
$scope.noMoreResults = false
|
||||
|
||||
##
|
||||
# Callback for the 'load more' button.
|
||||
# Will load the next results of the current search, if any
|
||||
##
|
||||
$scope.showNextMembers = ->
|
||||
$scope.page += 1
|
||||
Member.query {
|
||||
requested_attributes:'[profile]',
|
||||
page: $scope.page,
|
||||
size: MEMBERS_PER_PAGE
|
||||
}, (members) ->
|
||||
$scope.members = $scope.members.concat(members)
|
||||
|
||||
if (!members[0] || members[0].maxMembers <= $scope.members.length)
|
||||
$scope.noMoreResults = true
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
if (!membersPromise[0] || membersPromise[0].maxMembers <= $scope.members.length)
|
||||
$scope.noMoreResults = true
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
]
|
||||
|
||||
|
||||
@ -211,8 +258,38 @@ Application.Controllers.controller "EditProfileController", ["$scope", "$rootSco
|
||||
##
|
||||
# Controller used on the public user's profile page (seeing another user's profile)
|
||||
##
|
||||
Application.Controllers.controller "ShowProfileController", ["$scope", "$stateParams", 'Member', 'memberPromise', ($scope, $stateParams, Member, memberPromise) ->
|
||||
Application.Controllers.controller "ShowProfileController", ["$scope", 'memberPromise', 'SocialNetworks', ($scope, memberPromise, SocialNetworks) ->
|
||||
|
||||
## Selected user's informations
|
||||
$scope.user = memberPromise # DEPENDENCY WITH NAVINUM GAMIFICATION PLUGIN !!!!
|
||||
|
||||
## List of social networks associated with this user and toggle 'show all' state
|
||||
$scope.social =
|
||||
showAllLinks: false
|
||||
networks: SocialNetworks
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
$scope.social.networks = filterNetworks()
|
||||
|
||||
##
|
||||
# Filter social network or website that are associated with the profile of the user provided in promise
|
||||
# and return the filtered networks
|
||||
# @return {Array}
|
||||
##
|
||||
filterNetworks = ->
|
||||
networks = [];
|
||||
for network in SocialNetworks
|
||||
if $scope.user.profile[network] && $scope.user.profile[network].length > 0
|
||||
networks.push(network);
|
||||
networks
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
## Selected user's profile (id from the current URL)
|
||||
$scope.user = memberPromise
|
||||
]
|
||||
|
@ -35,7 +35,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
## plan to subscribe (shopping cart)
|
||||
$scope.selectedPlan = null
|
||||
|
||||
##
|
||||
## text that appears in the bottom-right box of the page (subscriptions rules details)
|
||||
$scope.subscriptionExplicationsAlert = subscriptionExplicationsPromise.setting.value
|
||||
|
||||
##
|
||||
@ -45,8 +45,11 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
$scope.updateMember = ->
|
||||
$scope.selectedPlan = null
|
||||
$scope.paidPlan = null
|
||||
$scope.group.id = $scope.ctrl.member.group_id
|
||||
$scope.group.change = false
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
$scope.group.id = $scope.ctrl.member.group_id
|
||||
|
||||
|
||||
|
||||
|
||||
@ -119,6 +122,19 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Test if the provided date is in the future
|
||||
# @param dateTime {Date}
|
||||
# @return {boolean}
|
||||
##
|
||||
$scope.isInFuture = (dateTime)->
|
||||
if moment().diff(moment(dateTime)) < 0
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
@ -130,22 +146,11 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
$scope.paidPlan = $scope.currentUser.subscribed_plan
|
||||
$scope.group.id = $scope.currentUser.group_id
|
||||
else
|
||||
Member.query {requested_attributes:'[subscription]'}, (members) ->
|
||||
membersNoPlan = []
|
||||
angular.forEach members, (v)->
|
||||
membersNoPlan.push v unless v.subscribed_plan
|
||||
$scope.members = membersNoPlan
|
||||
|
||||
$scope.$on 'devise:new-session', (event, user)->
|
||||
$scope.ctrl.member = user
|
||||
|
||||
|
||||
$scope.isInFuture = (dateTime)->
|
||||
if moment().diff(moment(dateTime)) < 0
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
##
|
||||
# Open a modal window which trigger the stripe payment process
|
||||
|
@ -24,7 +24,7 @@
|
||||
# - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ]
|
||||
##
|
||||
class ProjectsController
|
||||
constructor: ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document)->
|
||||
constructor: ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics)->
|
||||
|
||||
## Retrieve the list of machines from the server
|
||||
Machine.query().$promise.then (data)->
|
||||
@ -140,6 +140,17 @@ class ProjectsController
|
||||
$scope.project.project_steps_attributes.splice(index, 1)
|
||||
|
||||
|
||||
$scope.autoCompleteName = (nameLookup) ->
|
||||
unless nameLookup
|
||||
return
|
||||
asciiName = Diacritics.remove(nameLookup)
|
||||
|
||||
Member.search { query: asciiName }, (users) ->
|
||||
$scope.matchingMembers = users
|
||||
, (error)->
|
||||
console.error(error)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used on projects listing page
|
||||
@ -275,8 +286,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
##
|
||||
# Controller used in the project creation page
|
||||
##
|
||||
Application.Controllers.controller "NewProjectController", ["$scope", "$state", 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF'
|
||||
, ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF) ->
|
||||
Application.Controllers.controller "NewProjectController", ["$scope", "$state", 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'Diacritics'
|
||||
, ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, Diacritics) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
@ -290,16 +301,10 @@ Application.Controllers.controller "NewProjectController", ["$scope", "$state",
|
||||
project_steps_attributes: []
|
||||
project_caos_attributes: []
|
||||
|
||||
## Other members list (project collaborators)
|
||||
Member.query().$promise.then (data)->
|
||||
$scope.members = data.filter (m) ->
|
||||
m.id != $scope.currentUser.id
|
||||
.map (d) ->
|
||||
id: d.id
|
||||
name: d.name
|
||||
$scope.matchingMembers = []
|
||||
|
||||
## Using the ProjectsController
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document)
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics)
|
||||
]
|
||||
|
||||
|
||||
@ -307,8 +312,8 @@ Application.Controllers.controller "NewProjectController", ["$scope", "$state",
|
||||
##
|
||||
# Controller used in the project edition page
|
||||
##
|
||||
Application.Controllers.controller "EditProjectController", ["$scope", "$state", '$stateParams', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'projectPromise'
|
||||
, ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, projectPromise) ->
|
||||
Application.Controllers.controller "EditProjectController", ["$scope", "$state", '$stateParams', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'projectPromise', 'Diacritics'
|
||||
, ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, projectPromise, Diacritics) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
@ -320,16 +325,12 @@ Application.Controllers.controller "EditProjectController", ["$scope", "$state",
|
||||
## Retrieve the project's details, if an error occured, redirect the user to the projects list page
|
||||
$scope.project = projectPromise
|
||||
|
||||
## Other members list (project collaborators)
|
||||
Member.query().$promise.then (data)->
|
||||
$scope.members = data.filter (m) ->
|
||||
m.id != $scope.project.author_id
|
||||
.map (d) ->
|
||||
id: d.id
|
||||
name: d.name
|
||||
$scope.matchingMembers = $scope.project.project_users.map (u) ->
|
||||
id: u.id
|
||||
name: u.full_name
|
||||
|
||||
## Using the ProjectsController
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document)
|
||||
## Using the ProjectsController
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics)
|
||||
]
|
||||
|
||||
|
||||
|
@ -42,9 +42,6 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.ctrl =
|
||||
member: {}
|
||||
|
||||
## the full list of members, used by admin to select a user to interact with
|
||||
$scope.members = []
|
||||
|
||||
## list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = []
|
||||
for group in groupsPromise
|
||||
@ -126,11 +123,13 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
##
|
||||
$scope.updateMember = ->
|
||||
if $scope.ctrl.member
|
||||
Availability.trainings {member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
$scope.calendar.fullCalendar 'removeEvents'
|
||||
$scope.eventSources.push
|
||||
events: trainings
|
||||
textColor: 'black'
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
Availability.trainings {member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
$scope.calendar.fullCalendar 'removeEvents'
|
||||
$scope.eventSources.push
|
||||
events: trainings
|
||||
textColor: 'black'
|
||||
$scope.trainingIsValid = false
|
||||
$scope.paidTraining = null
|
||||
$scope.plansAreShown = false
|
||||
@ -324,9 +323,6 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
Member.get id: $scope.currentUser.id, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
else
|
||||
Member.query {requested_attributes:'[subscription,credits]'}, (members) ->
|
||||
$scope.members = members
|
||||
|
||||
|
||||
|
||||
|
34
app/assets/javascripts/directives/selectMember.coffee.erb
Normal file
34
app/assets/javascripts/directives/selectMember.coffee.erb
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
##
|
||||
# This directive will allow to select a member.
|
||||
# Please surround it with a ng-if directive to prevent it from being used by a non-admin user.
|
||||
# The resulting member will be set into the parent $scope (=> $scope.ctrl.member).
|
||||
# The directive takes an optional parameter "subscription" as a "boolean string" that will filter the user
|
||||
# which have a valid running subscription or not.
|
||||
# Usage: <select-member [subscription="false|true"]></select-member>
|
||||
##
|
||||
Application.Directives.directive 'selectMember', [ 'Diacritics', 'Member', (Diacritics, Member) ->
|
||||
{
|
||||
restrict: 'E'
|
||||
templateUrl: '<%= asset_path "shared/_member_select.html" %>'
|
||||
link: (scope, element, attributes) ->
|
||||
scope.autoCompleteName = (nameLookup) ->
|
||||
unless nameLookup
|
||||
return
|
||||
scope.isLoadingMembers = true
|
||||
asciiName = Diacritics.remove(nameLookup)
|
||||
|
||||
q = { query: asciiName }
|
||||
if attributes.subscription
|
||||
q['subscription'] = attributes.subscription
|
||||
|
||||
Member.search q, (users) ->
|
||||
scope.matchingMembers = users
|
||||
scope.isLoadingMembers = false
|
||||
, (error)->
|
||||
console.error(error)
|
||||
|
||||
}
|
||||
]
|
||||
|
23
app/assets/javascripts/directives/socialLink.coffee.erb
Normal file
23
app/assets/javascripts/directives/socialLink.coffee.erb
Normal file
@ -0,0 +1,23 @@
|
||||
Application.Directives.directive 'socialLink', [ ->
|
||||
{
|
||||
restrict: 'E'
|
||||
scope:
|
||||
network: '@?'
|
||||
user: '='
|
||||
templateUrl: '<%= asset_path "shared/_social_link.html" %>'
|
||||
link: (scope, element, attributes) ->
|
||||
if scope.network == 'dailymotion'
|
||||
scope.image = "<%= asset_path('social/dailymotion.png') %>"
|
||||
scope.altText = 'd'
|
||||
else if scope.network == 'echosciences'
|
||||
scope.image = "<%= asset_path('social/echosciences.png') %>"
|
||||
scope.altText = 'E)'
|
||||
else
|
||||
if scope.network == 'website'
|
||||
scope.faClass = 'fa-globe'
|
||||
else
|
||||
scope.faClass = 'fa-'+scope.network.replace('_', '-')
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -136,6 +136,16 @@ angular.module('application.router', ['ui.router']).
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "dashboard/profile.html" %>'
|
||||
controller: 'DashboardController'
|
||||
resolve:
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.logged.dashboard.profile', 'app.shared.public_profile']).$promise
|
||||
]
|
||||
.state 'app.logged.dashboard.settings',
|
||||
url: '/settings'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "dashboard/settings.html" %>'
|
||||
controller: 'EditProfileController'
|
||||
resolve:
|
||||
groups: ['Group', (Group)->
|
||||
@ -145,7 +155,7 @@ angular.module('application.router', ['ui.router']).
|
||||
AuthProvider.active().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.logged.dashboard.profile', 'app.shared.user']).$promise
|
||||
Translations.query(['app.logged.dashboard.settings', 'app.shared.user']).$promise
|
||||
]
|
||||
.state 'app.logged.dashboard.projects',
|
||||
url: '/projects'
|
||||
@ -201,7 +211,7 @@ angular.module('application.router', ['ui.router']).
|
||||
Member.get(id: $stateParams.id).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.logged.members_show').$promise
|
||||
Translations.query(['app.logged.members_show', 'app.shared.public_profile']).$promise
|
||||
]
|
||||
.state 'app.logged.members',
|
||||
url: '/members'
|
||||
@ -211,7 +221,7 @@ angular.module('application.router', ['ui.router']).
|
||||
controller: 'MembersController'
|
||||
resolve:
|
||||
membersPromise: ['Member', (Member)->
|
||||
Member.query({requested_attributes:'[profile]'}).$promise
|
||||
Member.query({requested_attributes:'[profile]', page: 1, size: 10}).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.logged.members').$promise
|
||||
@ -591,6 +601,18 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.admin.pricing').$promise
|
||||
]
|
||||
trainingsPromise: ['Training', (Training) ->
|
||||
Training.query().$promise
|
||||
]
|
||||
machineCreditsPromise: ['Credit', (Credit) ->
|
||||
Credit.query({creditable_type: 'Machine'}).$promise
|
||||
]
|
||||
machinesPromise: ['Machine', (Machine) ->
|
||||
Machine.query().$promise
|
||||
]
|
||||
trainingCreditsPromise: ['Credit', (Credit) ->
|
||||
Credit.query({creditable_type: 'Training'}).$promise
|
||||
]
|
||||
|
||||
# plans
|
||||
.state 'app.admin.plans',
|
||||
@ -659,6 +681,11 @@ angular.module('application.router', ['ui.router']).
|
||||
'invoice_logo'
|
||||
]").$promise
|
||||
]
|
||||
invoices: [ 'Invoice', (Invoice) ->
|
||||
Invoice.list({
|
||||
query: { number: '', customer: '', date: null, order_by: '-reference', page: 1, size: 20 }
|
||||
}).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.admin.invoices').$promise
|
||||
]
|
||||
@ -682,7 +709,7 @@ angular.module('application.router', ['ui.router']).
|
||||
controller: 'AuthentificationController'
|
||||
resolve:
|
||||
membersPromise: ['Member', (Member)->
|
||||
Member.query({requested_attributes:'[profile,group,subscription]'}).$promise
|
||||
Member.list({ query: { search: '', order_by: 'id', page: 1, size: 20 } }).$promise
|
||||
]
|
||||
adminsPromise: ['Admin', (Admin)->
|
||||
Admin.query().$promise
|
||||
@ -836,6 +863,9 @@ angular.module('application.router', ['ui.router']).
|
||||
faviconFile: ['CustomAsset', (CustomAsset) ->
|
||||
CustomAsset.get({name: 'favicon-file'}).$promise
|
||||
]
|
||||
profileImageFile: ['CustomAsset', (CustomAsset) ->
|
||||
CustomAsset.get({name: 'profile-image-file'}).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.admin.settings').$promise
|
||||
]
|
||||
|
121
app/assets/javascripts/services/diacritics.js
Executable file
121
app/assets/javascripts/services/diacritics.js
Executable file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Created by sylvain on 20/08/14.
|
||||
*
|
||||
* Based on http://jsperf.com/diacritics/12
|
||||
*/
|
||||
Application.Services.service('Diacritics', [
|
||||
"$resource", function($resource) {
|
||||
return {
|
||||
|
||||
/**
|
||||
* Fast diacritics removing function, works on the provided string.
|
||||
* Eg. "éêè çä bönjoür" will become "eee ca bonjour"
|
||||
* @param str {string} to ascii-fy
|
||||
* @returns {string} without diacritics
|
||||
*/
|
||||
remove: function(str) {
|
||||
var defaultDiacriticsRemovalap = [
|
||||
{'base':'A', 'letters':'\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'},
|
||||
{'base':'AA','letters':'\uA732'},
|
||||
{'base':'AE','letters':'\u00C6\u01FC\u01E2'},
|
||||
{'base':'AO','letters':'\uA734'},
|
||||
{'base':'AU','letters':'\uA736'},
|
||||
{'base':'AV','letters':'\uA738\uA73A'},
|
||||
{'base':'AY','letters':'\uA73C'},
|
||||
{'base':'B', 'letters':'\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'},
|
||||
{'base':'C', 'letters':'\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'},
|
||||
{'base':'D', 'letters':'\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779'},
|
||||
{'base':'DZ','letters':'\u01F1\u01C4'},
|
||||
{'base':'Dz','letters':'\u01F2\u01C5'},
|
||||
{'base':'E', 'letters':'\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'},
|
||||
{'base':'F', 'letters':'\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'},
|
||||
{'base':'G', 'letters':'\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'},
|
||||
{'base':'H', 'letters':'\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'},
|
||||
{'base':'I', 'letters':'\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'},
|
||||
{'base':'J', 'letters':'\u004A\u24BF\uFF2A\u0134\u0248'},
|
||||
{'base':'K', 'letters':'\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'},
|
||||
{'base':'L', 'letters':'\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'},
|
||||
{'base':'LJ','letters':'\u01C7'},
|
||||
{'base':'Lj','letters':'\u01C8'},
|
||||
{'base':'M', 'letters':'\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'},
|
||||
{'base':'N', 'letters':'\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'},
|
||||
{'base':'NJ','letters':'\u01CA'},
|
||||
{'base':'Nj','letters':'\u01CB'},
|
||||
{'base':'O', 'letters':'\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'},
|
||||
{'base':'OI','letters':'\u01A2'},
|
||||
{'base':'OO','letters':'\uA74E'},
|
||||
{'base':'OU','letters':'\u0222'},
|
||||
{'base':'OE','letters':'\u008C\u0152'},
|
||||
{'base':'oe','letters':'\u009C\u0153'},
|
||||
{'base':'P', 'letters':'\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'},
|
||||
{'base':'Q', 'letters':'\u0051\u24C6\uFF31\uA756\uA758\u024A'},
|
||||
{'base':'R', 'letters':'\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'},
|
||||
{'base':'S', 'letters':'\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'},
|
||||
{'base':'T', 'letters':'\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'},
|
||||
{'base':'TZ','letters':'\uA728'},
|
||||
{'base':'U', 'letters':'\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'},
|
||||
{'base':'V', 'letters':'\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'},
|
||||
{'base':'VY','letters':'\uA760'},
|
||||
{'base':'W', 'letters':'\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'},
|
||||
{'base':'X', 'letters':'\u0058\u24CD\uFF38\u1E8A\u1E8C'},
|
||||
{'base':'Y', 'letters':'\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'},
|
||||
{'base':'Z', 'letters':'\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'},
|
||||
{'base':'a', 'letters':'\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'},
|
||||
{'base':'aa','letters':'\uA733'},
|
||||
{'base':'ae','letters':'\u00E6\u01FD\u01E3'},
|
||||
{'base':'ao','letters':'\uA735'},
|
||||
{'base':'au','letters':'\uA737'},
|
||||
{'base':'av','letters':'\uA739\uA73B'},
|
||||
{'base':'ay','letters':'\uA73D'},
|
||||
{'base':'b', 'letters':'\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'},
|
||||
{'base':'c', 'letters':'\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'},
|
||||
{'base':'d', 'letters':'\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'},
|
||||
{'base':'dz','letters':'\u01F3\u01C6'},
|
||||
{'base':'e', 'letters':'\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'},
|
||||
{'base':'f', 'letters':'\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'},
|
||||
{'base':'g', 'letters':'\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'},
|
||||
{'base':'h', 'letters':'\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'},
|
||||
{'base':'hv','letters':'\u0195'},
|
||||
{'base':'i', 'letters':'\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'},
|
||||
{'base':'j', 'letters':'\u006A\u24D9\uFF4A\u0135\u01F0\u0249'},
|
||||
{'base':'k', 'letters':'\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'},
|
||||
{'base':'l', 'letters':'\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'},
|
||||
{'base':'lj','letters':'\u01C9'},
|
||||
{'base':'m', 'letters':'\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'},
|
||||
{'base':'n', 'letters':'\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'},
|
||||
{'base':'nj','letters':'\u01CC'},
|
||||
{'base':'o', 'letters':'\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'},
|
||||
{'base':'oi','letters':'\u01A3'},
|
||||
{'base':'ou','letters':'\u0223'},
|
||||
{'base':'oo','letters':'\uA74F'},
|
||||
{'base':'p','letters':'\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'},
|
||||
{'base':'q','letters':'\u0071\u24E0\uFF51\u024B\uA757\uA759'},
|
||||
{'base':'r','letters':'\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'},
|
||||
{'base':'s','letters':'\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'},
|
||||
{'base':'t','letters':'\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'},
|
||||
{'base':'tz','letters':'\uA729'},
|
||||
{'base':'u','letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'},
|
||||
{'base':'v','letters':'\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'},
|
||||
{'base':'vy','letters':'\uA761'},
|
||||
{'base':'w','letters':'\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'},
|
||||
{'base':'x','letters':'\u0078\u24E7\uFF58\u1E8B\u1E8D'},
|
||||
{'base':'y','letters':'\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'},
|
||||
{'base':'z','letters':'\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'}
|
||||
];
|
||||
|
||||
var diacriticsMap = {};
|
||||
for (var i=0; i < defaultDiacriticsRemovalap.length; i++){
|
||||
var letters = defaultDiacriticsRemovalap[i].letters.split("");
|
||||
for (var j=0; j < letters.length ; j++){
|
||||
diacriticsMap[letters[j]] = defaultDiacriticsRemovalap[i].base;
|
||||
}
|
||||
}
|
||||
|
||||
return str.replace(/[^\u0000-\u007E]/g, function(a){
|
||||
return diacriticsMap[a] || a;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
]);
|
@ -5,4 +5,8 @@ Application.Services.factory 'Invoice', ["$resource", ($resource)->
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
list:
|
||||
url: '/api/invoices/list'
|
||||
method: 'POST'
|
||||
isArray: true
|
||||
]
|
||||
|
@ -13,4 +13,13 @@ Application.Services.factory 'Member', ["$resource", ($resource)->
|
||||
merge:
|
||||
method: 'PUT'
|
||||
url: '/api/members/:id/merge'
|
||||
list:
|
||||
url: '/api/members/list'
|
||||
method: 'POST'
|
||||
isArray: true
|
||||
search:
|
||||
method: 'GET'
|
||||
url: '/api/members/search/:query'
|
||||
params: {query: "@query"}
|
||||
isArray: true
|
||||
]
|
||||
|
6
app/assets/javascripts/services/socialNetworks.coffee
Normal file
6
app/assets/javascripts/services/socialNetworks.coffee
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
# list the social networks supported in the user's profiles
|
||||
Application.Services.factory 'SocialNetworks', [ ->
|
||||
['facebook', 'twitter', 'google_plus', 'viadeo', 'linkedin', 'instagram', 'youtube', 'vimeo', 'dailymotion', 'github', 'echosciences', 'website', 'pinterest', 'lastfm', 'flickr']
|
||||
]
|
@ -243,8 +243,10 @@
|
||||
|
||||
.amount {
|
||||
padding-top: 16px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
font-weight: bold;
|
||||
font-size: rem-calc(20);
|
||||
font-size: rem-calc(18);
|
||||
color: white;
|
||||
}
|
||||
.period {
|
||||
|
@ -468,10 +468,140 @@ body.container{
|
||||
}
|
||||
}
|
||||
|
||||
.custom-profile-image-container {
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.custom-profile-image {
|
||||
height: 240px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
border: 1px dashed #c4c4c4;
|
||||
border-radius: 0.7em;
|
||||
padding: 1.6em;
|
||||
margin-left: 1em;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: auto;
|
||||
max-height: 185px;
|
||||
max-width: 100%;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
&:hover .tools-box {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tools-box {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-dark {
|
||||
background-color: #000;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.flash-message {
|
||||
position: absolute;
|
||||
top: 1%;
|
||||
z-index: 1001;
|
||||
width: 33%;
|
||||
left: 33%;
|
||||
}
|
||||
|
||||
// profile edition -- add a social network buttons
|
||||
.social-icons {
|
||||
& > div {
|
||||
cursor: pointer;
|
||||
padding: 0.2em;
|
||||
width: 3em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public profile view
|
||||
.profile-top {
|
||||
background-size: cover !important;
|
||||
|
||||
.profile-top-infos {
|
||||
padding: 30px 15px 30px 15px;
|
||||
|
||||
|
||||
.private-profile {
|
||||
color: #000;
|
||||
border: 1px solid $border-color;
|
||||
background-color: $border-color;
|
||||
border-radius: 3px;
|
||||
padding: 0 5px 0 5px;
|
||||
margin-left: 2em;
|
||||
|
||||
i {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-top-pictos {
|
||||
padding: 43px 15px 15px 15px;
|
||||
text-align: center;
|
||||
|
||||
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
width: 140px;
|
||||
img {
|
||||
border: 9px solid #fff;
|
||||
background-color: #fff;
|
||||
box-shadow: 1px 2px 2px 0 #1f1f1f;
|
||||
}
|
||||
}
|
||||
|
||||
.social-links {
|
||||
width: 133px;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
|
||||
a {
|
||||
border: 1px solid #fff;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
color: $input-color;
|
||||
margin-bottom: 5px;
|
||||
|
||||
i { vertical-align: middle; }
|
||||
}
|
||||
}
|
||||
|
||||
.links-center {
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bio-title {
|
||||
display: inherit;
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
}
|
@ -19,6 +19,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// fix for full-calendar display on small devices
|
||||
.fc-toolbar {
|
||||
margin-bottom: 2.5em !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -82,6 +82,7 @@ p, .widget p {
|
||||
.font-bold{font-weight: 700;}
|
||||
.font-ebold{font-weight: 900;}
|
||||
|
||||
.text-xl{font-size: $font-size-xl;}
|
||||
.text-lg{font-size: $font-size-lg;}
|
||||
.text-md{font-size: $font-size-md;}
|
||||
.text-sm{font-size: $font-size-sm;}
|
||||
@ -257,12 +258,19 @@ p, .widget p {
|
||||
.thumb-38 { width: 38px !important; height: 38px; }
|
||||
.thumb-50 { width: 50px !important; height: 50px; }
|
||||
.thumb-128 { width: 128px !important; height: 128px; }
|
||||
.thumb-140 { width: 140px !important; height: 140px; }
|
||||
.thumb-128-wrapper {
|
||||
img {
|
||||
width: 128px !important; height: 128px;
|
||||
@extend .img-circle;
|
||||
}
|
||||
}
|
||||
.thumb-140-wrapper {
|
||||
img {
|
||||
width: 140px !important; height: 140px;
|
||||
@extend .img-circle;
|
||||
}
|
||||
}
|
||||
|
||||
.thumb-lg{width: 128px;display: inline-block}
|
||||
.thumb-md{width: 64px;display: inline-block}
|
||||
@ -286,6 +294,13 @@ p, .widget p {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
.fa-img {
|
||||
display: inline-block;
|
||||
height: 0.9em;
|
||||
width: auto;
|
||||
vertical-align: sub;
|
||||
}
|
||||
.contrast-250 { -webkit-filter: contrast(250%); filter: contrast(250%); }
|
||||
.clear{display:block;overflow: hidden;}
|
||||
|
||||
.scroll-x, .scroll-y{overflow:hidden;-webkit-overflow-scrolling:touch;}
|
||||
|
@ -34,3 +34,8 @@
|
||||
@import "modules/invoice";
|
||||
|
||||
@import "app.responsive";
|
||||
|
||||
<% PluginRegistry.stylesheets.each do |stylesheet| %>
|
||||
<% basename = File.basename(stylesheet,'.scss') %>
|
||||
<%= "@import '#{basename}';" %>
|
||||
<% end %>
|
@ -95,6 +95,7 @@ $font-size-medium: ceil(($font-size-base * 0.875)) !default; // ~14px
|
||||
$font-size-small: ceil(($font-size-base * 0.75)) !default; // ~12px
|
||||
|
||||
//add sleede
|
||||
$font-size-xl: rem-calc(32);
|
||||
$font-size-lg: rem-calc(20);
|
||||
$font-size-md: rem-calc(18);
|
||||
$font-size-sm: rem-calc(14);
|
||||
|
@ -44,7 +44,7 @@
|
||||
<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>
|
||||
<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'">
|
||||
|
@ -26,7 +26,7 @@
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" translate>{{ 'invoice_#_' }}</span>
|
||||
<input type="text" ng-model="searchInvoice.reference" class="form-control" placeholder="">
|
||||
<input type="text" ng-model="searchInvoice.reference" class="form-control" placeholder="" ng-change="handleFilterChange()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -35,7 +35,7 @@
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" translate>{{ 'customer_' }}</span>
|
||||
<input type="text" ng-model="searchInvoice.name" class="form-control" placeholder="">
|
||||
<input type="text" ng-model="searchInvoice.name" class="form-control" placeholder="" ng-change="handleFilterChange()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -44,7 +44,7 @@
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{ "date_" | translate }}</span>
|
||||
<input type="date" ng-model="searchInvoice.date" class="form-control">
|
||||
<input type="date" ng-model="searchInvoice.date" class="form-control" ng-change="handleFilterChange()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,7 +69,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="invoice in invoices | filter:searchInvoice | orderBy:orderInvoice">
|
||||
<tr ng-repeat="invoice in invoices">
|
||||
<td>{{ invoice.reference }}</td>
|
||||
<td ng-if="!invoice.is_avoir">{{ invoice.date | amDateFormat:'L LTS' }}</td>
|
||||
<td ng-if="invoice.is_avoir">{{ invoice.date | amDateFormat:'L' }}</td>
|
||||
@ -91,6 +91,9 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-warning" ng-click="showNextInvoices()" ng-hide="noMoreResults"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'display_more_invoices' | translate }}</button>
|
||||
</div>
|
||||
<p ng-if="invoices.length == 0" translate>{{ 'no_invoices_for_now' }}</p>
|
||||
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<input type="text" ng-model="searchMember" class="form-control" placeholder="{{ 'search_for_an_user' | translate }}">
|
||||
<input type="text" ng-model="member.searchText" class="form-control" placeholder="{{ 'search_for_an_user' | translate }}" ng-change="updateTextSearch()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -46,32 +46,32 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='last_name', 'fa fa-sort-alpha-desc': orderMember=='-last_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='last_name', 'fa fa-sort-alpha-desc': member.order=='-last_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='first_name', 'fa fa-sort-alpha-desc': orderMember=='-first_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='first_name', 'fa fa-sort-alpha-desc': member.order=='-first_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='email', 'fa fa-sort-alpha-desc': orderMember=='-email', 'fa fa-arrows-v': orderMember }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='email', 'fa fa-sort-alpha-desc': member.order=='-email', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderMember('profile.phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderMember=='profile.phone', 'fa fa-sort-numeric-desc': orderMember=='-profile.phone', 'fa fa-arrows-v': orderMember }"></i></a></th>
|
||||
<th style="width:10%"><a href="" ng-click="setOrderMember('phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': member.order=='phone', 'fa fa-sort-numeric-desc': member.order=='-phone', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:20%"><a href="" ng-click="setOrderMember('group.name')">{{ 'user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='group.name', 'fa fa-sort-alpha-desc': orderMember=='-group.name', 'fa fa-arrows-v': orderMember }"></i></a></th>
|
||||
<th style="width:20%"><a href="" ng-click="setOrderMember('group')">{{ 'user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='group', 'fa fa-sort-alpha-desc': member.order=='-group', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('subscribed_plan.base_name')">{{ 'subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='subscribed_plan.base_name', 'fa fa-sort-alpha-desc': orderMember=='-subscribed_plan.base_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('plan')">{{ 'subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='plan', 'fa fa-sort-alpha-desc': member.order=='-plan', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="member in members | filter:searchMember | orderBy:orderMember">
|
||||
<td class="text-c">{{ member.profile.last_name }}</td>
|
||||
<td class="text-c">{{ member.profile.first_name }}</td>
|
||||
<td>{{ member.email }}</td>
|
||||
<td>{{ member.profile.phone }}</td>
|
||||
<td class="text-u-c text-sm">{{ member.group.name }}</td>
|
||||
<td>{{ member.subscribed_plan | humanReadablePlanName }}</td>
|
||||
<tr ng-repeat="m in members">
|
||||
<td class="text-c">{{ m.profile.last_name }}</td>
|
||||
<td class="text-c">{{ m.profile.first_name }}</td>
|
||||
<td>{{ m.email }}</td>
|
||||
<td>{{ m.profile.phone }}</td>
|
||||
<td class="text-u-c text-sm">{{ m.group.name }}</td>
|
||||
<td>{{ m.subscribed_plan | humanReadablePlanName }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.admin.members_edit({id: member.id})">
|
||||
<button class="btn btn-default" ui-sref="app.admin.members_edit({id: m.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
@ -79,6 +79,9 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-warning" ng-click="showNextMembers()" ng-hide="member.noMore"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'display_more_users' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
|
@ -297,6 +297,34 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-t">
|
||||
<div class="col-md-4">
|
||||
<form class="custom-profile-image-container" method="post" action="{{actionUrl.profileImage}}" novalidate name="profileImageForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="profile-image-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.profileImage">
|
||||
<h3 class="m-l" translate>{{ 'background_picture_of_the_profile_banner' }}</h3>
|
||||
<div class="custom-profile-image" style="background-image: url({{profileImage}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!profileImage" class="img-responsive">
|
||||
<img base-sixty-four-image="profileImage" ng-show="profileImage && profileImage.base64">
|
||||
<img ng-src="{{profileImage.custom_asset_file_attributes.attachment_url}}" alt="{{profileImage.custom_asset_file_attributes.attachment}}" ng-show="profileImage && profileImage.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_profile_banner' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
ng-model="profileImage"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="profileImageForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
@ -80,6 +80,12 @@
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'trainings_monitoring' | translate }}">
|
||||
<div class="m-lg">
|
||||
<label for="training_select" translate>{{ 'select_a_training' }}</label>
|
||||
<select ng-options="training as training.name for training in trainings" ng-model="monitoring.training" class="form-control" ng-change="selectTrainingToMonitor()" name="training_select">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -11,6 +11,7 @@
|
||||
<h4 class="m-l text-sm" translate>{{ 'dashboard' }}</h4>
|
||||
<ul class="nav-page nav nav-pills text-u-c text-sm">
|
||||
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.profile" translate>{{ 'my_profile' }}</a></li>
|
||||
<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>
|
||||
|
@ -1,125 +1,16 @@
|
||||
<div>
|
||||
|
||||
<ng-include src="'<%= asset_path 'dashboard/nav.html' %>'"></ng-include>
|
||||
|
||||
|
||||
|
||||
<div class="row no-gutter wrapper">
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small text-center">
|
||||
<span class="avatar ">
|
||||
<fab-user-avatar ng-model="user.profile.user_avatar" avatar-class="thumb-50">test</fab-user-avatar>
|
||||
</span>
|
||||
<div class="font-sbold m-t-sm">{{user.name}}</div>
|
||||
<div>{{user.email}}</div>
|
||||
<div class="text-xs" ng-if="user.last_sign_in_at"><i>{{ 'last_activity_on_' | translate }} {{user.last_sign_in_at | amDateFormat: 'LL'}}</i></div>
|
||||
</div>
|
||||
<div class="widget-content no-bg b-b auto wrapper">
|
||||
<div class="m-b-md">
|
||||
<h3 class="text-u-c" translate>{{ 'group' }}</h3>
|
||||
<div ng-show="!group.change">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{getUserGroup().name}}</span>
|
||||
</uib-alert>
|
||||
<button class="btn text-black btn-warning-full btn-sm m-t-n-sm" ng-click="group.change = !group.change" ng-show="!user.subscribed_plan.name" translate>{{ 'i_want_to_change_group' }}</button>
|
||||
</div>
|
||||
<div ng-show="group.change">
|
||||
<select class="form-control" ng-options="g.id as g.name for g in groups" ng-model="userGroup"></select>
|
||||
<button class="btn btn-success m-t" ng-click="selectGroup()">Changer mon groupe</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-hide="fablabWithoutPlans">
|
||||
<h3 class="text-u-c" translate>{{ 'subscription' }}</h3>
|
||||
<div ng-show="user.subscribed_plan">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{ user.subscribed_plan | humanReadablePlanName }}</span>
|
||||
<div class="font-sbold" ng-if="user.subscription">{{ 'your_subscription_expires_on_' | translate }} {{user.subscription.expired_at | amDateFormat: 'LL'}}</div>
|
||||
</uib-alert>
|
||||
|
||||
</div>
|
||||
<div ng-show="!user.subscribed_plan.name">{{ 'no_subscriptions' | translate }} <br><a class="btn text-black btn-warning-full btn-sm m-t-xs" ui-sref="app.public.plans" translate>{{ 'i_want_to_subscribe' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'trainings' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.training_reservations.length > 0 || user.trainings.length > 0">
|
||||
<li ng-repeat="r in user.training_reservations | trainingReservationsFilter:'future'">
|
||||
{{r.reservable.name}} - {{ 'to_come' | translate }}
|
||||
</li>
|
||||
<li ng-repeat="t in user.trainings">
|
||||
{{t.name}} - {{ 'approved' | translate }}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.training_reservations.length == 0 && user.trainings.length == 0" translate>{{ 'no_trainings' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'projects' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.all_projects.length > 0">
|
||||
<li ng-repeat="p in user.all_projects">
|
||||
{{p.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.all_projects.length == 0" translate>{{ 'no_projects' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'labels' }}</h3>
|
||||
<span ng-if="user.tags.length > 0" ng-repeat="t in user.tags">
|
||||
<span class='label label-success text-white'>{{t.name}}</span>
|
||||
</span>
|
||||
<div ng-if="user.tags.length == 0" translate>{{ 'no_labels' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-content no-bg text-center auto wrapper">
|
||||
<button class="btn text-white btn-danger btn-sm" ng-click="deleteUser(user)"><i class="fa fa-warning"></i> {{ 'delete_my_account' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="heading">
|
||||
<div class="row no-gutter">
|
||||
<ng-include src="'<%= asset_path 'dashboard/nav.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b">
|
||||
<h1 class="red text-u-c" translate>{{ 'edit_my_profile' }}</h1>
|
||||
</div>
|
||||
<form role="form" name="userForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
|
||||
<div class="widget-content no-bg auto">
|
||||
<section class="panel panel-default bg-light m p-lg row" ng-if="hasSsoFields()">
|
||||
<div class="panel-heading">
|
||||
<h2>
|
||||
<img class="v-middle" height="16" width="16" src='https://www.google.com/s2/favicons?domain={{activeProvider.domain}}' />
|
||||
<span class="v-middle">{{activeProvider.name}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="panel-body row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-href="{{activeProvider.link_to_sso_profile}}" target="_blank">
|
||||
<i class="fa fa-edit"></i> {{ 'change_my_data' | translate }}
|
||||
</a>
|
||||
<p>{{ 'once_your_data_are_up_to_date_' | translate }} <strong translate>{{ '_click_on_the_synchronization_button_opposite_' }}</strong> {{ 'or' | translate}} <strong translate>{{ '_disconnect_then_reconnect_' }}</strong> {{ '_for_your_changes_to_take_effect' | translate }}</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-click="syncProfile()">
|
||||
<i class="fa fa-refresh"></i> {{ 'sync_my_profile' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="panel panel-default bg-light m">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'shared/_member_form.html' %>'"></ng-include>
|
||||
</div> <!-- ./panel-body -->
|
||||
</section>
|
||||
</div>
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="userForm.$invalid"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter">
|
||||
<div class="col-md-12 m m-t-lg">
|
||||
<ng-include src="'<%= asset_path 'shared/publicProfile.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
125
app/assets/templates/dashboard/settings.html.erb
Normal file
125
app/assets/templates/dashboard/settings.html.erb
Normal file
@ -0,0 +1,125 @@
|
||||
<div>
|
||||
|
||||
<ng-include src="'<%= asset_path 'dashboard/nav.html' %>'"></ng-include>
|
||||
|
||||
|
||||
|
||||
<div class="row no-gutter wrapper">
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small text-center">
|
||||
<span class="avatar ">
|
||||
<fab-user-avatar ng-model="user.profile.user_avatar" avatar-class="thumb-50">test</fab-user-avatar>
|
||||
</span>
|
||||
<div class="font-sbold m-t-sm">{{user.name}}</div>
|
||||
<div>{{user.email}}</div>
|
||||
<div class="text-xs" ng-if="user.last_sign_in_at"><i>{{ 'last_activity_on_' | translate }} {{user.last_sign_in_at | amDateFormat: 'LL'}}</i></div>
|
||||
</div>
|
||||
<div class="widget-content no-bg b-b auto wrapper">
|
||||
<div class="m-b-md">
|
||||
<h3 class="text-u-c" translate>{{ 'group' }}</h3>
|
||||
<div ng-show="!group.change">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{getUserGroup().name}}</span>
|
||||
</uib-alert>
|
||||
<button class="btn text-black btn-warning-full btn-sm m-t-n-sm" ng-click="group.change = !group.change" ng-show="!user.subscribed_plan.name" translate>{{ 'i_want_to_change_group' }}</button>
|
||||
</div>
|
||||
<div ng-show="group.change">
|
||||
<select class="form-control" ng-options="g.id as g.name for g in groups" ng-model="userGroup"></select>
|
||||
<button class="btn btn-success m-t" ng-click="selectGroup()">Changer mon groupe</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-hide="fablabWithoutPlans">
|
||||
<h3 class="text-u-c" translate>{{ 'subscription' }}</h3>
|
||||
<div ng-show="user.subscribed_plan">
|
||||
<uib-alert type="warning">
|
||||
<span class="text-black font-sbold">{{ user.subscribed_plan | humanReadablePlanName }}</span>
|
||||
<div class="font-sbold" ng-if="user.subscription">{{ 'your_subscription_expires_on_' | translate }} {{user.subscription.expired_at | amDateFormat: 'LL'}}</div>
|
||||
</uib-alert>
|
||||
|
||||
</div>
|
||||
<div ng-show="!user.subscribed_plan.name">{{ 'no_subscriptions' | translate }} <br><a class="btn text-black btn-warning-full btn-sm m-t-xs" ui-sref="app.public.plans" translate>{{ 'i_want_to_subscribe' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'trainings' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.training_reservations.length > 0 || user.trainings.length > 0">
|
||||
<li ng-repeat="r in user.training_reservations | trainingReservationsFilter:'future'">
|
||||
{{r.reservable.name}} - {{ 'to_come' | translate }}
|
||||
</li>
|
||||
<li ng-repeat="t in user.trainings">
|
||||
{{t.name}} - {{ 'approved' | translate }}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.training_reservations.length == 0 && user.trainings.length == 0" translate>{{ 'no_trainings' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'projects' }}</h3>
|
||||
<ul class="list-unstyled" ng-if="user.all_projects.length > 0">
|
||||
<li ng-repeat="p in user.all_projects">
|
||||
{{p.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.all_projects.length == 0" translate>{{ 'no_projects' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<h3 class="text-u-c" translate>{{ 'labels' }}</h3>
|
||||
<span ng-if="user.tags.length > 0" ng-repeat="t in user.tags">
|
||||
<span class='label label-success text-white'>{{t.name}}</span>
|
||||
</span>
|
||||
<div ng-if="user.tags.length == 0" translate>{{ 'no_labels' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-content no-bg text-center auto wrapper">
|
||||
<button class="btn text-white btn-danger btn-sm" ng-click="deleteUser(user)"><i class="fa fa-warning"></i> {{ 'delete_my_account' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b">
|
||||
<h1 class="red text-u-c" translate>{{ 'edit_my_profile' }}</h1>
|
||||
</div>
|
||||
<form role="form" name="userForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
|
||||
<div class="widget-content no-bg auto">
|
||||
<section class="panel panel-default bg-light m p-lg row" ng-if="hasSsoFields()">
|
||||
<div class="panel-heading">
|
||||
<h2>
|
||||
<img class="v-middle" height="16" width="16" src='https://www.google.com/s2/favicons?domain={{activeProvider.domain}}' />
|
||||
<span class="v-middle">{{activeProvider.name}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="panel-body row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-href="{{activeProvider.link_to_sso_profile}}" target="_blank">
|
||||
<i class="fa fa-edit"></i> {{ 'change_my_data' | translate }}
|
||||
</a>
|
||||
<p>{{ 'once_your_data_are_up_to_date_' | translate }} <strong translate>{{ '_click_on_the_synchronization_button_opposite_' }}</strong> {{ 'or' | translate}} <strong translate>{{ '_disconnect_then_reconnect_' }}</strong> {{ '_for_your_changes_to_take_effect' | translate }}</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-click="syncProfile()">
|
||||
<i class="fa fa-refresh"></i> {{ 'sync_my_profile' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="panel panel-default bg-light m">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'shared/_member_form.html' %>'"></ng-include>
|
||||
</div> <!-- ./panel-body -->
|
||||
</section>
|
||||
</div>
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="userForm.$invalid"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
@ -56,7 +56,9 @@
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<ng-include src="'<%= asset_path 'shared/_member_select.html' %>'" ng-if="currentUser.role === 'admin'"></ng-include>
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
<section class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
|
@ -25,8 +25,8 @@
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div>
|
||||
<ng-include src="'<%= asset_path 'shared/_member_select.html' %>'" ng-if="currentUser.role === 'admin'"></ng-include>
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-show="!ctrl.member && currentUser.role == 'admin' && eventsReserved.length == 0 && (!paidMachineSlots || paidMachineSlots.length == 0) && !slotToModify && !modifiedSlots">
|
||||
|
@ -48,6 +48,10 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-warning" ng-click="showNextMembers()" ng-hide="noMoreResults"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'display_more_members' | translate }}</button>
|
||||
</div>
|
||||
<p ng-if="members.length == 0" translate>{{ 'no_members_for_now' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -21,97 +21,5 @@
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter wrapper">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
|
||||
|
||||
|
||||
<div class="wrapper">
|
||||
<section class="widget panel no-border bg-black-light text-white lt">
|
||||
<div class="panel-body">
|
||||
<div class="row m-t-xl">
|
||||
<div class="col-xs-3 text-right padder-v">
|
||||
<!-- <a href="#" class="btn btn-primary btn-icon btn-rounded m-t-xl"><i class="fa fa-flag"></i></a> -->
|
||||
</div>
|
||||
<div class="col-xs-6 text-center">
|
||||
<div class="inline">
|
||||
<div class="easypiechart easyPieChart" data-percent="75" data-line-width="6" data-bar-color="#fff" data-track-color="#2796de" data-scale-color="false" data-size="140" data-line-cap="butt" data-animate="1000" style="width: 140px; height: 140px; line-height: 140px;">
|
||||
<div class="thumb-lg avatar thumb-128-wrapper img">
|
||||
<fab-user-avatar ng-model="user.profile.user_avatar" avatar-class="thumb-140"></fab-user-avatar>
|
||||
</div>
|
||||
<canvas width="140" height="140"></canvas></div>
|
||||
<div class="h4 m-t m-b-xs font-bold text-lt text-white">{{user.name}}</div>
|
||||
<small class="text-muted m-b">{{user.username}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-3 padder-v">
|
||||
<!-- <a href="#" class="btn btn-primary btn-icon btn-rounded m-t-xl" data-toggle="class:btn-danger">
|
||||
<i class="i i-phone text"></i>
|
||||
<i class="i i-phone2 text-active"></i>
|
||||
</a> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper m-t-xl m-b">
|
||||
<div class="row m-b">
|
||||
<div class="col-xs-6 text-right">
|
||||
<small translate>{{ 'last_activity_' }}</small>
|
||||
<div class="text-lt font-bold" ng-if="user.last_sign_in_at">{{ '_on_' | translate }} {{user.last_sign_in_at | amDateFormat: 'LL'}}</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<small translate>{{ 'email_address' }}</small>
|
||||
<div class="text-lt font-bold break-word">{{user.email}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 text-right">
|
||||
<small translate>{{ 'CAD_softwares_mastered' }}</small>
|
||||
<div class="text-lt font-bold">{{user.profile.software_mastered}}</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<small translate>{{ 'interests' }}</small>
|
||||
<div class="text-lt font-bold">{{user.profile.interest}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="widget panel b-a m ">
|
||||
<div class="panel-heading b-b">
|
||||
<h1 class="red text-u-c" translate>{{ 'trainings' }}</h1>
|
||||
<!-- <h3 class="text-u-c">Formations</h3> -->
|
||||
<ul class="list-unstyled" ng-if="user.training_reservations.length > 0 || user.trainings.length > 0">
|
||||
<li ng-repeat="r in user.training_reservations | trainingReservationsFilter:'future'">
|
||||
{{r.reservable.name}} - <span class="label label-info text-white" translate>{{ 'to_come' }}</span>
|
||||
</li>
|
||||
<li ng-repeat="t in user.trainings">
|
||||
{{t.name}} - <span class="label label-success text-white" translate>{{ 'approved' }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.training_reservations.length == 0 && user.trainings.length == 0" translate>{{ 'no_trainings' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m ">
|
||||
<div class="panel-heading b-b">
|
||||
<h1 class="red text-u-c" translate>{{ 'projects' }}</h1>
|
||||
<ul class="list-unstyled" ng-if="user.all_projects.length > 0">
|
||||
<li ng-repeat="p in user.all_projects" class="m-t-sm">
|
||||
<a class="text-u-c" ui-sref="app.public.projects_show({id:p.slug})" role="button">{{p.name}} <span class="m-l-sm label label-success text-white">{{p.author_id == currentUser.id ? 'author' : 'collaborator' | translate}}</span></a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div ng-if="user.all_projects.length == 0" translate>{{ 'no_projects' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<ng-include src="'<%= asset_path 'shared/publicProfile.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
@ -81,8 +81,8 @@
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div>
|
||||
<ng-include src="'<%= asset_path 'shared/_member_select.html' %>'" ng-if="currentUser.role === 'admin'"></ng-include>
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member subscription="false"></select-member>
|
||||
</div>
|
||||
|
||||
<section class="widget panel b-a m m-t-lg" ng-show="ctrl.member">
|
||||
|
@ -80,14 +80,6 @@
|
||||
<span class="help-block" ng-show="userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$error.required" translate>{{ 'user_s_profile_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- allow contact-->
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[is_allow_contact]'].$dirty && userForm['user[is_allow_contact]'].$invalid}">
|
||||
<input type="checkbox"
|
||||
name="user[is_allow_contact]"
|
||||
ng-model="user.is_allow_contact"
|
||||
value="true"/> {{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' | translate }}
|
||||
</div>
|
||||
|
||||
<!-- accept cgu -->
|
||||
<div class="form-group" ng-class="{'has-error': userForm.cgu.$dirty && userForm.cgu.$invalid}" ng-show="cgu">
|
||||
<input type="checkbox"
|
||||
|
@ -171,7 +171,7 @@
|
||||
<span ng-bind="$item.name"></span>
|
||||
<input type="hidden" name="project[user_ids][]" value="{{$item.id}}" />
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="m.id as m in (members | filter: $select.search)">
|
||||
<ui-select-choices repeat="m.id as m in matchingMembers" refresh="autoCompleteName($select.search)" refresh-delay="300">
|
||||
<span ng-bind-html="m.name | highlight: $select.search"></span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
|
@ -214,6 +214,33 @@
|
||||
<span class="help-block" ng-show="userForm['user[profile_attributes][phone]'].$dirty && userForm['user[profile_attributes][phone]'].$error.required" translate>{{ 'phone_number_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][website]'].$dirty && userForm['user[profile_attributes][website]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-globe"></i> </span>
|
||||
<input type="url"
|
||||
name="user[profile_attributes][website]"
|
||||
ng-model="user.profile.website"
|
||||
class="form-control"
|
||||
id="user_website"
|
||||
ng-pattern="/^https?:\/\//"
|
||||
placeholder="{{ 'website' | translate }} (http://...)"
|
||||
ng-disabled="preventField['profile.website'] && user.profile.website && !userForm['user[profile_attributes][website]'].$dirty"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][job]'].$dirty && userForm['user[profile_attributes][job]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-briefcase"></i> </span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][job]"
|
||||
ng-model="user.profile.job"
|
||||
class="form-control"
|
||||
id="user_job"
|
||||
placeholder="{{ 'job' | translate }}"
|
||||
ng-disabled="preventField['profile.job'] && user.profile.job && !userForm['user[profile_attributes][job]'].$dirty"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user_interest" translate>{{ 'interests' }}</label>
|
||||
<textarea name="user[profile_attributes][interest]"
|
||||
@ -236,6 +263,250 @@
|
||||
ng-disabled="preventField['profile.software_mastered'] && user.profile.software_mastered && !userForm['user[profile_attributes][software_mastered]'].$dirty"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- allow contact-->
|
||||
<div class="form-group">
|
||||
<label for="allowContact" translate>{{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="user.is_allow_contact"
|
||||
id="allowContact"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<input type="hidden" name="user[is_allow_contact]" value="{{user.is_allow_contact}}"/>
|
||||
</div>
|
||||
|
||||
<div id="social" ng-init="social={}">
|
||||
<div class="form-group" ng-show="social.facebook || user.profile.facebook" ng-class="{'has-error': userForm['user[profile_attributes][facebook]'].$dirty && userForm['user[profile_attributes][facebook]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-facebook"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][facebook]"
|
||||
ng-model="user.profile.facebook"
|
||||
class="form-control"
|
||||
id="user_facebook"
|
||||
ng-pattern="/^https?:\/\/.*?facebook/i"
|
||||
placeholder="https://www.facebook.com/..."
|
||||
ng-disabled="preventField['profile.facebook'] && user.profile.first_name && !userForm['user[profile_attributes][facebook]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.twitter || user.profile.twitter" ng-class="{'has-error': userForm['user[profile_attributes][twitter]'].$dirty && userForm['user[profile_attributes][twitter]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-twitter"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][twitter]"
|
||||
ng-model="user.profile.twitter"
|
||||
class="form-control"
|
||||
id="user_twitter"
|
||||
ng-pattern="/^https?:\/\/.*?twitter/"
|
||||
placeholder="https://twitter.com/..."
|
||||
ng-disabled="preventField['profile.twitter'] && user.profile.first_name && !userForm['user[profile_attributes][twitter]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.google_plus || user.profile.google_plus" ng-class="{'has-error': userForm['user[profile_attributes][google_plus]'].$dirty && userForm['user[profile_attributes][google_plus]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-google-plus"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][google_plus]"
|
||||
ng-model="user.profile.google_plus"
|
||||
class="form-control"
|
||||
id="user_google_plus"
|
||||
ng-pattern="/^https?:\/\/.*?google/"
|
||||
placeholder="https://plus.google.com/+..."
|
||||
ng-disabled="preventField['profile.google_plus'] && user.profile.first_name && !userForm['user[profile_attributes][google_plus]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.viadeo || user.profile.viadeo" ng-class="{'has-error': userForm['user[profile_attributes][viadeo]'].$dirty && userForm['user[profile_attributes][viadeo]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-viadeo"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][viadeo]"
|
||||
ng-model="user.profile.viadeo"
|
||||
class="form-control"
|
||||
id="user_viadeo"
|
||||
ng-pattern="/^https?:\/\/.*?viadeo/"
|
||||
placeholder="http://www.viadeo.com/fr/profile/..."
|
||||
ng-disabled="preventField['profile.viadeo'] && user.profile.first_name && !userForm['user[profile_attributes][viadeo]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.linkedin || user.profile.linkedin" ng-class="{'has-error': userForm['user[profile_attributes][linkedin]'].$dirty && userForm['user[profile_attributes][linkedin]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-linkedin"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][linkedin]"
|
||||
ng-model="user.profile.linkedin"
|
||||
class="form-control"
|
||||
id="user_linkedin"
|
||||
ng-pattern="/^https?:\/\/.*?linkedin/"
|
||||
placeholder="https://www.linkedin.com/in/..."
|
||||
ng-disabled="preventField['profile.linkedin'] && user.profile.first_name && !userForm['user[profile_attributes][linkedin]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.instagram || user.profile.instragram" ng-class="{'has-error': userForm['user[profile_attributes][instagram]'].$dirty && userForm['user[profile_attributes][instagram]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-instagram"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][instagram]"
|
||||
ng-model="user.profile.instagram"
|
||||
class="form-control"
|
||||
id="user_instagram"
|
||||
ng-pattern="/^https?:\/\/.*?instagram/"
|
||||
placeholder="https://www.instagram.com/..."
|
||||
ng-disabled="preventField['profile.instagram'] && user.profile.first_name && !userForm['user[profile_attributes][instagram]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.youtube || user.profile.youtube" ng-class="{'has-error': userForm['user[profile_attributes][youtube]'].$dirty && userForm['user[profile_attributes][youtube]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-youtube"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][youtube]"
|
||||
ng-model="user.profile.youtube"
|
||||
class="form-control"
|
||||
id="user_youtube"
|
||||
ng-pattern="/^https?:\/\/.*?youtube/"
|
||||
placeholder="https://www.youtube.com/..."
|
||||
ng-disabled="preventField['profile.youtube'] && user.profile.first_name && !userForm['user[profile_attributes][youtube]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.vimeo || user.profile.vimeo" ng-class="{'has-error': userForm['user[profile_attributes][vimeo]'].$dirty && userForm['user[profile_attributes][vimeo]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-vimeo"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][vimeo]"
|
||||
ng-model="user.profile.vimeo"
|
||||
class="form-control"
|
||||
id="user_vimeo"
|
||||
ng-pattern="/^https?:\/\/.*?vimeo/"
|
||||
placeholder="https://vimeo.com/..."
|
||||
ng-disabled="preventField['profile.vimeo'] && user.profile.first_name && !userForm['user[profile_attributes][vimeo]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.dailymotion || user.profile.dailymotion" ng-class="{'has-error': userForm['user[profile_attributes][dailymotion]'].$dirty && userForm['user[profile_attributes][dailymotion]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><img src="<%= asset_path('social/dailymotion.png') %>" alt="d" class="fa-img"/></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][dailymotion]"
|
||||
ng-model="user.profile.dailymotion"
|
||||
class="form-control"
|
||||
id="user_dailymotion"
|
||||
ng-pattern="/^https?:\/\/.*?dailymotion/"
|
||||
placeholder="http://www.dailymotion.com/..."
|
||||
ng-disabled="preventField['profile.dailymotion'] && user.profile.first_name && !userForm['user[profile_attributes][dailymotion]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" ng-show="social.github || user.profile.github" ng-class="{'has-error': userForm['user[profile_attributes][github]'].$dirty && userForm['user[profile_attributes][github]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-github"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][github]"
|
||||
ng-model="user.profile.github"
|
||||
class="form-control"
|
||||
id="user_github"
|
||||
ng-pattern="/^https?:\/\/.*?github/"
|
||||
placeholder="https://github.com/..."
|
||||
ng-disabled="preventField['profile.github'] && user.profile.first_name && !userForm['user[profile_attributes][github]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.echosciences || user.profile.echosciences" ng-class="{'has-error': userForm['user[profile_attributes][echosciences]'].$dirty && userForm['user[profile_attributes][echosciences]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><img src="<%= asset_path('social/echosciences.png') %>" alt="d" class="fa-img"/></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][echosciences]"
|
||||
ng-model="user.profile.echosciences"
|
||||
class="form-control"
|
||||
id="user_echosciences"
|
||||
ng-pattern="/^https?:\/\/.*?echosciences/"
|
||||
placeholder="http://www.echosciences-local.fr/membres/..."
|
||||
ng-disabled="preventField['profile.echosciences'] && user.profile.first_name && !userForm['user[profile_attributes][echosciences]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.pinterest || user.profile.pinterest" ng-class="{'has-error': userForm['user[profile_attributes][pinterest]'].$dirty && userForm['user[profile_attributes][pinterest]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-pinterest"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][pinterest]"
|
||||
ng-model="user.profile.pinterest"
|
||||
class="form-control"
|
||||
id="user_pinterest"
|
||||
ng-pattern="/^https?:\/\/.*?pinterest/"
|
||||
placeholder="https://fr.pinterest.com/..."
|
||||
ng-disabled="preventField['profile.pinterest'] && user.profile.first_name && !userForm['user[profile_attributes][pinterest]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.lastfm || user.profile.lastfm" ng-class="{'has-error': userForm['user[profile_attributes][lastfm]'].$dirty && userForm['user[profile_attributes][lastfm]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lastfm"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][lastfm]"
|
||||
ng-model="user.profile.lastfm"
|
||||
class="form-control"
|
||||
id="user_lastfm"
|
||||
ng-pattern="/^https?:\/\/.*?last.fm/"
|
||||
placeholder="http://www.last.fm/fr/user/..."
|
||||
ng-disabled="preventField['profile.lastfm'] && user.profile.first_name && !userForm['user[profile_attributes][lastfm]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="social.flickr || user.profile.flickr" ng-class="{'has-error': userForm['user[profile_attributes][flickr]'].$dirty && userForm['user[profile_attributes][flickr]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-flickr"></i></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][flickr]"
|
||||
ng-model="user.profile.flickr"
|
||||
class="form-control"
|
||||
id="user_flickr"
|
||||
ng-pattern="/^https?:\/\/.*?flickr/"
|
||||
placeholder="https://www.flickr.com/photos/..."
|
||||
ng-disabled="preventField['profile.flickr'] && user.profile.flickr && !userForm['user[profile_attributes][flickr]'].$dirty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="social-icons m-b">
|
||||
<div ng-click="social.facebook = !social.facebook" ng-hide="social.facebook || user.profile.facebook"><i class="fa fa-facebook fa-2x"></i></div>
|
||||
<div ng-click="social.twitter = !social.twitter" ng-hide="social.twitter || user.profile.twitter"><i class="fa fa-twitter fa-2x"></i></div>
|
||||
<div ng-click="social.google_plus = !social.google_plus" ng-hide="social.google_plus || user.profile.google_plus"><i class="fa fa-google-plus fa-2x"></i></div>
|
||||
<div ng-click="social.viadeo = !social.viadeo" ng-hide="social.viadeo || user.profile.viadeo"><i class="fa fa-viadeo fa-2x"></i></div>
|
||||
<div ng-click="social.linkedin = !social.linkedin" ng-hide="social.linkedin || user.profile.linkedin"><i class="fa fa-linkedin fa-2x"></i></div>
|
||||
<div ng-click="social.instagram = !social.instagram" ng-hide="social.instagram || user.profile.instagram"><i class="fa fa-instagram fa-2x"></i></div>
|
||||
<div ng-click="social.youtube = !social.youtube" ng-hide="social.youtube || user.profile.youtube"><i class="fa fa-youtube fa-2x"></i></div>
|
||||
<div ng-click="social.vimeo = !social.vimeo" ng-hide="social.vimeo || user.profile.vimeo"><i class="fa fa-vimeo fa-2x"></i></div>
|
||||
<div ng-click="social.dailymotion = !social.dailymotion" ng-hide="social.dailymotion || user.profile.dailymotion"><img src="<%= asset_path('social/dailymotion.png') %>" alt="d" class="fa-img contrast-250 fa-2x"/></div>
|
||||
<div ng-click="social.github = !social.github" ng-hide="social.github || user.profile.github"><i class="fa fa-github fa-2x"></i></div>
|
||||
<div ng-click="social.echosciences = !social.echosciences" ng-hide="social.echosciences || user.profile.echosciences"><img src="<%= asset_path('social/echosciences.png') %>" alt="E" class="fa-img contrast-250 fa-2x"/></div>
|
||||
<div ng-click="social.pinterest = !social.pinterest" ng-hide="social.pinterest || user.profile.pinterest"><i class="fa fa-pinterest fa-2x"></i></div>
|
||||
<div ng-click="social.lastfm = !social.lastfm" ng-hide="social.lastfm || user.profile.lastfm"><i class="fa fa-lastfm fa-2x"></i></div>
|
||||
<div ng-click="social.flickr = !social.flickr" ng-hide="social.flickr || user.profile.flickr"><i class="fa fa-flickr fa-2x"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -4,10 +4,10 @@
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ui-select ng-model="ctrl.member" on-select="updateMember()">
|
||||
<ui-select-match>
|
||||
<ui-select-match placeholder="{{ 'start_typing' | translate }}">
|
||||
<span ng-bind="$select.selected.name"></span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="m in (members | filter: {'name':$select.search})">
|
||||
<ui-select-choices repeat="m in matchingMembers" refresh="autoCompleteName($select.search)" refresh-delay="300">
|
||||
<span ng-bind-html="m.name | highlight: $select.search"></span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
|
4
app/assets/templates/shared/_social_link.html
Normal file
4
app/assets/templates/shared/_social_link.html
Normal file
@ -0,0 +1,4 @@
|
||||
<a ng-show="user.profile[network]" ng-href="{{user.profile[network]}}" target="_blank">
|
||||
<img ng-src="{{image}}" alt="{{altText}}" class="fa-img" ng-show="image"/>
|
||||
<i class="fa {{faClass}}" ng-hide="image"></i>
|
||||
</a>
|
@ -35,6 +35,7 @@
|
||||
</a>
|
||||
<ul class="uib-dropdown-menu animated fadeInRight">
|
||||
<li><a href="#" ui-sref="app.logged.dashboard.profile" translate>{{ 'my_profile' }}</a></li>
|
||||
<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>
|
||||
|
@ -32,8 +32,13 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="hidden-sm hidden-md hidden-lg" >
|
||||
<a href="#" ui-sref="app.logged.dashboard.profile">
|
||||
<i class="fa fa-user"></i> <span translate>{{ 'my_profile' }}</span>
|
||||
<a href="#" ui-sref="app.logged.dashboard.profile">
|
||||
<i class="fa fa-user"></i> <span translate>{{ 'my_profile' }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="hidden-sm hidden-md hidden-lg" >
|
||||
<a href="#" ui-sref="app.logged.dashboard.settings">
|
||||
<i class="fa fa-user"></i> <span translate>{{ 'my_settings' }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="hidden-sm hidden-md hidden-lg">
|
||||
|
130
app/assets/templates/shared/publicProfile.html.erb
Normal file
130
app/assets/templates/shared/publicProfile.html.erb
Normal file
@ -0,0 +1,130 @@
|
||||
<div class="row no-gutter wrapper">
|
||||
|
||||
<div class="col-lg-12">
|
||||
<section class="profile-top text-white widget panel b-a m row">
|
||||
<div class="col-lg-2 col-md-12 profile-top-pictos">
|
||||
<div class="avatar thumb-140-wrapper img m-b-md">
|
||||
<fab-user-avatar ng-model="user.profile.user_avatar" avatar-class="thumb-140"></fab-user-avatar>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="social-links links-center" ng-if="social.networks.length <= 4">
|
||||
<ng-repeat ng-repeat="network in social.networks" >
|
||||
<social-link network="{{network}}" user="user"></social-link>
|
||||
</ng-repeat>
|
||||
</div>
|
||||
<div class="social-links" ng-if="social.networks.length > 4">
|
||||
<ng-repeat ng-repeat="network in social.networks.slice(0,3)">
|
||||
<social-link network="{{network}}" user="user"></social-link>
|
||||
</ng-repeat>
|
||||
<a href="#" ng-click="social.showAllLinks = !social.showAllLinks">
|
||||
<i class="fa fa-plus" ng-show="!social.showAllLinks"></i>
|
||||
<i class="fa fa-minus" ng-show="social.showAllLinks"></i>
|
||||
</a>
|
||||
<ng-repeat ng-repeat="network in social.networks.slice(3)" ng-show="social.showAllLinks">
|
||||
<social-link network="{{network}}" user="user"></social-link>
|
||||
</ng-repeat>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-7 profile-top-infos">
|
||||
<div class="text-xl font-ebold upper">{{user.username}}</div>
|
||||
<small class="font-bold">{{user.name}}</small>
|
||||
<small class="text-xs upper font-thin private-profile" ng-show="!user.is_allow_contact"><i class="fa fa-lock" aria-hidden="true"></i> {{ 'private_profile' | translate }}</small>
|
||||
<div class="m-t">
|
||||
<small translate>{{ 'last_activity_' }}</small>
|
||||
<div class="text-lt font-bold" ng-if="user.last_sign_in_at">{{ '_on_' | translate }} {{user.last_sign_in_at | amDateFormat: 'LL'}}</div>
|
||||
<small translate>{{ 'email_address' }}</small>
|
||||
<div class="text-lt font-bold break-word">{{user.email}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 profile-top-badge">
|
||||
<%= PluginRegistry.insert_code('html.user.profile') %>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
|
||||
|
||||
|
||||
<div class="wrapper">
|
||||
<section class="widget panel no-border bg-black-light text-white lt">
|
||||
<div class="panel-body">
|
||||
<div class="wrapper m-t-xl m-b">
|
||||
<div class="row m-b">
|
||||
<div class="col-xs-5 text-right">
|
||||
<span class="font-bold bio-title" translate>{{ 'interests' }}</span>
|
||||
<div class="m-b m-t-sm">{{user.profile.interest}}</div>
|
||||
</div>
|
||||
<div class="col-xs-offset-2 col-xs-5">
|
||||
<span class="font-bold bio-title" translate>{{ 'CAD_softwares_mastered' }}</span>
|
||||
<div class="m-t-sm">{{user.profile.software_mastered}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="widget panel b-a m ">
|
||||
<div class="panel-heading b-b">
|
||||
<h1 class="red text-u-c" translate>{{ 'trainings' }}</h1>
|
||||
<!-- <h3 class="text-u-c">Formations</h3> -->
|
||||
<ul class="list-unstyled" ng-if="user.training_reservations.length > 0 || user.trainings.length > 0">
|
||||
<li ng-repeat="r in user.training_reservations | trainingReservationsFilter:'future'">
|
||||
{{r.reservable.name}} - <span class="label label-info text-white" translate>{{ 'to_come' }}</span>
|
||||
</li>
|
||||
<li ng-repeat="t in user.trainings">
|
||||
{{t.name}} - <span class="label label-success text-white" translate>{{ 'approved' }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="user.training_reservations.length == 0 && user.trainings.length == 0" translate>{{ 'no_trainings' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m ">
|
||||
<div class="panel-heading b-b">
|
||||
<h1 class="red text-u-c m-b" translate>{{ 'projects' }}</h1>
|
||||
<div ng-if="user.all_projects.length > 0" class="row m-t">
|
||||
<a class="col-xs-12 col-sm-6 col-md-6 col-lg-6" ng-repeat="project in user.all_projects" ui-sref="app.public.projects_show({id:project.slug})" style="display: block;">
|
||||
<div class="card card-project">
|
||||
|
||||
<div class="card-header">
|
||||
<div class="card-header-bg" style="background-image: url({{project.project_image}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!project.project_image">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-block">
|
||||
<h1 class="card-title">{{project.name}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<span class="m-l-sm label label-success text-white">{{project.author_id == user.id ? 'author' : 'collaborator' | translate}}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-overlay">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default" ng-click="showProject(project)">
|
||||
{{ 'consult' | translate }}
|
||||
</div>
|
||||
<div class="btn btn-default" ui-sref="app.logged.projects_edit({id:project.id})" ng-if="isAuthorized('admin')">
|
||||
<i class="fa fa-edit"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div ng-if="user.all_projects.length == 0" translate>{{ 'no_projects' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
@ -24,8 +24,8 @@
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div>
|
||||
<ng-include src="'<%= asset_path 'shared/_member_select.html' %>'" ng-if="currentUser.role === 'admin'"></ng-include>
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -12,6 +12,51 @@ class API::InvoicesController < API::ApiController
|
||||
send_file File.join(Rails.root, @invoice.file), :type => 'application/pdf', :disposition => 'attachment'
|
||||
end
|
||||
|
||||
def list
|
||||
authorize Invoice
|
||||
|
||||
p = params.require(:query).permit(:number, :customer, :date, :order_by, :page, :size)
|
||||
|
||||
unless p[:page].is_a? Integer
|
||||
render json: {error: 'page must be an integer'}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
unless p[:size].is_a? Integer
|
||||
render json: {error: 'size must be an integer'}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
|
||||
direction = (p[:order_by][0] == '-' ? 'DESC' : 'ASC')
|
||||
order_key = (p[:order_by][0] == '-' ? p[:order_by][1, p[:order_by].size] : p[:order_by])
|
||||
|
||||
case order_key
|
||||
when 'reference'
|
||||
order_key = 'invoices.reference'
|
||||
when 'date'
|
||||
order_key = 'invoices.created_at'
|
||||
when 'total'
|
||||
order_key = 'invoices.total'
|
||||
when 'name'
|
||||
order_key = 'profiles.first_name'
|
||||
else
|
||||
order_key = 'invoices.id'
|
||||
end
|
||||
|
||||
@invoices = Invoice.includes(:avoir, :invoiced, invoice_items: [:subscription, :invoice_item], user: [:profile, :trainings])
|
||||
.joins(:user => :profile)
|
||||
.order("#{order_key} #{direction}")
|
||||
.page(p[:page])
|
||||
.per(p[:size])
|
||||
|
||||
# ILIKE => PostgreSQL case-insensitive LIKE
|
||||
@invoices = @invoices.where('invoices.reference LIKE :search', search: "#{p[:number].to_s}%") if p[:number].size > 0
|
||||
@invoices = @invoices.where('profiles.first_name ILIKE :search OR profiles.last_name ILIKE :search', search: "%#{p[:customer]}%") if p[:customer].size > 0
|
||||
@invoices = @invoices.where("date_trunc('day', invoices.created_at) = :search", search: "%#{DateTime.iso8601(p[:date]).to_time.to_date.to_s}%") unless p[:date].nil?
|
||||
|
||||
@invoices
|
||||
|
||||
end
|
||||
|
||||
# only for create avoir
|
||||
def create
|
||||
authorize Invoice
|
||||
|
@ -6,6 +6,10 @@ class API::MembersController < API::ApiController
|
||||
def index
|
||||
@requested_attributes = params[:requested_attributes]
|
||||
@members = policy_scope(User)
|
||||
|
||||
unless params[:page].nil? and params[:size].nil?
|
||||
@members = @members.page(params[:page].to_i).per(params[:size].to_i)
|
||||
end
|
||||
end
|
||||
|
||||
def last_subscribed
|
||||
@ -131,6 +135,75 @@ class API::MembersController < API::ApiController
|
||||
end
|
||||
end
|
||||
|
||||
def list
|
||||
authorize User
|
||||
|
||||
p = params.require(:query).permit(:search, :order_by, :page, :size)
|
||||
|
||||
unless p[:page].is_a? Integer
|
||||
render json: {error: 'page must be an integer'}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
unless p[:size].is_a? Integer
|
||||
render json: {error: 'size must be an integer'}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
|
||||
direction = (p[:order_by][0] == '-' ? 'DESC' : 'ASC')
|
||||
order_key = (p[:order_by][0] == '-' ? p[:order_by][1, p[:order_by].size] : p[:order_by])
|
||||
|
||||
case order_key
|
||||
when 'last_name'
|
||||
order_key = 'profiles.last_name'
|
||||
when 'first_name'
|
||||
order_key = 'profiles.first_name'
|
||||
when 'email'
|
||||
order_key = 'users.email'
|
||||
when 'phone'
|
||||
order_key = 'profiles.phone'
|
||||
when 'group'
|
||||
order_key = 'groups.name'
|
||||
when 'plan'
|
||||
order_key = 'plans.base_name'
|
||||
else
|
||||
order_key = 'users.id'
|
||||
end
|
||||
|
||||
@members = User.includes(:profile, :group)
|
||||
.joins(:profile, :group, :roles, 'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id" LEFT JOIN "plans" ON "plans"."id" = "subscriptions"."plan_id"')
|
||||
.where("users.is_active = 'true' AND roles.name = 'member'")
|
||||
.order("#{order_key} #{direction}")
|
||||
.page(p[:page])
|
||||
.per(p[:size])
|
||||
|
||||
# ILIKE => PostgreSQL case-insensitive LIKE
|
||||
@members = @members.where('profiles.first_name ILIKE :search OR profiles.last_name ILIKE :search OR profiles.phone ILIKE :search OR email ILIKE :search OR groups.name ILIKE :search OR plans.base_name ILIKE :search', search: "%#{p[:search]}%") if p[:search].size > 0
|
||||
|
||||
@members
|
||||
|
||||
end
|
||||
|
||||
def search
|
||||
@members = User.includes(:profile)
|
||||
.joins(:profile, :roles, 'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id"')
|
||||
.where("users.is_active = 'true' AND roles.name = 'member'")
|
||||
.where("lower(f_unaccent(profiles.first_name)) ~ regexp_replace(:search, E'\\\\s+', '|') OR lower(f_unaccent(profiles.last_name)) ~ regexp_replace(:search, E'\\\\s+', '|')", search: params[:query].downcase)
|
||||
|
||||
if current_user.is_member?
|
||||
# non-admin can only retrieve users with "public profiles"
|
||||
@members = @members.where("users.is_allow_contact = 'true'")
|
||||
else
|
||||
# only admins have the ability to filter by subscription
|
||||
if params[:subscription] === 'true'
|
||||
@members = @members.where('subscriptions.id IS NOT NULL AND subscriptions.expired_at >= :now', now: Date.today.to_s)
|
||||
elsif params[:subscription] === 'false'
|
||||
@members = @members.where('subscriptions.id IS NULL OR subscriptions.expired_at < :now', now: Date.today.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
@members
|
||||
end
|
||||
|
||||
private
|
||||
def set_member
|
||||
@member = User.find(params[:id])
|
||||
@ -139,13 +212,15 @@ class API::MembersController < API::ApiController
|
||||
def user_params
|
||||
if current_user.id == params[:id].to_i
|
||||
params.require(:user).permit(:username, :email, :password, :password_confirmation, :group_id, :is_allow_contact,
|
||||
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest, :software_mastered,
|
||||
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest, :software_mastered, :website, :job,
|
||||
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo, :dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
|
||||
:user_avatar_attributes => [:id, :attachment, :_destroy], :address_attributes => [:id, :address]])
|
||||
|
||||
elsif current_user.is_admin?
|
||||
params.require(:user).permit(:username, :email, :password, :password_confirmation, :invoicing_disabled,
|
||||
params.require(:user).permit(:username, :email, :password, :password_confirmation, :invoicing_disabled, :is_allow_contact,
|
||||
:group_id, training_ids: [], tag_ids: [],
|
||||
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest, :software_mastered,
|
||||
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest, :software_mastered, :website, :job,
|
||||
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo, :dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
|
||||
user_avatar_attributes: [:id, :attachment, :_destroy], address_attributes: [:id, :address]])
|
||||
|
||||
end
|
||||
|
@ -1,12 +1,21 @@
|
||||
class API::TrainingsController < API::ApiController
|
||||
include ApplicationHelper
|
||||
|
||||
before_action :authenticate_user!, except: [:index, :show]
|
||||
before_action :set_training, only: [:show, :update, :destroy]
|
||||
before_action :set_training, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@requested_attributes = params[:requested_attributes]
|
||||
@trainings = policy_scope(Training)
|
||||
|
||||
if attribute_requested?(@requested_attributes, 'availabilities')
|
||||
@trainings = @trainings.includes(:availabilities => [:slots => [:reservation => [:user => [:profile, :trainings]]]]).order('availabilities.start_at DESC')
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@training = Training.includes(availabilities: {slots: {reservation: {user: [:profile, :trainings] }}})
|
||||
.where(id: params[:id]).first
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -7,4 +7,12 @@ class CustomAsset < ActiveRecord::Base
|
||||
asset = CustomAsset.find_by(name: name)
|
||||
asset.custom_asset_file.attachment_url if asset and asset.custom_asset_file
|
||||
end
|
||||
|
||||
after_update :update_stylesheet if :viewable_changed?
|
||||
|
||||
def update_stylesheet
|
||||
if %w(profile-image-file).include? self.name
|
||||
Stylesheet.first.rebuild!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -73,7 +73,10 @@ class Stylesheet < ActiveRecord::Base
|
||||
.pricing-panel .content .wrap { border-color: #{Stylesheet.secondary}; }
|
||||
.pricing-panel .cta-button .btn:hover, .pricing-panel .cta-button .custom-invoice .modal-body .elements li:hover, .custom-invoice .modal-body .elements .pricing-panel .cta-button li:hover { background-color: #{Stylesheet.secondary} !important; }
|
||||
a.label:hover, .form-control.form-control-ui-select .select2-choices a.select2-search-choice:hover, a.label:focus, .form-control.form-control-ui-select .select2-choices a.select2-search-choice:focus { color: #{Stylesheet.primary}; }
|
||||
.about-picture { background: linear-gradient( rgba(255,255,255,0.12), rgba(255,255,255,0.13) ), linear-gradient( #{Stylesheet.primary_with_alpha(0.78)}, #{Stylesheet.primary_with_alpha(0.82)} ), url('/about-fablab.jpg') no-repeat; }"
|
||||
.about-picture { background: linear-gradient( rgba(255,255,255,0.12), rgba(255,255,255,0.13) ), linear-gradient( #{Stylesheet.primary_with_alpha(0.78)}, #{Stylesheet.primary_with_alpha(0.82)} ), url('/about-fablab.jpg') no-repeat; }
|
||||
.social-icons > div:hover { background-color: #{Stylesheet.secondary}; }
|
||||
.profile-top { background: linear-gradient( rgba(255,255,255,0.12), rgba(255,255,255,0.13) ), linear-gradient(#{Stylesheet.primary_with_alpha(0.78)}, #{Stylesheet.primary_with_alpha(0.82)} ), url('#{CustomAsset.get_url('profile-image-file') || '/about-fablab.jpg'}') no-repeat; }
|
||||
.profile-top .social-links a:hover { background-color: #{Stylesheet.secondary} !important; border-color: #{Stylesheet.secondary} !important; }"
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -10,4 +10,8 @@ class InvoicePolicy < ApplicationPolicy
|
||||
def create?
|
||||
user.is_admin?
|
||||
end
|
||||
|
||||
def list?
|
||||
user.is_admin?
|
||||
end
|
||||
end
|
||||
|
@ -1,16 +1,14 @@
|
||||
class TrainingPolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
scope.includes(:plans, :machines, :availabilities => [:slots => [:reservation => [:user => [:profile, :trainings]]]]).order('availabilities.start_at DESC')
|
||||
scope.includes(:plans, :machines)
|
||||
end
|
||||
end
|
||||
|
||||
def create?
|
||||
user.is_admin?
|
||||
end
|
||||
|
||||
def update?
|
||||
user.is_admin?
|
||||
%w(show create update).each do |action|
|
||||
define_method "#{action}?" do
|
||||
user.is_admin?
|
||||
end
|
||||
end
|
||||
|
||||
def destroy?
|
||||
|
@ -4,7 +4,7 @@ class UserPolicy < ApplicationPolicy
|
||||
if user.is_admin?
|
||||
scope.includes(:group, :training_credits, :machine_credits, :subscriptions => [:plan => [:credits]], :profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc')
|
||||
else
|
||||
scope.includes(:group, :training_credits, :machine_credits, :profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").where(is_allow_contact: true).order('users.created_at desc')
|
||||
scope.includes(:profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").where(is_allow_contact: true).order('users.created_at desc')
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -28,4 +28,9 @@ class UserPolicy < ApplicationPolicy
|
||||
def merge?
|
||||
user.id == record.id
|
||||
end
|
||||
|
||||
def list?
|
||||
user.is_admin?
|
||||
end
|
||||
|
||||
end
|
||||
|
15
app/views/api/invoices/list.json.jbuilder
Normal file
15
app/views/api/invoices/list.json.jbuilder
Normal file
@ -0,0 +1,15 @@
|
||||
maxInvoices = @invoices.except(:offset, :limit, :order).count
|
||||
|
||||
json.array!(@invoices) do |invoice|
|
||||
json.maxInvoices maxInvoices
|
||||
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)
|
||||
json.name invoice.user.profile.full_name
|
||||
json.has_avoir invoice.has_avoir
|
||||
json.is_avoir invoice.is_a?(Avoir)
|
||||
json.is_subscription_invoice invoice.is_subscription_invoice?
|
||||
json.stripe invoice.stp_invoice_id?
|
||||
json.date invoice.is_a?(Avoir) ? invoice.avoir_date : invoice.created_at
|
||||
json.prevent_refund invoice.prevent_refund?
|
||||
end
|
@ -1,6 +1,8 @@
|
||||
user_is_admin = (current_user and current_user.is_admin?)
|
||||
maxMembers = @members.except(:offset, :limit, :order).count
|
||||
|
||||
json.array!(@members) do |member|
|
||||
json.maxMembers maxMembers
|
||||
json.id member.id
|
||||
json.username member.username
|
||||
json.slug member.slug
|
||||
|
18
app/views/api/members/list.json.jbuilder
Normal file
18
app/views/api/members/list.json.jbuilder
Normal file
@ -0,0 +1,18 @@
|
||||
maxMembers = @members.except(:offset, :limit, :order).count
|
||||
|
||||
json.array!(@members) do |member|
|
||||
json.maxMembers maxMembers
|
||||
json.id member.id
|
||||
json.email member.email if current_user
|
||||
json.profile do
|
||||
json.first_name member.profile.first_name
|
||||
json.last_name member.profile.last_name
|
||||
json.phone member.profile.phone
|
||||
end
|
||||
json.group do
|
||||
json.name member.group.name
|
||||
end
|
||||
json.subscribed_plan do
|
||||
json.partial! 'api/shared/plan', plan: member.subscribed_plan
|
||||
end if member.subscribed_plan
|
||||
end
|
5
app/views/api/members/search.json.jbuilder
Normal file
5
app/views/api/members/search.json.jbuilder
Normal file
@ -0,0 +1,5 @@
|
||||
json.array!(@members) do |member|
|
||||
json.id member.id
|
||||
json.name "#{member.profile.first_name} #{member.profile.last_name}"
|
||||
json.group_id member.group_id
|
||||
end
|
@ -1,4 +1,4 @@
|
||||
json.extract! @member, :id, :username, :email, :group_id, :slug, :invoicing_disabled, :is_allow_contact
|
||||
json.extract! @member, :id, :uid, :username, :email, :group_id, :slug, :invoicing_disabled, :is_allow_contact
|
||||
json.role @member.roles.first.name
|
||||
json.name @member.profile.full_name
|
||||
json.need_completion @member.need_completion?
|
||||
@ -19,6 +19,9 @@ json.profile do
|
||||
json.address @member.profile.address.address
|
||||
end if @member.profile.address
|
||||
json.phone @member.profile.phone
|
||||
json.website @member.profile.website
|
||||
json.job @member.profile.job
|
||||
json.extract! @member.profile, :facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo, :dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr
|
||||
end
|
||||
json.subscribed_plan do
|
||||
json.partial! 'api/shared/plan', plan: @member.subscribed_plan
|
||||
|
@ -1,5 +1,5 @@
|
||||
json.title notification.notification_type
|
||||
json.description t('.USER_became_collaborator_of_your_project',
|
||||
USER: notification.attached_object.user.profile.full_name) +
|
||||
"<a href='/#!/projects/#{notification.attached_object.project.id}'><strong><em>#{notification.attached_object.project.name}</em></strong></a>."
|
||||
"<a href='/#!/projects/#{notification.attached_object.project.id}'><strong><em> #{notification.attached_object.project.name}</em></strong></a>."
|
||||
json.url notification_url(notification, format: :json)
|
||||
|
@ -12,7 +12,7 @@ json.array!(@trainings) do |training|
|
||||
json.full_name slot.reservation.user.profile.full_name
|
||||
json.is_valid slot.reservation.user.trainings.include?(training)
|
||||
end
|
||||
end
|
||||
end if attribute_requested?(@requested_attributes, 'availabilities')
|
||||
json.nb_total_places training.nb_total_places
|
||||
|
||||
json.plan_ids training.plan_ids if current_user and current_user.is_admin?
|
||||
|
@ -6,6 +6,6 @@ json.availabilities @training.availabilities.order('start_at DESC') do |a|
|
||||
json.reservation_users a.slots.map do |slot|
|
||||
json.id slot.reservation.user.id
|
||||
json.full_name slot.reservation.user.profile.full_name
|
||||
json.is_valid slot.reservation.user.trainings.include?(training)
|
||||
json.is_valid slot.reservation.user.trainings.include?(@training)
|
||||
end
|
||||
end
|
||||
|
@ -15,7 +15,7 @@
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"angular-bootstrap": ">=0.13.1",
|
||||
"angular-bootstrap": "~0.14.3",
|
||||
"angular-ui-router": ">=0.2.15",
|
||||
"fullcalendar": "=2.3.1",
|
||||
"angular-ui-calendar": "0.9.0-beta.1",
|
||||
|
@ -13,12 +13,14 @@ require "rails/all"
|
||||
require 'elasticsearch/rails/instrumentation'
|
||||
require 'elasticsearch/persistence/model'
|
||||
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
module Fablab
|
||||
class Application < Rails::Application
|
||||
require 'fab_manager'
|
||||
# Settings in config/environments/* take precedence over those specified here.
|
||||
# Application configuration should go into files in config/initializers
|
||||
# -- all .rb files in that directory are automatically loaded.
|
||||
@ -62,5 +64,20 @@ module Fablab
|
||||
config.web_console.whitelisted_ips << '10.0.2.2' #vagrant
|
||||
end
|
||||
|
||||
# enable the app to find locales in plugins locales directory
|
||||
config.i18n.load_path += Dir["#{Rails.root}/plugins/*/config/locales/*.yml"]
|
||||
|
||||
# enable the app to find views in plugins views directory
|
||||
Dir["#{Rails.root}/plugins/*/views"].each do |path|
|
||||
Rails.application.config.paths['app/views'] << path
|
||||
end
|
||||
|
||||
FabManager.activate_plugins!
|
||||
|
||||
config.after_initialize do
|
||||
if plugins = FabManager.plugins
|
||||
plugins.each { |plugin| plugin.notify_after_initialize }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -14,7 +14,8 @@ Sidekiq.configure_server do |config|
|
||||
schedule_file = "config/schedule.yml"
|
||||
|
||||
if File.exists?(schedule_file)
|
||||
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
|
||||
rendered_schedule_file = ERB.new(File.read(schedule_file)).result
|
||||
Sidekiq::Cron::Job.load_from_hash YAML.load(rendered_schedule_file)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -160,6 +160,7 @@ en:
|
||||
invoice_#: "Invoice #"
|
||||
customer: "Customer"
|
||||
credit_note: "Credit note"
|
||||
display_more_invoices: "Display more invoices..."
|
||||
invoicing_settings: "Invoicing settings"
|
||||
change_logo: "Change logo"
|
||||
john_smith: "John Smith"
|
||||
@ -265,6 +266,7 @@ en:
|
||||
email: "Email"
|
||||
phone: "Phone"
|
||||
user_type: "User type"
|
||||
display_more_users: "Display more users..."
|
||||
administrators: "Administrators"
|
||||
search_for_an_administrator: "Search for an administrator"
|
||||
add_a_new_administrator: "Add a new administrator"
|
||||
@ -439,6 +441,8 @@ en:
|
||||
primary: "Primary"
|
||||
secondary_colour: "Secondary colour:"
|
||||
secondary: "Secondary"
|
||||
background_picture_of_the_profile_banner: "Background picture of the profile banner"
|
||||
change_the_profile_banner: "Change the profile banner"
|
||||
home_page: "Home page"
|
||||
news_of_the_home_page: "News of the home page:"
|
||||
type_your_news_here: "Type your news here"
|
||||
|
@ -160,6 +160,7 @@ fr:
|
||||
invoice_#: "Facture n°"
|
||||
customer: "Client"
|
||||
credit_note: "Avoir"
|
||||
display_more_invoices: "Afficher plus de factures ..."
|
||||
invoicing_settings: "Paramètres de facturation"
|
||||
change_logo: "Changer le logo"
|
||||
john_smith: "Jean Dupont"
|
||||
@ -265,6 +266,7 @@ fr:
|
||||
email: "Courriel"
|
||||
phone: "Tel."
|
||||
user_type: "Type utilisateur"
|
||||
display_more_users: "Afficher plus d'utilisateurs ..."
|
||||
administrators: "Administrateurs"
|
||||
search_for_an_administrator: "Recherchez un administrateur"
|
||||
add_a_new_administrator: "Ajouter un nouvel administrateur"
|
||||
@ -439,6 +441,8 @@ fr:
|
||||
primary: "Primaire"
|
||||
secondary_colour: "Couleur secondaire :"
|
||||
secondary: "Secondaire"
|
||||
background_picture_of_the_profile_banner: "Image de fond du bandeau de profil"
|
||||
change_the_profile_banner: "Changer le bandeau de profil"
|
||||
home_page: "Page d'accueil"
|
||||
news_of_the_home_page: "Brève de la page d'accueil :"
|
||||
type_your_news_here: "Saisir votre brève ici"
|
||||
|
@ -23,8 +23,11 @@ en:
|
||||
an_unexpected_error_occurred_check_your_authentication_code: "An unexpected error occurred, please check your authentication code."
|
||||
|
||||
dashboard:
|
||||
# dashboard: public profile
|
||||
profile:
|
||||
# dashboard: my profile
|
||||
empty: ''
|
||||
settings:
|
||||
# dashboard: edit my profile
|
||||
last_activity_on_: "Last activity on"
|
||||
i_want_to_change_group: "I want to change group!"
|
||||
your_subscription_expires_on_: "Your subscription expires on"
|
||||
@ -69,18 +72,12 @@ en:
|
||||
members_show:
|
||||
# public profil of a member
|
||||
members_list: "Members list"
|
||||
last_activity_: "Last activity"
|
||||
_on_: "on"
|
||||
to_come: "to come"
|
||||
approved: "approved"
|
||||
projects: "Projects"
|
||||
no_projects: "No projects"
|
||||
author: "Author"
|
||||
collaborator: "Collaborator"
|
||||
|
||||
members:
|
||||
# list of members accepting to be contacted
|
||||
the_fablab_members: "The Fab Lab members"
|
||||
display_more_members: "Display more members..."
|
||||
no_members_for_now: "No members for now"
|
||||
avatar: "Avatar"
|
||||
|
||||
projects_new:
|
||||
|
@ -23,8 +23,11 @@ fr:
|
||||
an_unexpected_error_occurred_check_your_authentication_code: "Une erreur inattendue est survenue, vérifiez votre code d'authentification."
|
||||
|
||||
dashboard:
|
||||
# tableau de bord: profile publique
|
||||
profile:
|
||||
# tableau de bord : mon profil
|
||||
empty: ''
|
||||
settings:
|
||||
# tableau de bord : éditer mon profil
|
||||
last_activity_on_: "Dernière activité le"
|
||||
i_want_to_change_group: "Je veux changer de groupe !"
|
||||
your_subscription_expires_on_: "Votre abonnement expire le"
|
||||
@ -69,18 +72,12 @@ fr:
|
||||
members_show:
|
||||
# profil public d'un membre
|
||||
members_list: "Liste des membres"
|
||||
last_activity_: "Dernière activité"
|
||||
_on_: "le"
|
||||
to_come: "à venir"
|
||||
approved: "validée"
|
||||
projects: "Projets"
|
||||
no_projects: "Aucun projet"
|
||||
author: "Auteur"
|
||||
collaborator: "Collaborateur"
|
||||
|
||||
members:
|
||||
# liste des membres qui acceptent d'être contactés
|
||||
the_fablab_members: "Les membres du Fab Lab"
|
||||
display_more_members: "Afficher plus de membres ..."
|
||||
no_members_for_now: "Pas de membres pour le moment"
|
||||
avatar: "Avatar"
|
||||
|
||||
projects_new:
|
||||
|
@ -9,6 +9,7 @@ en:
|
||||
# dashboard sections
|
||||
dashboard: "Dashboard"
|
||||
my_profile: "My Profile"
|
||||
my_settings: "My Settings"
|
||||
my_projects: "My Projects"
|
||||
my_trainings: "My Trainings"
|
||||
my_courses_and_workshops: "My Courses and Workshops"
|
||||
@ -34,6 +35,7 @@ en:
|
||||
|
||||
# left menu (admin)
|
||||
trainings_monitoring: "Trainings monitoring"
|
||||
select_a_training: "Select a training"
|
||||
manage_the_calendar: "Manage the Calendar"
|
||||
manage_the_users: "Manage the Users"
|
||||
manage_the_invoices: "Manage the invoices"
|
||||
|
@ -9,6 +9,7 @@ fr:
|
||||
# sections du tableau de bord
|
||||
dashboard: "Tableau de bord"
|
||||
my_profile: "Mon profil"
|
||||
my_settings: "Mes paramètres"
|
||||
my_projects: "Mes projets"
|
||||
my_trainings: "Mes formations"
|
||||
my_courses_and_workshops: "Mes stages et ateliers"
|
||||
@ -34,6 +35,7 @@ fr:
|
||||
|
||||
# menu de gauche (partie admin)
|
||||
trainings_monitoring: "Suivi formations"
|
||||
select_a_training: "Sélectionnez une formation"
|
||||
manage_the_calendar: "Gérer le calendrier"
|
||||
manage_the_users: "Gérer les utilisateurs"
|
||||
manage_the_invoices: "Gérer les factures"
|
||||
|
@ -104,6 +104,8 @@ en:
|
||||
confirmation_mismatch_with_password: "Confirmation mismatch with password."
|
||||
date_of_birth: "Date of birth"
|
||||
date_of_birth_is_required: "Date of birth is required."
|
||||
website: "Website"
|
||||
job: "Occupation"
|
||||
|
||||
project:
|
||||
# project edition form
|
||||
@ -144,6 +146,7 @@ en:
|
||||
member_select:
|
||||
# admin: choose a member to interact with
|
||||
select_a_member: "Select a member"
|
||||
start_typing: "Start typing..."
|
||||
please_select_a_member_first: "Please select a member first"
|
||||
|
||||
stripe:
|
||||
@ -267,3 +270,15 @@ en:
|
||||
machine_reservation: "Machine reservation"
|
||||
you_must_wait_for_your_training_is_being_validated_by_the_fablab_team_to_book_this_machine: "You must wait for your training is being validated by the FabLab team to book this machine."
|
||||
your_training_will_occur_: "Your training will occur"
|
||||
|
||||
public_profile:
|
||||
# user public profile
|
||||
last_activity_: "Last activity"
|
||||
_on_: "on"
|
||||
to_come: "to come"
|
||||
approved: "approved"
|
||||
projects: "Projects"
|
||||
no_projects: "No projects"
|
||||
author: "Author"
|
||||
collaborator: "Collaborator"
|
||||
private_profile: "Private profile"
|
@ -104,6 +104,8 @@ fr:
|
||||
confirmation_mismatch_with_password: "La confirmation ne concorde pas avec le mot de passe."
|
||||
date_of_birth: "Date de naissance"
|
||||
date_of_birth_is_required: "La date de naissance est requise."
|
||||
website: "Site web"
|
||||
job: "Profession"
|
||||
|
||||
project:
|
||||
# formulaire d'étition d'un projet
|
||||
@ -144,6 +146,7 @@ fr:
|
||||
member_select:
|
||||
# admin : choisir un membre avec lequel interagir
|
||||
select_a_member: "Sélectionnez un membre"
|
||||
start_typing: "Commencez à taper ..."
|
||||
please_select_a_member_first: "Veuillez tout d'abord sélectionner un membre"
|
||||
|
||||
stripe:
|
||||
@ -267,3 +270,15 @@ fr:
|
||||
machine_reservation: "Réservation machine"
|
||||
you_must_wait_for_your_training_is_being_validated_by_the_fablab_team_to_book_this_machine: "Il faut attendre que votre formation soit validée par l'équipe du Fab Lab pour réserver cette machine."
|
||||
your_training_will_occur_: "Votre formation aura lieu le"
|
||||
|
||||
public_profile:
|
||||
# profil publique d'un utilisateur
|
||||
last_activity_: "Dernière activité"
|
||||
_on_: "le"
|
||||
to_come: "à venir"
|
||||
approved: "validée"
|
||||
projects: "Projets"
|
||||
no_projects: "Aucun projet"
|
||||
author: "Auteur"
|
||||
collaborator: "Collaborateur"
|
||||
private_profile: "Profil privé"
|
||||
|
@ -208,7 +208,7 @@ fr:
|
||||
notify_partner_subscribed_plan:
|
||||
subscription_partner_PLAN_has_been_subscribed_by_USER_html: "L'abonnement partenaire <strong><em>%{PLAN}</em></strong> a été souscrit par <strong><em>%{USER}</strong></em>."
|
||||
notify_project_author_when_collaborator_valid:
|
||||
USER_became_collaborator_of_your_project_PROJECT: "Le membre %{USER} est devenu un collaborateur de votre projet :"
|
||||
USER_became_collaborator_of_your_project: "Le membre %{USER} est devenu un collaborateur de votre projet :"
|
||||
notify_project_collaborator_to_valid:
|
||||
you_are_invited_to_collaborate_on_the_project: "Vous êtes invité à collaborer sur le projet suivant :"
|
||||
notify_user_auth_migration:
|
||||
|
@ -40,6 +40,8 @@ Rails.application.routes.draw do
|
||||
get '/export_reservations', action: 'export_reservations', on: :collection
|
||||
get '/export_members', action: 'export_members', on: :collection
|
||||
put ':id/merge', action: 'merge', on: :collection
|
||||
post 'list', action: 'list', on: :collection
|
||||
get 'search/:query', action: 'search', on: :collection
|
||||
end
|
||||
resources :reservations, only: [:show, :create, :index, :update]
|
||||
resources :notifications, only: [:index, :show, :update] do
|
||||
@ -78,6 +80,7 @@ Rails.application.routes.draw do
|
||||
|
||||
resources :invoices, only: [:index, :show, :create] do
|
||||
get ':id/download', action: 'download', on: :collection
|
||||
post 'list', action: 'list', on: :collection
|
||||
end
|
||||
|
||||
# for admin
|
||||
|
@ -14,3 +14,5 @@ generate_statistic:
|
||||
cron: "0 1 * * *"
|
||||
class: "StatisticWorker"
|
||||
queue: default
|
||||
|
||||
<%= PluginRegistry.insert_code('yml.schedule') %>
|
||||
|
@ -31,6 +31,8 @@ development:
|
||||
openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %>
|
||||
openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %>
|
||||
openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %>
|
||||
navinum_api_login: <%= ENV["NAVINUM_API_LOGIN"] %>
|
||||
navinum_api_password: <%= ENV["NAVINUM_API_PASSWORD"] %>
|
||||
|
||||
test:
|
||||
secret_key_base: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30
|
||||
@ -53,6 +55,8 @@ test:
|
||||
openlab_app_secret:
|
||||
openlab_app_id:
|
||||
openlab_base_uri:
|
||||
navinum_api_login:
|
||||
navinum_api_password:
|
||||
|
||||
staging:
|
||||
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
||||
@ -82,6 +86,8 @@ staging:
|
||||
openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %>
|
||||
openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %>
|
||||
openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %>
|
||||
navinum_api_login: <%= ENV["NAVINUM_API_LOGIN"] %>
|
||||
navinum_api_password: <%= ENV["NAVINUM_API_PASSWORD"] %>
|
||||
|
||||
# Do not keep production secrets in the repository,
|
||||
# instead read values from the environment.
|
||||
@ -114,3 +120,5 @@ production:
|
||||
openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %>
|
||||
openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %>
|
||||
google_analytics_id: <%= ENV["GA_ID"] %>
|
||||
navinum_api_login: <%= ENV["NAVINUM_API_LOGIN"] %>
|
||||
navinum_api_password: <%= ENV["NAVINUM_API_PASSWORD"] %>
|
||||
|
15
db/migrate/20160516090121_add_social_fields_to_profile.rb
Normal file
15
db/migrate/20160516090121_add_social_fields_to_profile.rb
Normal file
@ -0,0 +1,15 @@
|
||||
class AddSocialFieldsToProfile < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :profiles, :facebook, :string
|
||||
add_column :profiles, :twitter, :string
|
||||
add_column :profiles, :google_plus, :string
|
||||
add_column :profiles, :viadeo, :string
|
||||
add_column :profiles, :linkedin, :string
|
||||
add_column :profiles, :instagram, :string
|
||||
add_column :profiles, :youtube, :string
|
||||
add_column :profiles, :vimeo, :string
|
||||
add_column :profiles, :dailymotion, :string
|
||||
add_column :profiles, :github, :string
|
||||
add_column :profiles, :echosciences, :string
|
||||
end
|
||||
end
|
5
db/migrate/20160516124056_add_website_to_profile.rb
Normal file
5
db/migrate/20160516124056_add_website_to_profile.rb
Normal file
@ -0,0 +1,5 @@
|
||||
class AddWebsiteToProfile < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :profiles, :website, :string
|
||||
end
|
||||
end
|
7
db/migrate/20160526095550_add_socials_to_profile.rb
Normal file
7
db/migrate/20160526095550_add_socials_to_profile.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class AddSocialsToProfile < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :profiles, :pinterest, :string
|
||||
add_column :profiles, :lastfm, :string
|
||||
add_column :profiles, :flickr, :string
|
||||
end
|
||||
end
|
5
db/migrate/20160526102307_add_job_to_profile.rb
Normal file
5
db/migrate/20160526102307_add_job_to_profile.rb
Normal file
@ -0,0 +1,5 @@
|
||||
class AddJobToProfile < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :profiles, :job, :string
|
||||
end
|
||||
end
|
25
db/migrate/20160613093842_create_unaccent_function.rb
Normal file
25
db/migrate/20160613093842_create_unaccent_function.rb
Normal file
@ -0,0 +1,25 @@
|
||||
class CreateUnaccentFunction < ActiveRecord::Migration
|
||||
|
||||
# PostgreSQL only
|
||||
def up
|
||||
execute 'CREATE EXTENSION unaccent;'
|
||||
execute 'CREATE EXTENSION pg_trgm;'
|
||||
execute "CREATE OR REPLACE FUNCTION f_unaccent(text)
|
||||
RETURNS text AS
|
||||
$func$
|
||||
SELECT public.unaccent('public.unaccent', $1)
|
||||
$func$ LANGUAGE sql IMMUTABLE;"
|
||||
execute 'CREATE INDEX profiles_lower_unaccent_first_name_trgm_idx ON profiles
|
||||
USING gin (lower(f_unaccent(first_name)) gin_trgm_ops);'
|
||||
execute 'CREATE INDEX profiles_lower_unaccent_last_name_trgm_idx ON profiles
|
||||
USING gin (lower(f_unaccent(last_name)) gin_trgm_ops);'
|
||||
end
|
||||
|
||||
def down
|
||||
execute 'DROP INDEX profiles_lower_unaccent_first_name_trgm_idx;'
|
||||
execute 'DROP INDEX profiles_lower_unaccent_last_name_trgm_idx;'
|
||||
execute 'DROP FUNCTION f_unaccent(text);'
|
||||
execute 'DROP EXTENSION pg_trgm;'
|
||||
execute 'DROP EXTENSION unaccent;'
|
||||
end
|
||||
end
|
219
db/schema.rb
219
db/schema.rb
@ -11,10 +11,12 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
ActiveRecord::Schema.define(version: 20160526102307) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
enable_extension "unaccent"
|
||||
enable_extension "pg_trgm"
|
||||
|
||||
create_table "abuses", force: :cascade do |t|
|
||||
t.integer "signaled_id"
|
||||
@ -30,23 +32,23 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "abuses", ["signaled_type", "signaled_id"], name: "index_abuses_on_signaled_type_and_signaled_id", using: :btree
|
||||
|
||||
create_table "addresses", force: :cascade do |t|
|
||||
t.string "address"
|
||||
t.string "street_number"
|
||||
t.string "route"
|
||||
t.string "locality"
|
||||
t.string "country"
|
||||
t.string "postal_code"
|
||||
t.string "address", limit: 255
|
||||
t.string "street_number", limit: 255
|
||||
t.string "route", limit: 255
|
||||
t.string "locality", limit: 255
|
||||
t.string "country", limit: 255
|
||||
t.string "postal_code", limit: 255
|
||||
t.integer "placeable_id"
|
||||
t.string "placeable_type"
|
||||
t.string "placeable_type", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
create_table "assets", force: :cascade do |t|
|
||||
t.integer "viewable_id"
|
||||
t.string "viewable_type"
|
||||
t.string "attachment"
|
||||
t.string "type"
|
||||
t.string "viewable_type", limit: 255
|
||||
t.string "attachment", limit: 255
|
||||
t.string "type", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
@ -63,11 +65,11 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
create_table "availabilities", force: :cascade do |t|
|
||||
t.datetime "start_at"
|
||||
t.datetime "end_at"
|
||||
t.string "available_type"
|
||||
t.string "available_type", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "nb_total_places"
|
||||
t.boolean "destroying", default: false
|
||||
t.boolean "destroying", default: false
|
||||
end
|
||||
|
||||
create_table "availability_tags", force: :cascade do |t|
|
||||
@ -81,18 +83,18 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "availability_tags", ["tag_id"], name: "index_availability_tags_on_tag_id", using: :btree
|
||||
|
||||
create_table "categories", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "name", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
create_table "components", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "name", limit: 255, null: false
|
||||
end
|
||||
|
||||
create_table "credits", force: :cascade do |t|
|
||||
t.integer "creditable_id"
|
||||
t.string "creditable_type"
|
||||
t.string "creditable_type", limit: 255
|
||||
t.integer "plan_id"
|
||||
t.integer "hours"
|
||||
t.datetime "created_at"
|
||||
@ -113,7 +115,7 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
end
|
||||
|
||||
create_table "events", force: :cascade do |t|
|
||||
t.string "title"
|
||||
t.string "title", limit: 255
|
||||
t.text "description"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
@ -139,10 +141,10 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "events_categories", ["event_id"], name: "index_events_categories_on_event_id", using: :btree
|
||||
|
||||
create_table "friendly_id_slugs", force: :cascade do |t|
|
||||
t.string "slug", null: false
|
||||
t.integer "sluggable_id", null: false
|
||||
t.string "slug", limit: 255, null: false
|
||||
t.integer "sluggable_id", null: false
|
||||
t.string "sluggable_type", limit: 50
|
||||
t.string "scope"
|
||||
t.string "scope", limit: 255
|
||||
t.datetime "created_at"
|
||||
end
|
||||
|
||||
@ -152,17 +154,17 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree
|
||||
|
||||
create_table "groups", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "name", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "slug"
|
||||
t.string "slug", limit: 255
|
||||
end
|
||||
|
||||
add_index "groups", ["slug"], name: "index_groups_on_slug", unique: true, using: :btree
|
||||
|
||||
create_table "invoice_items", force: :cascade do |t|
|
||||
t.integer "invoice_id"
|
||||
t.string "stp_invoice_item_id"
|
||||
t.string "stp_invoice_item_id", limit: 255
|
||||
t.integer "amount"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
@ -175,17 +177,17 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
|
||||
create_table "invoices", force: :cascade do |t|
|
||||
t.integer "invoiced_id"
|
||||
t.string "invoiced_type"
|
||||
t.string "stp_invoice_id"
|
||||
t.string "invoiced_type", limit: 255
|
||||
t.string "stp_invoice_id", limit: 255
|
||||
t.integer "total"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "user_id"
|
||||
t.string "reference"
|
||||
t.string "avoir_mode"
|
||||
t.string "reference", limit: 255
|
||||
t.string "avoir_mode", limit: 255
|
||||
t.datetime "avoir_date"
|
||||
t.integer "invoice_id"
|
||||
t.string "type"
|
||||
t.string "type", limit: 255
|
||||
t.boolean "subscription_to_expire"
|
||||
t.text "description"
|
||||
end
|
||||
@ -194,17 +196,17 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "invoices", ["user_id"], name: "index_invoices_on_user_id", using: :btree
|
||||
|
||||
create_table "licences", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "name", limit: 255, null: false
|
||||
t.text "description"
|
||||
end
|
||||
|
||||
create_table "machines", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "name", limit: 255, null: false
|
||||
t.text "description"
|
||||
t.text "spec"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "slug"
|
||||
t.string "slug", limit: 255
|
||||
end
|
||||
|
||||
add_index "machines", ["slug"], name: "index_machines_on_slug", unique: true, using: :btree
|
||||
@ -220,14 +222,14 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
create_table "notifications", force: :cascade do |t|
|
||||
t.integer "receiver_id"
|
||||
t.integer "attached_object_id"
|
||||
t.string "attached_object_type"
|
||||
t.string "attached_object_type", limit: 255
|
||||
t.integer "notification_type_id"
|
||||
t.boolean "is_read", default: false
|
||||
t.boolean "is_read", default: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "receiver_type"
|
||||
t.boolean "is_send", default: false
|
||||
t.jsonb "meta_data", default: {}
|
||||
t.string "receiver_type", limit: 255
|
||||
t.boolean "is_send", default: false
|
||||
t.jsonb "meta_data", default: {}
|
||||
end
|
||||
|
||||
add_index "notifications", ["notification_type_id"], name: "index_notifications_on_notification_type_id", using: :btree
|
||||
@ -267,21 +269,39 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
|
||||
add_index "offer_days", ["subscription_id"], name: "index_offer_days_on_subscription_id", using: :btree
|
||||
|
||||
create_table "plans", force: :cascade do |t|
|
||||
create_table "open_api_calls_count_tracings", force: :cascade do |t|
|
||||
t.integer "open_api_client_id"
|
||||
t.integer "calls_count", null: false
|
||||
t.datetime "at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
add_index "open_api_calls_count_tracings", ["open_api_client_id"], name: "index_open_api_calls_count_tracings_on_open_api_client_id", using: :btree
|
||||
|
||||
create_table "open_api_clients", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.integer "calls_count", default: 0
|
||||
t.string "token"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "plans", force: :cascade do |t|
|
||||
t.string "name", limit: 255
|
||||
t.integer "amount"
|
||||
t.string "interval"
|
||||
t.string "interval", limit: 255
|
||||
t.integer "group_id"
|
||||
t.string "stp_plan_id"
|
||||
t.string "stp_plan_id", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "training_credit_nb", default: 0
|
||||
t.boolean "is_rolling", default: true
|
||||
t.integer "training_credit_nb", default: 0
|
||||
t.boolean "is_rolling", default: true
|
||||
t.text "description"
|
||||
t.string "type"
|
||||
t.string "base_name"
|
||||
t.integer "ui_weight", default: 0
|
||||
t.integer "interval_count", default: 1
|
||||
t.integer "ui_weight", default: 0
|
||||
t.integer "interval_count", default: 1
|
||||
end
|
||||
|
||||
add_index "plans", ["group_id"], name: "index_plans_on_group_id", using: :btree
|
||||
@ -302,15 +322,31 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
|
||||
create_table "profiles", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.string "first_name"
|
||||
t.string "last_name"
|
||||
t.string "first_name", limit: 255
|
||||
t.string "last_name", limit: 255
|
||||
t.boolean "gender"
|
||||
t.date "birthday"
|
||||
t.string "phone"
|
||||
t.string "phone", limit: 255
|
||||
t.text "interest"
|
||||
t.text "software_mastered"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "facebook"
|
||||
t.string "twitter"
|
||||
t.string "google_plus"
|
||||
t.string "viadeo"
|
||||
t.string "linkedin"
|
||||
t.string "instagram"
|
||||
t.string "youtube"
|
||||
t.string "vimeo"
|
||||
t.string "dailymotion"
|
||||
t.string "github"
|
||||
t.string "echosciences"
|
||||
t.string "website"
|
||||
t.string "pinterest"
|
||||
t.string "lastfm"
|
||||
t.string "flickr"
|
||||
t.string "job"
|
||||
end
|
||||
|
||||
add_index "profiles", ["user_id"], name: "index_profiles_on_user_id", using: :btree
|
||||
@ -320,7 +356,7 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
t.integer "project_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "title"
|
||||
t.string "title", limit: 255
|
||||
end
|
||||
|
||||
add_index "project_steps", ["project_id"], name: "index_project_steps_on_project_id", using: :btree
|
||||
@ -330,27 +366,27 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
t.integer "user_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "is_valid", default: false
|
||||
t.string "valid_token"
|
||||
t.boolean "is_valid", default: false
|
||||
t.string "valid_token", limit: 255
|
||||
end
|
||||
|
||||
add_index "project_users", ["project_id"], name: "index_project_users_on_project_id", using: :btree
|
||||
add_index "project_users", ["user_id"], name: "index_project_users_on_user_id", using: :btree
|
||||
|
||||
create_table "projects", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "name", limit: 255
|
||||
t.text "description"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "author_id"
|
||||
t.text "tags"
|
||||
t.integer "licence_id"
|
||||
t.string "state"
|
||||
t.string "slug"
|
||||
t.string "state", limit: 255
|
||||
t.string "slug", limit: 255
|
||||
t.datetime "published_at"
|
||||
end
|
||||
|
||||
add_index "projects", ["slug"], name: "index_projects_on_slug", unique: true, using: :btree
|
||||
add_index "projects", ["slug"], name: "index_projects_on_slug", using: :btree
|
||||
|
||||
create_table "projects_components", force: :cascade do |t|
|
||||
t.integer "project_id"
|
||||
@ -382,20 +418,20 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "reservable_id"
|
||||
t.string "reservable_type"
|
||||
t.string "stp_invoice_id"
|
||||
t.string "reservable_type", limit: 255
|
||||
t.string "stp_invoice_id", limit: 255
|
||||
t.integer "nb_reserve_places"
|
||||
t.integer "nb_reserve_reduced_places"
|
||||
end
|
||||
|
||||
add_index "reservations", ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id", using: :btree
|
||||
add_index "reservations", ["reservable_id", "reservable_type"], name: "index_reservations_on_reservable_id_and_reservable_type", using: :btree
|
||||
add_index "reservations", ["stp_invoice_id"], name: "index_reservations_on_stp_invoice_id", using: :btree
|
||||
add_index "reservations", ["user_id"], name: "index_reservations_on_user_id", using: :btree
|
||||
|
||||
create_table "roles", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "name", limit: 255
|
||||
t.integer "resource_id"
|
||||
t.string "resource_type"
|
||||
t.string "resource_type", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
@ -420,8 +456,8 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
t.datetime "updated_at"
|
||||
t.integer "availability_id"
|
||||
t.datetime "ex_start_at"
|
||||
t.datetime "ex_end_at"
|
||||
t.datetime "canceled_at"
|
||||
t.datetime "ex_end_at"
|
||||
t.boolean "offered", default: false
|
||||
end
|
||||
|
||||
@ -430,18 +466,18 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
|
||||
create_table "statistic_fields", force: :cascade do |t|
|
||||
t.integer "statistic_index_id"
|
||||
t.string "key"
|
||||
t.string "label"
|
||||
t.string "key", limit: 255
|
||||
t.string "label", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "data_type"
|
||||
t.string "data_type", limit: 255
|
||||
end
|
||||
|
||||
add_index "statistic_fields", ["statistic_index_id"], name: "index_statistic_fields_on_statistic_index_id", using: :btree
|
||||
|
||||
create_table "statistic_graphs", force: :cascade do |t|
|
||||
t.integer "statistic_index_id"
|
||||
t.string "chart_type"
|
||||
t.string "chart_type", limit: 255
|
||||
t.integer "limit"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
@ -450,17 +486,17 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "statistic_graphs", ["statistic_index_id"], name: "index_statistic_graphs_on_statistic_index_id", using: :btree
|
||||
|
||||
create_table "statistic_indices", force: :cascade do |t|
|
||||
t.string "es_type_key"
|
||||
t.string "label"
|
||||
t.string "es_type_key", limit: 255
|
||||
t.string "label", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "table", default: true
|
||||
t.boolean "ca", default: true
|
||||
t.boolean "table", default: true
|
||||
t.boolean "ca", default: true
|
||||
end
|
||||
|
||||
create_table "statistic_sub_types", force: :cascade do |t|
|
||||
t.string "key"
|
||||
t.string "label"
|
||||
t.string "key", limit: 255
|
||||
t.string "label", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
@ -477,8 +513,8 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
|
||||
create_table "statistic_types", force: :cascade do |t|
|
||||
t.integer "statistic_index_id"
|
||||
t.string "key"
|
||||
t.string "label"
|
||||
t.string "key", limit: 255
|
||||
t.string "label", limit: 255
|
||||
t.boolean "graph"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
@ -496,7 +532,7 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
create_table "subscriptions", force: :cascade do |t|
|
||||
t.integer "plan_id"
|
||||
t.integer "user_id"
|
||||
t.string "stp_subscription_id"
|
||||
t.string "stp_subscription_id", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "expired_at"
|
||||
@ -515,15 +551,15 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
|
||||
|
||||
create_table "themes", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "name", limit: 255, null: false
|
||||
end
|
||||
|
||||
create_table "trainings", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "name", limit: 255
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "nb_total_places"
|
||||
t.string "slug"
|
||||
t.string "slug", limit: 255
|
||||
t.text "description"
|
||||
end
|
||||
|
||||
@ -579,32 +615,32 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "user_trainings", ["user_id"], name: "index_user_trainings_on_user_id", using: :btree
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", default: "", null: false
|
||||
t.string "reset_password_token"
|
||||
t.string "username", limit: 255
|
||||
t.string "email", limit: 255, default: "", null: false
|
||||
t.string "encrypted_password", limit: 255, default: "", null: false
|
||||
t.string "reset_password_token", limit: 255
|
||||
t.datetime "reset_password_sent_at"
|
||||
t.datetime "remember_created_at"
|
||||
t.integer "sign_in_count", default: 0, null: false
|
||||
t.integer "sign_in_count", default: 0, null: false
|
||||
t.datetime "current_sign_in_at"
|
||||
t.datetime "last_sign_in_at"
|
||||
t.string "current_sign_in_ip"
|
||||
t.string "last_sign_in_ip"
|
||||
t.string "confirmation_token"
|
||||
t.string "current_sign_in_ip", limit: 255
|
||||
t.string "last_sign_in_ip", limit: 255
|
||||
t.string "confirmation_token", limit: 255
|
||||
t.datetime "confirmed_at"
|
||||
t.datetime "confirmation_sent_at"
|
||||
t.string "unconfirmed_email"
|
||||
t.integer "failed_attempts", default: 0, null: false
|
||||
t.string "unlock_token"
|
||||
t.string "unconfirmed_email", limit: 255
|
||||
t.integer "failed_attempts", default: 0, null: false
|
||||
t.string "unlock_token", limit: 255
|
||||
t.datetime "locked_at"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "is_allow_contact", default: true
|
||||
t.boolean "is_allow_contact", default: true
|
||||
t.integer "group_id"
|
||||
t.string "stp_customer_id"
|
||||
t.string "username"
|
||||
t.string "slug"
|
||||
t.boolean "is_active", default: true
|
||||
t.boolean "invoicing_disabled", default: false
|
||||
t.string "stp_customer_id", limit: 255
|
||||
t.string "slug", limit: 255
|
||||
t.boolean "is_active", default: true
|
||||
t.boolean "invoicing_disabled", default: false
|
||||
t.string "provider"
|
||||
t.string "uid"
|
||||
t.string "auth_token"
|
||||
@ -643,6 +679,7 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_foreign_key "availability_tags", "availabilities"
|
||||
add_foreign_key "availability_tags", "tags"
|
||||
add_foreign_key "o_auth2_mappings", "o_auth2_providers"
|
||||
add_foreign_key "open_api_calls_count_tracings", "open_api_clients"
|
||||
add_foreign_key "prices", "groups"
|
||||
add_foreign_key "prices", "plans"
|
||||
add_foreign_key "user_tags", "tags"
|
||||
|
@ -98,27 +98,27 @@ Finally you have to create an admin interface with AngularJS:
|
||||
- **app/assets/javascript/controllers/admin/authentifications.coffee**
|
||||
|
||||
|
||||
## list of supported authentication methods
|
||||
METHODS = {
|
||||
...
|
||||
'LdapProvider' : 'LDAP' # add the name of your ActiveRecord model class here as a hash key, associated with a human readable name as a hash value (string)
|
||||
}
|
||||
## list of supported authentication methods
|
||||
METHODS = {
|
||||
...
|
||||
'LdapProvider' : 'LDAP' # add the name of your ActiveRecord model class here as a hash key, associated with a human readable name as a hash value (string)
|
||||
}
|
||||
|
||||
Application.Controllers.controller "newAuthentificationController", ...
|
||||
|
||||
$scope.updateProvidable = ->
|
||||
...
|
||||
if $scope.provider.providable_type == 'LdapProvider'
|
||||
# you may want to do some stuff to initialize your provider here
|
||||
Application.Controllers.controller "newAuthentificationController", ...
|
||||
|
||||
$scope.registerProvider = ->
|
||||
...
|
||||
# === LdapProvider ===
|
||||
else if $scope.provider.providable_type == 'LdapProvider'
|
||||
# here you may want to do some data validation
|
||||
# then: save the settings
|
||||
AuthProvider.save auth_provider: $scope.provider, (provider) ->
|
||||
# register was a success, display a message, redirect, etc.
|
||||
$scope.updateProvidable = ->
|
||||
...
|
||||
if $scope.provider.providable_type == 'LdapProvider'
|
||||
# you may want to do some stuff to initialize your provider here
|
||||
|
||||
$scope.registerProvider = ->
|
||||
...
|
||||
# === LdapProvider ===
|
||||
else if $scope.provider.providable_type == 'LdapProvider'
|
||||
# here you may want to do some data validation
|
||||
# then: save the settings
|
||||
AuthProvider.save auth_provider: $scope.provider, (provider) ->
|
||||
# register was a success, display a message, redirect, etc.
|
||||
|
||||
And to include this interface into the existing one ( **app/assets/templates/admin/authentifications/edit.html.erb**)
|
||||
|
||||
|
@ -215,3 +215,69 @@ docker run --restart=always -d --name=fabmanager \
|
||||
|
||||
`docker exec -it fabmanager-app bash`
|
||||
|
||||
|
||||
|
||||
|
||||
### Docker Compose
|
||||
|
||||
#### download docker compose https://github.com/docker/compose/releases
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/docker/compose/releases/download/1.7.1/docker-compose-`uname -s`-`uname -m` > ./docker-compose
|
||||
sudo mkdir -p /opt/bin
|
||||
sudo mv docker-compose /opt/bin/
|
||||
sudo chmod +x /opt/bin/docker-compose
|
||||
```
|
||||
|
||||
#### Setup folders and env file
|
||||
|
||||
```bash
|
||||
mkdir -p /home/core/fabmanager/config
|
||||
```
|
||||
|
||||
Copy the previously customized `env` file as `/home/core/fabmanager/config/env`.
|
||||
|
||||
```bash
|
||||
mkdir -p /home/core/fabmanager/config/nginx
|
||||
```
|
||||
|
||||
Copy the previously customized `nginx.conf` as `/home/core/fabmanager/config/nginx/fabmanager.conf`.
|
||||
|
||||
#### copy docker-compose.yml to /home/core/
|
||||
|
||||
#### pull images
|
||||
|
||||
`docker-compose pull`
|
||||
|
||||
#### create/migrate/seed db
|
||||
|
||||
`docker-compose run --rm fabmanager bundle exec rake db:setup`
|
||||
|
||||
#### build assets
|
||||
|
||||
`docker-compose run --rm fabmanager bundle exec rake assets:precompile`
|
||||
|
||||
#### PREPARE ELASTIC
|
||||
`docker-compose run --rm fabmanager bundle exec rake fablab:es_build_stats`
|
||||
|
||||
#### run create and run all services
|
||||
|
||||
`docker-compose up -d`
|
||||
|
||||
#### restart all services
|
||||
|
||||
`docker-compose restart`
|
||||
|
||||
#### show services status
|
||||
|
||||
`docker-compose ps`
|
||||
|
||||
#### update service fabmanager, rebuild assets and restart fabmanager
|
||||
|
||||
```bash
|
||||
docker-compose pull fabmanager
|
||||
docker-compose stop fabmanager
|
||||
sudo rm -rf fabmanager/public/assets
|
||||
docker-compose run --rm fabmanager bundle exec rake assets:precompile
|
||||
docker-compose start fabmanager
|
||||
```
|
||||
|
41
docker/docker-compose.yml
Normal file
41
docker/docker-compose.yml
Normal file
@ -0,0 +1,41 @@
|
||||
version: '2'
|
||||
services:
|
||||
fabmanager:
|
||||
image: sleede/fab-manager
|
||||
environment:
|
||||
RAILS_ENV: production
|
||||
RACK_ENV: production
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
env_file:
|
||||
- /home/core/fabmanager/config/env
|
||||
volumes:
|
||||
- /home/core/fabmanager/config/nginx:/etc/nginx/conf.d
|
||||
- /home/core/fabmanager/public/assets:/usr/src/app/public/assets
|
||||
- /home/core/fabmanager/public/uploads:/usr/src/app/public/uploads
|
||||
- /home/core/fabmanager/invoices:/usr/src/app/invoices
|
||||
- /home/core/fabmanager/log:/var/log/supervisor
|
||||
depends_on:
|
||||
- fabmanager-postgres
|
||||
- fabmanager-redis
|
||||
- fabmanager-elastic
|
||||
restart: always
|
||||
|
||||
fabmanager-postgres:
|
||||
image: postgres:9.4
|
||||
volumes:
|
||||
- /home/core/fabmanager/postgresql:/var/lib/postgresql/data
|
||||
restart: always
|
||||
|
||||
fabmanager-elastic:
|
||||
image: elasticsearch:1.7
|
||||
volumes:
|
||||
- /home/core/fabmanager/elasticsearch:/usr/share/elasticsearch/data
|
||||
restart: always
|
||||
|
||||
fabmanager-redis:
|
||||
image: redis:3.0
|
||||
volumes:
|
||||
- /home/core/fabmanager/redis:/data
|
||||
restart: always
|
@ -45,5 +45,9 @@ WEEK_STARTING_DAY=monday
|
||||
D3_DATE_FORMAT=%d/%m/%y
|
||||
UIB_DATE_FORMAT=dd/MM/yyyy
|
||||
|
||||
OPENLAB_APP_SECRET: 'fSF9jZEWxjHyqjAzzg34jd92'
|
||||
OPENLAB_APP_ID: 'xLn9CmryyURNNHZiDRYVRXbv'
|
||||
OPENLAB_APP_SECRET=fSF9jZEWxjHyqjAzzg34jd92
|
||||
OPENLAB_APP_ID=xLn9CmryyURNNHZiDRYVRXbv
|
||||
|
||||
|
||||
NAVINUM_API_LOGIN:
|
||||
NAVINUM_API_PASSWORD:
|
||||
|
17
lib/fab_manager.rb
Normal file
17
lib/fab_manager.rb
Normal file
@ -0,0 +1,17 @@
|
||||
require_dependency 'plugin/instance'
|
||||
|
||||
module FabManager
|
||||
class << self
|
||||
attr_reader :plugins
|
||||
end
|
||||
|
||||
def self.activate_plugins!
|
||||
all_plugins = Plugin::Instance.find_all("#{Rails.root}/plugins")
|
||||
|
||||
@plugins = []
|
||||
all_plugins.each do |p|
|
||||
p.activate!
|
||||
@plugins << p
|
||||
end
|
||||
end
|
||||
end
|
98
lib/plugin/instance.rb
Normal file
98
lib/plugin/instance.rb
Normal file
@ -0,0 +1,98 @@
|
||||
require 'fileutils'
|
||||
require 'plugin_registry'
|
||||
|
||||
module Plugin
|
||||
class Instance
|
||||
attr_accessor :path#, :directory
|
||||
|
||||
[:assets, :initializers, :javascripts, :styles].each do |att|
|
||||
class_eval %Q{
|
||||
def #{att}
|
||||
@#{att} ||= []
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.find_all(parent_path)
|
||||
[].tap { |plugins|
|
||||
# also follows symlinks - http://stackoverflow.com/q/357754
|
||||
Dir["#{parent_path}/**/*/**/plugin.rb"].sort.each do |path|
|
||||
|
||||
source = File.read(path)
|
||||
# metadata = Plugin::Metadata.parse(source)
|
||||
plugins << self.new(nil, path)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def initialize(metadata=nil, path=nil)
|
||||
@metadata = metadata
|
||||
@path = path
|
||||
#@directory = path.match(/(.*)\/plugin.rb/)[1]
|
||||
@idx = 0
|
||||
end
|
||||
|
||||
def activate!
|
||||
if @path
|
||||
root_path = "#{File.dirname(@path)}/assets/javascripts"
|
||||
PluginRegistry.register_glob(root_path, 'coffee.erb')
|
||||
end
|
||||
|
||||
self.instance_eval File.read(path), path # execute all code of the plugin main file ! (named plugin.rb)
|
||||
|
||||
register_assets! unless assets.blank?
|
||||
|
||||
Rails.configuration.assets.paths << File.dirname(path) + "/assets"
|
||||
|
||||
Rails.configuration.assets.precompile += [lambda do |filename, path|
|
||||
(Dir["plugins/*/assets/templates"].any? { |p| path.include?(p) }) # useless because already included in application.css/js || (%w(.js).include?(File.extname(filename)) && Dir["plugins/*/assets/javascripts"].any? { |p| path.include?(p) }) || (%w(.css).include?(File.extname(filename)) && Dir["plugins/*/assets/stylesheets"].any? { |p| path.include?(p) })
|
||||
end] #
|
||||
|
||||
Rails.configuration.sass.load_paths += Dir["plugins/*/assets/stylesheets"]
|
||||
|
||||
|
||||
# Automatically include rake tasks
|
||||
Rake.add_rakelib(File.dirname(path) + "/lib/tasks")
|
||||
|
||||
# Automatically include migrations
|
||||
Rails.configuration.paths["db/migrate"] << File.dirname(path) + "/db/migrate"
|
||||
end
|
||||
|
||||
def register_asset(file, opts=nil) # to be used by the plugin !
|
||||
full_path = File.dirname(path) << "/assets/" << file
|
||||
assets << [full_path, opts]
|
||||
end
|
||||
|
||||
def register_code_insertion(key, code)
|
||||
PluginRegistry.code_insertions[key] ||= []
|
||||
PluginRegistry.code_insertions[key] << code
|
||||
end
|
||||
|
||||
def register_css(style) # useless ?
|
||||
styles << style
|
||||
end
|
||||
|
||||
def after_initialize(&block)
|
||||
initializers << block
|
||||
end
|
||||
|
||||
def notify_after_initialize
|
||||
initializers.each do |callback|
|
||||
begin
|
||||
callback.call(self)
|
||||
rescue ActiveRecord::StatementInvalid => e
|
||||
# When running db:migrate for the first time on a new database, plugin initializers might
|
||||
# try to use models. Tolerate it.
|
||||
raise e unless e.message.try(:include?, "PG::UndefinedTable")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def register_assets!
|
||||
assets.each do |asset, opts|
|
||||
PluginRegistry.register_asset(asset, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
58
lib/plugin_registry.rb
Normal file
58
lib/plugin_registry.rb
Normal file
@ -0,0 +1,58 @@
|
||||
class PluginRegistry
|
||||
class << self
|
||||
attr_writer :javascripts
|
||||
attr_writer :stylesheets
|
||||
|
||||
def asset_globs
|
||||
@asset_globs ||= Set.new
|
||||
end
|
||||
|
||||
def javascripts
|
||||
@javascripts ||= Set.new
|
||||
end
|
||||
|
||||
def stylesheets
|
||||
@stylesheets ||= Set.new
|
||||
end
|
||||
|
||||
def code_insertions
|
||||
@code_insertions ||= {}
|
||||
end
|
||||
end
|
||||
|
||||
def self.register_glob(root, extension, options=nil)
|
||||
self.asset_globs << [root, extension, options || {}]
|
||||
end
|
||||
|
||||
def self.register_asset(asset, opts=nil)
|
||||
if asset =~ /\.js$|\.js\.erb$|\.js\.es6$|\.coffee$|\.coffee\.erb/
|
||||
# if opts == :admin
|
||||
# self.admin_javascripts << asset
|
||||
# else
|
||||
# if opts == :server_side
|
||||
# self.server_side_javascripts << asset
|
||||
# end
|
||||
self.javascripts << asset
|
||||
# end
|
||||
elsif asset =~ /\.css$|\.scss$/
|
||||
# if opts == :mobile
|
||||
# self.mobile_stylesheets << asset
|
||||
# elsif opts == :desktop
|
||||
# self.desktop_stylesheets << asset
|
||||
# elsif opts == :variables
|
||||
# self.sass_variables << asset
|
||||
# else
|
||||
self.stylesheets << asset
|
||||
# end
|
||||
|
||||
# elsif asset =~ /\.hbs$/
|
||||
# self.handlebars << asset
|
||||
# elsif asset =~ /\.js\.handlebars$/
|
||||
# self.handlebars << asset
|
||||
end
|
||||
end
|
||||
|
||||
def self.insert_code(key)
|
||||
self.code_insertions[key]&.join('\n')
|
||||
end
|
||||
end
|
0
plugins/.keep
Normal file
0
plugins/.keep
Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user