mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
Merge branch 'dev' for release 4.3.0
This commit is contained in:
commit
b15edb1fee
10
.github/ISSUE_TEMPLATE.md
vendored
10
.github/ISSUE_TEMPLATE.md
vendored
@ -1,8 +1,14 @@
|
||||
This issue tracker is **reserved** for bug reports and feature requests.
|
||||
This issue tracker is **reserved** for bug reports.
|
||||
|
||||
The place to ask a question or call for help is at Fab-manager forums at https://forum.fab-manager.com/.
|
||||
The place to ask a question or call for help is at [Fab-manager forums](https://forum.fab-manager.com)
|
||||
|
||||
The place to request or vote for new feature is on the [feedback website](https://feedback.fab-manager.com)
|
||||
|
||||
To report a bug, please describe:
|
||||
- Expected behavior and actual behavior.
|
||||
- Steps to reproduce the problem.
|
||||
- Specifications like the version of the project, operating system, or hardware.
|
||||
|
||||
The following elements may help to quickly resolve your issue:
|
||||
- Server logs `tail -f /apps/fabmanager/log/app-stdout.log` on the server
|
||||
- Client logs `Ctrl`+`Maj`+`i` > `Console` in the browser
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
Fab-Manager uses some external components, which are licenced under the
|
||||
Fab-manager uses some external components, which are licenced under the
|
||||
terms of the following licences:
|
||||
|
||||
- [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0):
|
||||
|
99
CHANGELOG.md
99
CHANGELOG.md
@ -1,4 +1,78 @@
|
||||
# Changelog Fab Manager
|
||||
# Changelog Fab-manager
|
||||
|
||||
## v4.3.0 2020 March 04
|
||||
|
||||
- Ability to configure reservation slot restricted for plan subscribers
|
||||
- Ability to configure the policy (allow or prevent) for members booking a machine/formation/event slot, if they already have a reservation the same day at the same time
|
||||
- Ability to create and delete periodic calendar availabilities (recurrence)
|
||||
- Ability to fully customize the home page
|
||||
- Automated setup assistant
|
||||
- An administrator can delete a member
|
||||
- An event reservation can be cancelled, if reservation cancellation is enabled
|
||||
- Delete multiple recurring events at one time
|
||||
- Edit multiple recurring events at one time
|
||||
- Ability to import iCalendar agendas in the public calendar, through URLs to ICS files (RFC 5545)
|
||||
- Ability to configure the duration of a reservation slot, using `SLOT_DURATION`. Previously, only 60 minutes slots were allowed
|
||||
- Ability to force the email validation when a new user registers. This is optionally configured with `USER_CONFIRMATION_NEEDED_TO_SIGN_IN`
|
||||
- Display the scheduled events in the admin calendar, depending on `EVENTS_IN_CALENDAR` configuration.
|
||||
- Display indications on required fields in new administrator form
|
||||
- Administrators can to book machine/space/training slots, until 1 month in the past
|
||||
- Filter members by non-validated emails or by inactive for 3 years
|
||||
- Ability to customize the title of the link to the about page
|
||||
- Automatic version check with security alerts
|
||||
- Public endpoint to check the system health
|
||||
- Configuration of phone number in members registration forms: can be required or optional, depending on `PHONE_REQUIRED` configuration
|
||||
- Improved user experience in defining slots in the calendar management
|
||||
- Improved notification email to the member when a rolling subscription is taken
|
||||
- Notify all admins on the creation of a refund invoice
|
||||
- Helper links between admin sections of the scheduling process
|
||||
- Calendar management: improved legend display and visual behavior
|
||||
- Reorganized left menu
|
||||
- Create machine availabilities: select all/none in a click
|
||||
- Prevent event reservation in the past [Taiga#127]
|
||||
- Removed the need of twitter API keys to display the last tweet on the home page
|
||||
- Various helper links to help newcomers creating their first items
|
||||
- Handle Ctrl^C in upgrade scripts
|
||||
- Updated moment-timezone
|
||||
- Updated angular-ui-bootstrap from v0.14 to v1.2
|
||||
- Updated caxlsx to 3.0.1 and rails_axlsx to rails_caxlsx
|
||||
- Updated sidekiq to 5.2.8
|
||||
- Option to disable developers analytics
|
||||
- Added the a "cron" tab in Sidekiq web-ui to watch scheduled tasks
|
||||
- Integration of Crowdin "in-context" translation management system
|
||||
- Added freeCAD files as default allowed extensions
|
||||
- Rake task to sync local users with Stripe
|
||||
- Unified translations syntax to use ICU MessageFormat
|
||||
- Refactored front-end translations keys with unified paths
|
||||
- Updated and refactored README and documentations
|
||||
- Harmonized Fab-manager typography and case
|
||||
- Updated seeds file
|
||||
- Fix a bug: unable to remove the picture from a training
|
||||
- Fix a bug: no alerts on errors during admin creation
|
||||
- Fix a bug: replaces all Time.now by DateTime.current to prevent time zones issues [Taiga#134]
|
||||
- Fix a bug: logs are not printed in staging environment
|
||||
- Fix a bug: theme colors must be selected twice before the changes became effective
|
||||
- Fix a bug: datepicker does not work in profile completion screen
|
||||
- Fix a bug: unable to select a group in profile completion screen
|
||||
- Fix a bug: in some cases, bogus admin notification on profile completed
|
||||
- Fix a bug: with Firefox browser, the texts in date inputs are shifted to the bottom
|
||||
- Fix a bug: sometimes when browsing the invoices section, the translations are missing
|
||||
- Fix a bug: first day of week is ignored in agendas (#169)
|
||||
- Fix a bug: statistics page is bogus before the creation of the first plan
|
||||
- Fix a bug: default invoice logo is broken and prevent invoice generation
|
||||
- Fix a security issue: updated loofah to fix [CVE-2019-15587](https://nvd.nist.gov/vuln/detail/CVE-2019-15587)
|
||||
- Fix a security issue: updated angular to 1.7.9 to fix [CVE-2019-10768](https://nvd.nist.gov/vuln/detail/CVE-2019-10768)
|
||||
- Fix a security issue: updated puma to 3.12.4 to fix [GHSA-7xx3-m584-x994](https://github.com/advisories/GHSA-7xx3-m584-x994), [CVE-2020-5247](https://nvd.nist.gov/vuln/detail/CVE-2020-5247) and [CVE-2019-16254](https://nvd.nist.gov/vuln/detail/CVE-2020-5247)
|
||||
- Fix a security issue: updated nokogiri to 1.10.8 to fix [CVE-2020-7595](https://nvd.nist.gov/vuln/detail/CVE-2020-7595)
|
||||
- Fix a security issue: updated rack to 1.6.12 to fix [CVE-2019-16782](https://nvd.nist.gov/vuln/detail/CVE-2019-16782)
|
||||
- [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details)
|
||||
- [TODO DEPLOY] add the `PHONE_REQUIRED` environment variable (see [doc/environment.md](doc/environment.md#PHONE_REQUIRED) for configuration details)
|
||||
- [TODO DEPLOY] add the `EVENTS_IN_CALENDAR` environment variable (see [doc/environment.md](doc/environment.md#EVENTS_IN_CALENDAR) for configuration details)
|
||||
- [TODO DEPLOY] add the `USER_CONFIRMATION_NEEDED_TO_SIGN_IN` environment variable (see [doc/environment.md](doc/environment.md#USER_CONFIRMATION_NEEDED_TO_SIGN_IN) for configuration details)
|
||||
- [TODO DEPLOY] add the `BOOK_SLOT_AT_SAME_TIME` environment variable (see [doc/environment.md](doc/environment.md#BOOK_SLOT_AT_SAME_TIME) for configuration details)
|
||||
- [TODO DEPLOY] -> (only dev) `bundle install && yarn install`
|
||||
- [TODO DEPLOY] `rake db:migrate && rake db:seed`
|
||||
- [TODO DEPLOY] `rake fablab:fix:name_stylesheet`
|
||||
|
||||
## v4.2.4 2019 October 30
|
||||
|
||||
@ -58,6 +132,7 @@
|
||||
- [TODO DEPLOY] add the `MAX_IMPORT_SIZE` environment variable (see [doc/environment.md](doc/environment.md#MAX_IMPORT_SIZE) for configuration details)
|
||||
- [TODO DEPLOY] add the `FABLAB_WITHOUT_INVOICES` environment variable (see [doc/environment.md](doc/environment.md#FABLAB_WITHOUT_INVOICES) for configuration details)
|
||||
- [TODO DEPLOY] add the `SMTP_TLS` environment variable (see [doc/environment.md](doc/environment.md#SMTP_TLS) for configuration details)
|
||||
- [TODO DEPLOY] add the `FABLAB_WITHOUT_WALLET` environment variable (see [doc/environment.md](doc/environment.md#FABLAB_WITHOUT_WALLET) for configuration details)
|
||||
- [TODO DEPLOY] **IMPORTANT** Please read [postgres_upgrade.md](doc/postgres_upgrade.md) for instructions on upgrading PostgreSQL.
|
||||
|
||||
## v4.1.1 2019 September 20
|
||||
@ -91,7 +166,7 @@
|
||||
## v4.0.4 2019 August 14
|
||||
|
||||
- Fix a bug: #140 VAT rate is erroneous in invoices.
|
||||
Note: this bug was introduced in v4.0.3 and requires (if you are on v4.0.3) to regenerate the invoices since August 1st (if
|
||||
Note: this bug was introduced in v4.0.3 and requires (if you are on v4.0.3) to regenerate the invoices since August 1st
|
||||
- [TODO DEPLOY] `rake fablab:maintenance:regenerate_invoices[2019,8]`
|
||||
|
||||
## v4.0.3 2019 August 01
|
||||
@ -135,7 +210,7 @@
|
||||
- Refactored user's profile to keep invoicing data after an user was deleted
|
||||
- Refactored user's profile to keep statistical data after an user was deleted
|
||||
- Ability to delete an user (fixes #129 and #120)
|
||||
- Ask user acceptance before deposing analytics cookies
|
||||
- Ask user acceptance before deposing analytics cookies
|
||||
- Fix a bug: (spanish) some translations are not loaded correctly
|
||||
- Fix a bug: some users may not appear in the admin's general listing
|
||||
- Fix a bug: Availabilities export report an erroneous number of reservations for machine availabilities (#131)
|
||||
@ -171,7 +246,7 @@
|
||||
- Improved date checks before closing an accounting period
|
||||
- Paginate list of coupons
|
||||
- Allow filtering coupons list
|
||||
- Fix a bug: when VAT has changed during fab-manager's lifecycle, this may not be reflected in archives
|
||||
- Fix a bug: when VAT has changed during Fab-manager's lifecycle, this may not be reflected in archives
|
||||
- Fix a bug: using a quote in event category's name results in angular $parse:syntax Error
|
||||
|
||||
## v3.0.1 2019 April 1st
|
||||
@ -204,7 +279,7 @@
|
||||
- [TODO DEPLOY] `rake fablab:setup:chain_invoices_records`
|
||||
- [TODO DEPLOY] `rake fablab:setup:chain_history_values_records`
|
||||
- [TODO DEPLOY] add `DISK_SPACE_MB_ALERT` and `SUPERADMIN_EMAIL` environment variables (see [doc/environment.md](doc/environment.md) for configuration details)
|
||||
- [TODO DEPLOY] add the `accounting` volume to the fab-manager's image in [docker-compose.yml](docker/docker-compose.yml)
|
||||
- [TODO DEPLOY] add the `accounting` volume to the Fab-manager's image in [docker-compose.yml](docker/docker-compose.yml)
|
||||
|
||||
## v2.8.4 2019 March 18
|
||||
|
||||
@ -332,8 +407,8 @@
|
||||
- Fix a security issue: sprockets < 2.12.5 has a security vulnerability as described in [CVE-2018-3760](https://nvd.nist.gov/vuln/detail/CVE-2018-3760)
|
||||
- Ensure elasticSearch indices are started with green status on new installations
|
||||
- Refactored User.to_json to remove code duplication
|
||||
- Fixed syntax and typos in README
|
||||
- [TODO DEPLOY] **IMPORTANT** Please read [elastic_upgrade.md](doc/elastic_upgrade.md) for instructions on upgrading ElasticSearch.
|
||||
- Fixed syntax and typos in README
|
||||
- [TODO DEPLOY] **IMPORTANT** Please read [elastic_upgrade.md](doc/elastic_upgrade.md) for instructions on upgrading ElasticSearch.
|
||||
- [TODO DEPLOY] `rake fablab:fix:categories_slugs`
|
||||
- [TODO DEPLOY] -> (only dev) `bundle install`
|
||||
- [TODO DEPLOY] `rake db:seed`
|
||||
@ -343,10 +418,10 @@
|
||||
- Ability to share trainings on social medias
|
||||
- Fix a bug: a reminder notification were sent for canceled reservations
|
||||
- Fix a bug: sharing an event on facebook has HTML tags in the description
|
||||
- Set Stripe API version, all fab-managers has to use this version because codebase relies on it
|
||||
- Set Stripe API version, all Fab-managers has to use this version because codebase relies on it
|
||||
- Fix a security issue: OmniAuth < 1.3.2 has a security vulnerability described in [CVE-2017-18076](https://nvd.nist.gov/vuln/detail/CVE-2017-18076)
|
||||
- Fix a security issue: rack-protection < 1.5.5 has a security vulnerability described in [CVE-2018-1000119](https://nvd.nist.gov/vuln/detail/CVE-2018-1000119)
|
||||
- Fix a security issue: http gem < 0.7.3 has a security vulnerability described in [CVE-2015-1828](https://nvd.nist.gov/vuln/detail/CVE-2015-1828), updates twitter gem as a dependency
|
||||
- Fix a security issue: http gem < 0.7.3 has a security vulnerability described in [CVE-2015-1828](https://nvd.nist.gov/vuln/detail/CVE-2015-1828), updates twitter gem as a dependency
|
||||
|
||||
## v2.6.3 2018 January 2
|
||||
|
||||
@ -402,12 +477,12 @@
|
||||
|
||||
## v2.5.13 2017 September 11
|
||||
|
||||
- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with recursive events which the initial event was deleted
|
||||
- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with recursive events which the initial event was deleted
|
||||
|
||||
## v2.5.12 2017 September 11
|
||||
|
||||
- Fix a bug: Long words overflow from homepage's events blocks
|
||||
- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with non-recursive events
|
||||
- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with non-recursive events
|
||||
|
||||
## v2.5.11 2017 September 7
|
||||
|
||||
@ -614,7 +689,7 @@
|
||||
- Project images will show in full-size on a click
|
||||
- Add a checkbox "I accept to receive informations from the FabLab" on Sign-up dialog and user's profile
|
||||
- Share project with Facebook/Twitter
|
||||
- Display fab-manager's version in "Powered by" label, when logged as admin
|
||||
- Display Fab-manager's version in "Powered by" label, when logged as admin
|
||||
- Load translation locales from subdirectories
|
||||
- Add wallet to user, client can pay total/partial reservation or subscription by wallet
|
||||
- Public calendar for show all trainings/machines/events
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Contributing to FabManager
|
||||
# Contributing to Fab-manager
|
||||
|
||||
♥ [FabManager](http://www.fab-manager.com) and want to get involved?
|
||||
♥ [Fab-manager](http://www.fab-manager.com) and want to get involved?
|
||||
Thanks! There are plenty of ways you can help!
|
||||
|
||||
Please take a moment to review this document in order to make the contribution process easy and effective for everyone
|
||||
|
11
Gemfile
11
Gemfile
@ -4,7 +4,7 @@ gem 'compass-rails', '2.0.4'
|
||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||
gem 'rails', '4.2.11.1'
|
||||
# Use Puma as web server
|
||||
gem 'puma', '3.10.0'
|
||||
gem 'puma', '3.12.4'
|
||||
# Use SCSS for stylesheets
|
||||
gem 'sass-rails', '5.0.1'
|
||||
|
||||
@ -61,7 +61,7 @@ group :test do
|
||||
gem 'webmock'
|
||||
end
|
||||
|
||||
group :production do
|
||||
group :production, :staging do
|
||||
gem 'rails_12factor'
|
||||
end
|
||||
|
||||
@ -89,9 +89,6 @@ gem 'mini_magick'
|
||||
# upload files
|
||||
gem 'carrierwave'
|
||||
|
||||
gem 'twitter'
|
||||
gem 'twitter-text'
|
||||
|
||||
# slug url
|
||||
gem 'friendly_id', '~> 5.1.0'
|
||||
|
||||
@ -140,8 +137,8 @@ gem 'apipie-rails'
|
||||
gem 'has_secure_token'
|
||||
|
||||
# XLS files generation
|
||||
gem 'axlsx_rails'
|
||||
gem 'caxlsx'
|
||||
gem 'caxlsx_rails'
|
||||
gem 'rubyzip', '>= 1.3.0'
|
||||
|
||||
gem 'rack-protection', '1.5.5'
|
||||
@ -152,3 +149,5 @@ gem 'sys-filesystem'
|
||||
gem 'sha3'
|
||||
|
||||
gem 'repost'
|
||||
|
||||
gem 'icalendar'
|
||||
|
72
Gemfile.lock
72
Gemfile.lock
@ -56,16 +56,12 @@ GEM
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
axlsx_rails (0.6.0)
|
||||
actionpack (>= 3.1)
|
||||
caxlsx (>= 3.0)
|
||||
bcrypt (3.1.13)
|
||||
binding_of_caller (0.7.3)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootstrap-sass (3.4.1)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sassc (>= 2.0.0)
|
||||
buftok (0.2.0)
|
||||
builder (3.2.3)
|
||||
byebug (8.2.3)
|
||||
camertron-eprun (1.1.0)
|
||||
@ -74,11 +70,14 @@ GEM
|
||||
activesupport (>= 3.2.0)
|
||||
json (>= 1.7)
|
||||
mime-types (>= 1.16)
|
||||
caxlsx (3.0.0)
|
||||
caxlsx (3.0.1)
|
||||
htmlentities (~> 4.3, >= 4.3.4)
|
||||
mimemagic (~> 0.3)
|
||||
nokogiri (~> 1.8, >= 1.8.2)
|
||||
rubyzip (~> 1.2, >= 1.2.1)
|
||||
nokogiri (~> 1.10, >= 1.10.4)
|
||||
rubyzip (>= 1.3.0, < 3)
|
||||
caxlsx_rails (0.6.2)
|
||||
actionpack (>= 3.1)
|
||||
caxlsx (>= 3.0)
|
||||
chroma (0.0.1)
|
||||
chunky_png (1.3.4)
|
||||
cldr-plurals-runtime-rb (1.0.1)
|
||||
@ -123,8 +122,6 @@ GEM
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
elasticsearch (5.0.5)
|
||||
elasticsearch-api (= 5.0.5)
|
||||
elasticsearch-transport (= 5.0.5)
|
||||
@ -153,7 +150,7 @@ GEM
|
||||
execjs (2.7.0)
|
||||
faker (1.4.3)
|
||||
i18n (~> 0.5)
|
||||
faraday (0.17)
|
||||
faraday (0.17.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.9.24)
|
||||
figaro (1.1.0)
|
||||
@ -177,20 +174,14 @@ GEM
|
||||
hashie (3.6.0)
|
||||
hike (1.2.3)
|
||||
htmlentities (4.3.4)
|
||||
http (3.0.0)
|
||||
addressable (~> 2.3)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (>= 2.0.0.pre.pre2, < 3)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.1.0)
|
||||
http_parser.rb (0.6.0)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
icalendar (2.5.3)
|
||||
ice_cube (~> 0.16)
|
||||
ice_cube (0.16.3)
|
||||
ice_nine (0.11.2)
|
||||
jaro_winkler (1.5.1)
|
||||
jbuilder (2.5.0)
|
||||
@ -208,7 +199,7 @@ GEM
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
libv8 (3.16.14.19)
|
||||
loofah (2.3.0)
|
||||
loofah (2.3.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
@ -221,12 +212,10 @@ GEM
|
||||
skinny (~> 0.2.3)
|
||||
sqlite3 (~> 1.3)
|
||||
thin (~> 1.5.0)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
message_format (0.0.3)
|
||||
twitter_cldr (~> 3.1)
|
||||
mime-types (2.99.3)
|
||||
mimemagic (0.3.3)
|
||||
mimemagic (0.3.4)
|
||||
mini_magick (4.9.4)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
@ -239,8 +228,7 @@ GEM
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
naught (1.1.0)
|
||||
nokogiri (1.10.4)
|
||||
nokogiri (1.10.8)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
notify_with (0.0.2)
|
||||
jbuilder (~> 2.0)
|
||||
@ -284,11 +272,11 @@ GEM
|
||||
protected_attributes (1.1.3)
|
||||
activemodel (>= 4.0.1, < 5.0)
|
||||
public_suffix (3.0.2)
|
||||
puma (3.10.0)
|
||||
puma (3.12.4)
|
||||
pundit (1.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.1.6)
|
||||
rack (1.6.11)
|
||||
rack (1.6.13)
|
||||
rack-protection (1.5.5)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
@ -335,7 +323,7 @@ GEM
|
||||
recurrence (1.3.0)
|
||||
activesupport
|
||||
i18n
|
||||
redis (4.1.2)
|
||||
redis (4.1.3)
|
||||
redis-namespace (1.6.0)
|
||||
redis (>= 3.0.4)
|
||||
ref (2.0.0)
|
||||
@ -372,15 +360,14 @@ GEM
|
||||
activerecord (~> 4)
|
||||
activesupport (~> 4)
|
||||
sha3 (1.0.1)
|
||||
sidekiq (5.2.7)
|
||||
sidekiq (5.2.8)
|
||||
connection_pool (~> 2.2, >= 2.2.2)
|
||||
rack (>= 1.5.0)
|
||||
rack (< 2.1.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (>= 3.3.5, < 5)
|
||||
sidekiq-cron (1.1.0)
|
||||
fugit (~> 1.1)
|
||||
sidekiq (>= 4.2.1)
|
||||
simple_oauth (0.3.1)
|
||||
simplecov (0.12.0)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
@ -423,19 +410,6 @@ GEM
|
||||
tilt (1.4.1)
|
||||
tins (1.13.0)
|
||||
ttfunk (1.4.0)
|
||||
twitter (6.2.0)
|
||||
addressable (~> 2.3)
|
||||
buftok (~> 0.2.0)
|
||||
equalizer (~> 0.0.11)
|
||||
http (~> 3.0)
|
||||
http-form_data (~> 2.0)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
memoizable (~> 0.4.0)
|
||||
multipart-post (~> 2.0)
|
||||
naught (~> 1.0)
|
||||
simple_oauth (~> 0.3.0)
|
||||
twitter-text (1.11.0)
|
||||
unf (~> 0.1.0)
|
||||
twitter_cldr (3.2.1)
|
||||
camertron-eprun
|
||||
cldr-plurals-runtime-rb (~> 1.0.0)
|
||||
@ -445,9 +419,6 @@ GEM
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (4.1.20)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.6)
|
||||
unicode-display_width (1.4.0)
|
||||
vcr (3.0.1)
|
||||
virtus (1.0.5)
|
||||
@ -477,11 +448,11 @@ DEPENDENCIES
|
||||
api-pagination
|
||||
apipie-rails
|
||||
awesome_print
|
||||
axlsx_rails
|
||||
bootstrap-sass (>= 3.4.1)
|
||||
byebug
|
||||
carrierwave
|
||||
caxlsx
|
||||
caxlsx_rails
|
||||
chroma
|
||||
compass-rails (= 2.0.4)
|
||||
coveralls
|
||||
@ -497,6 +468,7 @@ DEPENDENCIES
|
||||
forgery
|
||||
friendly_id (~> 5.1.0)
|
||||
has_secure_token
|
||||
icalendar
|
||||
jbuilder (~> 2.5)
|
||||
jbuilder_cache_multi
|
||||
jquery-rails
|
||||
@ -516,7 +488,7 @@ DEPENDENCIES
|
||||
prawn
|
||||
prawn-table
|
||||
protected_attributes
|
||||
puma (= 3.10.0)
|
||||
puma (= 3.12.4)
|
||||
pundit
|
||||
rack-protection (= 1.5.5)
|
||||
railroady
|
||||
@ -543,8 +515,6 @@ DEPENDENCIES
|
||||
sys-filesystem
|
||||
test_after_commit
|
||||
therubyracer (= 0.12.0)
|
||||
twitter
|
||||
twitter-text
|
||||
uglifier (>= 4.1.20)
|
||||
vcr
|
||||
web-console (~> 2.1.3)
|
||||
|
377
README.md
377
README.md
@ -1,42 +1,30 @@
|
||||
# FabManager
|
||||
# Fab-manager
|
||||
|
||||
FabManager is the Fab Lab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.
|
||||
Fab-manager is the Fab Lab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.
|
||||
|
||||
[![Coverage Status](https://coveralls.io/repos/github/sleede/fab-manager/badge.svg)](https://coveralls.io/github/sleede/fab-manager)
|
||||
[![Docker pulls](https://img.shields.io/docker/pulls/sleede/fab-manager.svg)](https://hub.docker.com/r/sleede/fab-manager/)
|
||||
[![Docker Build Status](https://img.shields.io/docker/build/sleede/fab-manager.svg)](https://hub.docker.com/r/sleede/fab-manager/builds)
|
||||
[![Crowdin](https://badges.crowdin.net/fab-manager/localized.svg)](https://crowdin.com/project/fab-manager)
|
||||
|
||||
##### Table of Contents
|
||||
1. [Software stack](#software-stack)
|
||||
2. [Contributing](#contributing)
|
||||
3. [Setup a production environment](#setup-a-production-environment)
|
||||
4. [Setup a development environment](#setup-a-development-environment)<br/>
|
||||
4.1. [General Guidelines](#general-guidelines)<br/>
|
||||
5. [PostgreSQL](#postgresql)<br/>
|
||||
5.1. [Install PostgreSQL 9.6](#setup-postgresql)
|
||||
6. [ElasticSearch](#elasticsearch)<br/>
|
||||
6.1. [Install ElasticSearch](#setup-elasticsearch)<br/>
|
||||
6.2. [Rebuild statistics](#rebuild-stats)<br/>
|
||||
6.3. [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/>
|
||||
7.1.2. [Back-end translations](#i18n-translation-back)<br/>
|
||||
7.2. [Configuration](#i18n-configuration)<br/>
|
||||
7.2.1. [Settings](#i18n-settings)<br/>
|
||||
7.2.2. [Applying changes](#i18n-apply)
|
||||
8. [Open Projects](#open-projects)
|
||||
9. [Plugins](#plugins)
|
||||
10. [Single Sign-On](#sso)
|
||||
11. [Known issues](#known-issues)
|
||||
12. [Related Documentation](#related-documentation)
|
||||
4. [Setup a development environment](#setup-a-development-environment)
|
||||
5. [Internationalization (i18n)](#i18n)
|
||||
6. [Open Projects](#open-projects)
|
||||
7. [Plugins](#plugins)
|
||||
8. [Single Sign-On](#sso)
|
||||
9. [Known issues](#known-issues)
|
||||
10. [Related Documentation](#related-documentation)
|
||||
|
||||
|
||||
|
||||
<a name="software-stack"></a>
|
||||
## Software stack
|
||||
|
||||
FabManager is a Ruby on Rails / AngularJS web application that runs on the following software:
|
||||
Fab-manager is a Ruby on Rails / AngularJS web application that runs on the following software:
|
||||
|
||||
- Ubuntu LTS 14.04+ / Debian 8+
|
||||
- Ruby 2.3
|
||||
@ -53,311 +41,40 @@ Contributions are welcome. Please read [the contribution guidelines](CONTRIBUTIN
|
||||
<a name="setup-a-production-environment"></a>
|
||||
## Setup a production environment
|
||||
|
||||
To run fab-manager as a production application, this is highly recommended to use [Docker-compose](https://docs.docker.com/compose/overview/).
|
||||
The procedure to follow is described in the [docker-compose readme](docker/README.md).
|
||||
To run Fab-manager as a production application, this is highly recommended to use [Docker-compose](https://docs.docker.com/compose/overview/).
|
||||
The procedure to follow is described in the [docker-compose readme](doc/docker-compose_readme.md).
|
||||
|
||||
<a name="setup-a-development-environment"></a>
|
||||
## Setup a development environment
|
||||
|
||||
In you intend to run fab-manager on your local machine to contribute to the project development, you can set it up with the following procedure.
|
||||
In you intend to run Fab-manager on your local machine to contribute to the project development, you can set it up by following the [development readme](doc/development_readme.md).
|
||||
This procedure relies on docker to set-up the dependencies.
|
||||
|
||||
This procedure is not easy to follow so if you don't need to write some code for Fab-manager, please prefer the [docker-compose installation method](docker/README.md).
|
||||
|
||||
Optionally, you can use a virtual development environment that relies on Vagrant and Virtual Box by following the [virtual machine instructions](doc/virtual-machine.md).
|
||||
|
||||
<a name="general-guidelines"></a>
|
||||
### General Guidelines
|
||||
|
||||
1. Install RVM, with the ruby version specified in the [.ruby-version file](.ruby-version).
|
||||
For more details about the process, please read the [official RVM documentation](http://rvm.io/rvm/install).
|
||||
If you're using ArchLinux, you may have to [read this](doc/archlinux_readme.md) before.
|
||||
|
||||
2. Install NVM, with the node.js version specified in the [.nvmrc file](.nvmrc).
|
||||
For instructions about installing NVM, please refer to [the NVM readme](https://github.com/creationix/nvm#installation).
|
||||
|
||||
3. Install Yarn, the front-end package manager.
|
||||
Depending on your system, the installation process may differ, please read the [official Yarn documentation](https://yarnpkg.com/en/docs/install#debian-stable).
|
||||
|
||||
4. Install docker.
|
||||
Your system may provide a pre-packaged version of docker in its repositories, but this version may be outdated.
|
||||
Please refer to [ubuntu](https://docs.docker.com/install/linux/docker-ce/ubuntu/), [debian](https://docs.docker.com/install/linux/docker-ce/debian/) or [MacOS](https://docs.docker.com/docker-for-mac/install/) documentation to setup a recent version of docker.
|
||||
|
||||
5. Add your current user to the docker group, to allow using docker without `sudo`.
|
||||
```bash
|
||||
# add the docker group if it doesn't already exist
|
||||
sudo groupadd docker
|
||||
# add the current user to the docker group
|
||||
sudo usermod -aG docker $(whoami)
|
||||
# restart to validate changes
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
6. Create a docker network for fab-manager.
|
||||
You may have to change the network address if it is already in use.
|
||||
```bash
|
||||
docker network create --subnet=172.18.0.0/16 fabmanager
|
||||
```
|
||||
|
||||
7. Retrieve the project from Git
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sleede/fab-manager.git
|
||||
```
|
||||
|
||||
8. Install the software dependencies.
|
||||
First install [PostgreSQL](#postgresql) and [ElasticSearch](#elasticsearch) as specified in their respective documentations.
|
||||
Then install the other dependencies:
|
||||
- For Ubuntu/Debian:
|
||||
|
||||
```bash
|
||||
# on Ubuntu 18.04 server, you may have to enable the "universe" repository
|
||||
sudo add-apt-repository universe
|
||||
# then, install the dependencies
|
||||
sudo apt-get install libpq-dev redis-server imagemagick
|
||||
```
|
||||
- For MacOS X:
|
||||
|
||||
```bash
|
||||
brew install redis imagemagick
|
||||
```
|
||||
|
||||
9. Init the RVM and NVM instances and check they were correctly configured
|
||||
|
||||
```bash
|
||||
cd fab-manager
|
||||
rvm current | grep -q `cat .ruby-version`@fab-manager && echo "ok"
|
||||
# Must print ok
|
||||
nvm use
|
||||
node --version | grep -q `cat .nvmrc` && echo "ok"
|
||||
# Must print ok
|
||||
```
|
||||
|
||||
10. Install bundler in the current RVM gemset
|
||||
|
||||
```bash
|
||||
gem install bundler --version=1.17.3
|
||||
```
|
||||
|
||||
11. Install the required ruby gems and javascript plugins
|
||||
|
||||
```bash
|
||||
bundle install
|
||||
yarn install
|
||||
```
|
||||
|
||||
12. Create the default configuration files **and configure them!** (see the [environment configuration documentation](doc/environment.md))
|
||||
|
||||
```bash
|
||||
cp config/database.yml.default config/database.yml
|
||||
cp config/application.yml.default config/application.yml
|
||||
vi config/application.yml
|
||||
# or use your favorite text editor instead of vi (nano, ne...)
|
||||
```
|
||||
|
||||
13. Build the databases.
|
||||
- **Warning**: **DO NOT** run `rake db:setup` instead of these commands, as this will not run some required raw SQL instructions.
|
||||
- **Please note**: Your password length must be between 8 and 128 characters, otherwise db:seed will be rejected. This is configured in [config/initializers/devise.rb](config/initializers/devise.rb)
|
||||
|
||||
```bash
|
||||
# for dev
|
||||
rake db:create
|
||||
rake db:migrate
|
||||
ADMIN_EMAIL='youradminemail' ADMIN_PASSWORD='youradminpassword' rake db:seed
|
||||
rake fablab:es:build_stats
|
||||
# for tests
|
||||
RAILS_ENV=test rake db:create
|
||||
RAILS_ENV=test rake db:migrate
|
||||
```
|
||||
|
||||
14. Create the pids folder used by Sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml`
|
||||
|
||||
```bash
|
||||
mkdir -p tmp/pids
|
||||
```
|
||||
|
||||
15. Start the development web server
|
||||
|
||||
```bash
|
||||
foreman s -p 3000
|
||||
```
|
||||
|
||||
16. You should now be able to access your local development FabManager instance by accessing `http://localhost:3000` in your web browser.
|
||||
|
||||
17. You can login as the default administrator using the credentials defined previously.
|
||||
|
||||
18. Email notifications will be caught by MailCatcher.
|
||||
To see the emails sent by the platform, open your web browser at `http://localhost:1080` to access the MailCatcher interface.
|
||||
|
||||
|
||||
<a name="postgresql"></a>
|
||||
## PostgreSQL
|
||||
|
||||
<a name="setup-postgresql"></a>
|
||||
### Install PostgreSQL 9.6
|
||||
|
||||
We will use docker to easily install the required version of PostgreSQL.
|
||||
|
||||
1. Create the docker binding folder
|
||||
```bash
|
||||
mkdir -p .docker/postgresql
|
||||
```
|
||||
|
||||
2. Start the PostgreSQL container.
|
||||
```bash
|
||||
docker run --restart=always -d --name fabmanager-postgres \
|
||||
-v $(pwd)/.docker/postgresql:/var/lib/postgresql/data \
|
||||
--network fabmanager --ip 172.18.0.2 \
|
||||
-p 5432:5432 \
|
||||
postgres:9.6
|
||||
```
|
||||
|
||||
3. Configure fab-manager to use it.
|
||||
On linux systems, PostgreSQL will be available at 172.18.0.2.
|
||||
On MacOS, you'll have to set the host to 127.0.0.1 (or localhost).
|
||||
See [environment.md](doc/environment.md) for more details.
|
||||
|
||||
4 . Finally, you may want to have a look at detailed informations about PostgreSQL usage in fab-manager.
|
||||
Some information about that is available in the [PostgreSQL Readme](doc/postgresql_readme.md).
|
||||
|
||||
<a name="elasticsearch"></a>
|
||||
## ElasticSearch
|
||||
|
||||
ElasticSearch is a powerful search engine based on Apache Lucene combined with a NoSQL database used as a cache to index data and quickly process complex requests on it.
|
||||
|
||||
In FabManager, it is used for the admin's statistics module and to perform searches in projects.
|
||||
|
||||
<a name="setup-elasticsearch"></a>
|
||||
### Install ElasticSearch
|
||||
|
||||
1. Create the docker binding folders
|
||||
```bash
|
||||
mkdir -p .docker/elasticsearch/config
|
||||
mkdir -p .docker/elasticsearch/plugins
|
||||
mkdir -p .docker/elasticsearch/backups
|
||||
```
|
||||
|
||||
2. Copy the default configuration files
|
||||
```bash
|
||||
cp docker/elasticsearch.yml .docker/elasticsearch/config
|
||||
cp docker/log4j2.properties .docker/elasticsearch/config
|
||||
```
|
||||
|
||||
3. Start the ElasticSearch container.
|
||||
```bash
|
||||
docker run --restart=always -d --name fabmanager-elastic \
|
||||
-v $(pwd)/.docker/elasticsearch/config:/usr/share/elasticsearch/config \
|
||||
-v $(pwd)/.docker/elasticsearch:/usr/share/elasticsearch/data \
|
||||
-v $(pwd)/.docker/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
|
||||
-v $(pwd)/.docker/elasticsearch/backups:/usr/share/elasticsearch/backups \
|
||||
--network fabmanager --ip 172.18.0.3 \
|
||||
-p 9200:9200 -p 9300:9300 \
|
||||
elasticsearch:5.6
|
||||
```
|
||||
|
||||
4. Configure fab-manager to use it.
|
||||
On linux systems, ElasticSearch will be available at 172.18.0.3.
|
||||
On MacOS, you'll have to set the host to 127.0.0.1 (or localhost).
|
||||
See [environment.md](doc/environment.md) for more details.
|
||||
|
||||
<a name="rebuild-stats"></a>
|
||||
### Rebuild statistics
|
||||
|
||||
Every nights, the statistics for the day that just ended are built automatically at 01:00 (AM) and stored in ElastricSearch.
|
||||
See [schedule.yml](config/schedule.yml) to modify this behavior.
|
||||
If the scheduled task wasn't executed for any reason (eg. you are in a dev environment and your computer was turned off at 1 AM), you can force the statistics data generation in ElasticSearch, running the following command.
|
||||
|
||||
```bash
|
||||
# Here for the 50 last days
|
||||
rake fablab:es:generate_stats[50]
|
||||
```
|
||||
|
||||
<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`.
|
||||
Optionally, you can use a virtual development environment that relies on Vagrant and Virtual Box by following the [virtual machine instructions](virtual-machine.md).
|
||||
|
||||
<a name="i18n"></a>
|
||||
## Internationalization (i18n)
|
||||
|
||||
The FabManager application can only run in a single language but this language can easily be changed.
|
||||
The Fab-manager application can only run in a single language but this language can easily be changed.
|
||||
|
||||
<a name="i18n-translation"></a>
|
||||
### Translation
|
||||
|
||||
Check the files located in `config/locales`:
|
||||
|
||||
- Front app translations (angular.js) are located in `config/locales/app.scope.XX.yml`.
|
||||
Where scope has one the following meaning :
|
||||
- admin: translations of the administrator views (manage and configure the FabLab).
|
||||
- logged: translations of the end-user's views accessible only to connected users.
|
||||
- public: translation of end-user's views publicly accessible to anyone.
|
||||
- shared: translations shared by many views (like forms or buttons).
|
||||
- Back app translations (Ruby on Rails) are located in `config/locales/XX.yml`.
|
||||
- Emails translations are located in `config/locales/mails.XX.yml`.
|
||||
- Messages related to the authentication system are located in `config/locales/devise.XX.yml`.
|
||||
|
||||
If you plan to translate the application to a new locale, please consider that the reference translation is French.
|
||||
Indeed, in some cases, the English texts/sentences can seems confuse or lack of context as they were originally translated from French.
|
||||
|
||||
To prevent syntax mistakes while translating locale files, we **STRONGLY advise** you to use a text editor which support syntax coloration for YML and Ruby.
|
||||
|
||||
<a name="i18n-translation-front"></a>
|
||||
#### Front-end translations
|
||||
|
||||
Front-end translations uses [angular-translate](http://angular-translate.github.io) with some interpolations interpreted by angular.js and other interpreted by [MessageFormat](https://github.com/SlexAxton/messageformat.js/).
|
||||
**These two kinds of interpolation use a near but different syntax witch SHOULD NOT be confused.**
|
||||
Please refer to the official [angular-translate documentation](http://angular-translate.github.io/docs/#/guide/14_pluralization) before translating.
|
||||
|
||||
<a name="i18n-translation-back"></a>
|
||||
#### Back-end translations
|
||||
|
||||
Back-end translations uses the [Ruby on Rails syntax](http://guides.rubyonrails.org/i18n.html) but some complex interpolations are interpreted by [MessageFormat](https://github.com/format-message/message-format-rb) and are marked as it in comments.
|
||||
**DO NOT confuse the syntaxes.**
|
||||
|
||||
In each cases, some inline comments are included in the localisation files.
|
||||
They can be recognized as they start with the sharp character (#).
|
||||
These comments are not required to be translated, they are intended to help the translator to have some context information about the sentence to translate.
|
||||
|
||||
You will also need to translate the invoice watermark, located in `app/pdfs/data/`.
|
||||
You'll find there the [GIMP source of the image](app/pdfs/data/watermark.xcf), which is using [Rubik Mono One](https://fonts.google.com/specimen/Rubik+Mono+One) as font.
|
||||
Use it to generate a similar localised PNG image which keep the default image size, as PDF are not responsive.
|
||||
|
||||
|
||||
<a name="i18n-configuration"></a>
|
||||
### Configuration
|
||||
|
||||
Locales configurations are made in `config/application.yml`.
|
||||
If you are in a development environment, your can keep the default values, otherwise, in production, values must be configured carefully.
|
||||
|
||||
<a name="i18n-settings"></a>
|
||||
#### Settings
|
||||
|
||||
Please refer to the [environment configuration documentation](doc/environment.md#internationalization-settings)
|
||||
|
||||
<a name="i18n-apply"></a>
|
||||
#### Applying changes
|
||||
|
||||
After modifying any values concerning the localisation, restart the application (ie. web server) to apply these changes in the i18n configuration.
|
||||
Please refer to the [translation readme](doc/translation_readme.md) for instructions about configuring the language or to contribute to the translation.
|
||||
|
||||
<a name="open-projects"></a>
|
||||
## Open Projects
|
||||
|
||||
**This configuration is optional.**
|
||||
|
||||
You can configure your fab-manager to synchronize every project with the [Open Projects platform](https://github.com/sleede/openlab-projects).
|
||||
It's very simple and straightforward and in return, your users will be able to search over projects from all fab-manager instances from within your platform.
|
||||
You can configure your Fab-manager to synchronize every project with the [Open Projects platform](https://github.com/sleede/openlab-projects).
|
||||
It's very simple and straightforward and in return, your users will be able to search over projects from all Fab-manager instances from within your platform.
|
||||
The deal is fair, you share your projects and as reward you benefits from projects of the whole community.
|
||||
|
||||
If you want to try it, you can visit [this fab-manager](https://fablab.lacasemate.fr/#!/projects) and see projects from different fab-managers.
|
||||
If you want to try it, you can visit [this Fab-manager](https://fablab.lacasemate.fr/#!/projects) and see projects from different Fab-managers.
|
||||
|
||||
To start using this awesome feature, there are a few steps:
|
||||
- send a mail to **contact@fab-manager.com** asking for your Open Projects client's credentials and giving them the name of your fab-manager, they will give you an `OPENLAB_APP_ID` and an `OPENLAB_APP_SECRET`
|
||||
- send a mail to **contact@fab-manager.com** asking for your Open Projects client's credentials and giving them the name of your Fab-manager, they will give you an `OPENLAB_APP_ID` and an `OPENLAB_APP_SECRET`
|
||||
- fill in the value of the keys in your environment file
|
||||
- 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`
|
||||
- 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.**
|
||||
|
||||
@ -390,51 +107,7 @@ Developers may find information on how to implement their own authentication pro
|
||||
<a name="known-issues"></a>
|
||||
## Known issues
|
||||
|
||||
- When browsing a machine page, you may encounter an "InterceptError" in the console and the loading bar will stop loading before reaching its ending.
|
||||
This may happen if the machine was created through a seed file without any image.
|
||||
To solve this, simply add an image to the machine's profile and refresh the web page.
|
||||
|
||||
- When starting the Ruby on Rails server (eg. `foreman s`) you may receive the following error:
|
||||
|
||||
worker.1 | invalid url: redis::6379
|
||||
web.1 | Exiting
|
||||
worker.1 | ...lib/redis/client.rb...:in `_parse_options'
|
||||
|
||||
This may happen when the `application.yml` file is missing.
|
||||
To solve this issue copy `config/application.yml.default` to `config/application.yml`.
|
||||
This is required before the first start.
|
||||
|
||||
- Due to a stripe limitation, you won't be able to create plans longer than one year.
|
||||
|
||||
- When running the tests suite with `rake test`, all tests may fail with errors similar to the following:
|
||||
|
||||
Error:
|
||||
...
|
||||
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "..." violates foreign key constraint "fk_rails_..."
|
||||
DETAIL: Key (group_id)=(1) is not present in table "...".
|
||||
: ...
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:11:in `block in transaction'
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:5:in `transaction'
|
||||
|
||||
This is due to an ActiveRecord behavior witch disable referential integrity in PostgreSQL to load the fixtures.
|
||||
PostgreSQL will prevent any users to disable referential integrity on the fly if they doesn't have the `SUPERUSER` role.
|
||||
To fix that, logon as the `postgres` user and run the PostgreSQL shell (see [the dedicated section](#run-postgresql-cli) for instructions).
|
||||
Then, run the following command (replace `sleede` with your test database user, as specified in your database.yml):
|
||||
|
||||
ALTER ROLE sleede WITH SUPERUSER;
|
||||
|
||||
DO NOT do this in a production environment, unless you know what you're doing: this could lead to a serious security issue.
|
||||
|
||||
- With Ubuntu 16.04, ElasticSearch may refuse to start even after having configured the service with systemd.
|
||||
To solve this issue, you may have to set `START_DAEMON` to `true` in `/etc/default/elasticsearch`.
|
||||
Then reload ElasticSearch with:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart elasticsearch.service
|
||||
```
|
||||
- In some cases, the invoices won't be generated. This can be due to the image included in the invoice header not being supported.
|
||||
To fix this issue, change the image in the administrator interface (manage the invoices / invoices settings).
|
||||
See [this thread](https://forum.fab-manager.com/t/resolu-erreur-generation-facture/428) for more info.
|
||||
Before reporting an issue, please check if your issue is not listed in the [know issues](doc/known-issues.md) with its solution.
|
||||
|
||||
<a name="related-documentation"></a>
|
||||
## Related Documentation
|
||||
|
@ -20,9 +20,10 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
|
||||
'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'DeviseModal', 'angular-growl', 'xeditable',
|
||||
'checklist-model', 'unsavedChanges', 'angular-loading-bar', 'ngTouch', 'angular-google-analytics',
|
||||
'angularUtils.directives.dirDisqus', 'summernote', 'elasticsearch', 'angular-medium-editor', 'naif.base64',
|
||||
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock', 'vcRecaptcha'])
|
||||
.config(['$httpProvider', 'AuthProvider', 'growlProvider', 'unsavedWarningsConfigProvider', 'AnalyticsProvider', 'uibDatepickerPopupConfig', '$provide', '$translateProvider',
|
||||
function ($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider) {
|
||||
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock', 'vcRecaptcha', 'ui.codemirror',
|
||||
'bm.uiTour'])
|
||||
.config(['$httpProvider', 'AuthProvider', 'growlProvider', 'unsavedWarningsConfigProvider', 'AnalyticsProvider', 'uibDatepickerPopupConfig', '$provide', '$translateProvider', 'TourConfigProvider',
|
||||
function ($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider, TourConfigProvider) {
|
||||
// Google analytics
|
||||
// first we check the user acceptance
|
||||
const cookiesConsent = document.cookie.replace(/(?:(?:^|.*;\s*)fab-manager-cookies-consent\s*=\s*([^;]*).*$)|^.*$/, '$1');
|
||||
@ -59,10 +60,12 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
|
||||
$translateProvider.useLoaderCache(true);
|
||||
// Secure i18n module against XSS attacks by escaping the output
|
||||
$translateProvider.useSanitizeValueStrategy('escapeParameters');
|
||||
// Enable the MessageFormat interpolation (used for pluralization)
|
||||
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
|
||||
// Use the MessageFormat interpolation by default (used for pluralization)
|
||||
$translateProvider.useMessageFormatInterpolation();
|
||||
// Set the langage of the instance (from ruby configuration)
|
||||
$translateProvider.preferredLanguage(Fablab.locale);
|
||||
// End the tour when the user clicks the forward or back buttons of the browser
|
||||
TourConfigProvider.enableNavigationInterceptors();
|
||||
}]).run(['$rootScope', '$log', 'AuthService', 'Auth', 'amMoment', '$state', 'editableOptions', 'Analytics',
|
||||
function ($rootScope, $log, AuthService, Auth, amMoment, $state, editableOptions, Analytics) {
|
||||
// Angular-moment (date-time manipulations library)
|
||||
@ -86,6 +89,16 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
|
||||
$rootScope.fablabWithoutOnlinePayment = Fablab.withoutOnlinePayment;
|
||||
// Global config: if true, no invoices will be generated
|
||||
$rootScope.fablabWithoutInvoices = Fablab.withoutInvoices;
|
||||
// Global config: if true, the phone number is required to create an account
|
||||
$rootScope.phoneRequired = Fablab.phoneRequired;
|
||||
// Global config: if true, the events are shown in the admin calendar
|
||||
$rootScope.eventsInCalendar = Fablab.eventsInCalendar;
|
||||
// Global config: machine/space slot duration
|
||||
$rootScope.slotDuration = Fablab.slotDuration;
|
||||
// Global config: if true, user must confirm his email to sign in
|
||||
$rootScope.userConfirmationNeededToSignIn = Fablab.userConfirmationNeededToSignIn;
|
||||
// Global config: if true, wallet will be disable
|
||||
$rootScope.fablabWithoutWallet = Fablab.fablabWithoutWallet;
|
||||
|
||||
// Global function to allow the user to navigate to the previous screen (ie. $state).
|
||||
// If no previous $state were recorded, navigate to the home page
|
||||
|
@ -21,7 +21,7 @@
|
||||
//= require angular-cookies
|
||||
//= require angular-touch
|
||||
//= require @uirouter/angularjs/release/angular-ui-router
|
||||
//= require angular-ui-bootstrap/ui-bootstrap-tpls
|
||||
//= require angular-ui-bootstrap/dist/ui-bootstrap-tpls
|
||||
//= require ui-select/dist/select
|
||||
//= require moment/moment
|
||||
//= require moment-timezone/builds/moment-timezone-with-data-2012-2022
|
||||
@ -46,6 +46,7 @@
|
||||
//= require elasticsearch-browser/elasticsearch.angular
|
||||
//= require d3/d3
|
||||
//= require nvd3/build/nv.d3.js
|
||||
//= require twitter-fetcher
|
||||
//= require app
|
||||
//= require router
|
||||
//= require medium-editor/dist/js/medium-editor
|
||||
@ -55,6 +56,7 @@
|
||||
//= require angular-base64-upload/dist/angular-base64-upload.min
|
||||
//= require summernote/dist/summernote
|
||||
//= require angular-summernote/dist/angular-summernote
|
||||
//= require summernote-ext-nugget
|
||||
//= require jquery-minicolors/jquery.minicolors.js
|
||||
//= require angular-minicolors/angular-minicolors.js
|
||||
//= require angular-translate/dist/angular-translate
|
||||
@ -65,6 +67,16 @@
|
||||
//= require angular-aside/dist/js/angular-aside
|
||||
//= require ng-caps-lock/ng-caps-lock
|
||||
//= require angular-recaptcha
|
||||
//= require codemirror/lib/codemirror
|
||||
//= require codemirror/addon/edit/matchbrackets
|
||||
//= require codemirror/mode/css/css
|
||||
//= require codemirror/mode/sass/sass
|
||||
//= require angular-ui-codemirror/src/ui-codemirror
|
||||
//= require angular-hotkeys/build/hotkeys
|
||||
//= require hone/dist/hone
|
||||
//= require tether/dist/js/tether
|
||||
//= require angular-bind-html-compile/angular-bind-html-compile
|
||||
//= require angular-ui-tour/dist/angular-ui-tour
|
||||
//= require_tree ./controllers
|
||||
//= require_tree ./services
|
||||
//= require_tree ./directives
|
||||
|
@ -17,21 +17,21 @@ Application.Controllers.controller('AbusesController', ['$scope', '$state', 'Abu
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('manage_abuses.confirmation_required'),
|
||||
msg: _t('manage_abuses.report_will_be_destroyed')
|
||||
title: _t('app.admin.manage_abuses.confirmation_required'),
|
||||
msg: _t('app.admin.manage_abuses.report_will_be_destroyed')
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () { // cancel confirmed
|
||||
Abuse.remove({ id: abuseId }, function () { // successfully canceled
|
||||
growl.success(_t('manage_abuses.report_removed'));
|
||||
growl.success(_t('app.admin.manage_abuses.report_removed'));
|
||||
Abuse.query({}, function (abuses) {
|
||||
$scope.abuses = abuses.abuses.filter(a => a.signaled_type === 'Project');
|
||||
});
|
||||
}
|
||||
, function () { // error while canceling
|
||||
growl.error(_t('manage_abuses.failed_to_remove'));
|
||||
growl.error(_t('app.admin.manage_abuses.failed_to_remove'));
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -54,14 +54,16 @@ const check_oauth2_id_is_mapped = function (mappings) {
|
||||
* - $scope.authMethods
|
||||
* - $scope.mappingFields
|
||||
* - $scope.cancel()
|
||||
* - $scope.methodName()
|
||||
* - $scope.defineDataMapping(mapping)
|
||||
*
|
||||
* Requires :
|
||||
* - mappingFieldsPromise: retrieved by AuthProvider.mapping_fields()
|
||||
* - $state (Ui-Router) [ 'app.admin.members' ]
|
||||
* - _t : translation method
|
||||
*/
|
||||
class AuthenticationController {
|
||||
constructor ($scope, $state, $uibModal, mappingFieldsPromise) {
|
||||
constructor ($scope, $state, $uibModal, _t, mappingFieldsPromise) {
|
||||
// list of supported authentication methods
|
||||
$scope.authMethods = METHODS;
|
||||
|
||||
@ -73,6 +75,13 @@ class AuthenticationController {
|
||||
*/
|
||||
$scope.cancel = function () { $state.go('app.admin.members'); };
|
||||
|
||||
/**
|
||||
* Return a localized string for the provided method
|
||||
*/
|
||||
$scope.methodName = function(method) {
|
||||
return _t('app.shared.authentication.' + METHODS[method]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a modal allowing to specify the data mapping for the given field
|
||||
*/
|
||||
@ -137,10 +146,10 @@ class AuthenticationController {
|
||||
$scope.ok = function () { $uibModalInstance.close($scope.transformation.rules); };
|
||||
|
||||
// do not save the modifications
|
||||
return $scope.cancel = function () { $uibModalInstance.dismiss(); };
|
||||
}
|
||||
] })
|
||||
.result['finally'](null).then(function (transfo_rules) { mapping.transformation = transfo_rules; });
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss(); };
|
||||
}]
|
||||
})
|
||||
.result['finally'](null).then(function (transfo_rules) { mapping.transformation = transfo_rules; });
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -163,9 +172,9 @@ Application.Controllers.controller('AuthentificationController', ['$scope', '$st
|
||||
$scope.getType = function (type) {
|
||||
const text = METHODS[type];
|
||||
if (typeof text !== 'undefined') {
|
||||
return _t(text);
|
||||
return _t(`app.admin.members.authentication_form.${text}`);
|
||||
} else {
|
||||
return _t('unknown') + type;
|
||||
return _t('app.admin.members.authentication_form.unknown') + type;
|
||||
}
|
||||
};
|
||||
|
||||
@ -176,10 +185,10 @@ Application.Controllers.controller('AuthentificationController', ['$scope', '$st
|
||||
*/
|
||||
$scope.getState = function (status) {
|
||||
switch (status) {
|
||||
case 'active': return _t('active');
|
||||
case 'pending': return _t('pending');
|
||||
case 'previous': return _t('previous_provider');
|
||||
default: return _t('unknown') + status;
|
||||
case 'active': return _t('app.admin.members.authentication_form.active');
|
||||
case 'pending': return _t('app.admin.members.authentication_form.pending');
|
||||
case 'previous': return _t('app.admin.members.authentication_form.previous_provider');
|
||||
default: return _t('app.admin.members.authentication_form.unknown') + status;
|
||||
}
|
||||
};
|
||||
|
||||
@ -194,8 +203,8 @@ Application.Controllers.controller('AuthentificationController', ['$scope', '$st
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_the_TYPE_authentication_provider_NAME', { TYPE: $scope.getType(provider.providable_type), NAME: provider.name })
|
||||
title: _t('app.admin.members.authentication_form.confirmation_required'),
|
||||
msg: _t('app.admin.members.authentication_form.do_you_really_want_to_delete_the_TYPE_authentication_provider_NAME', { TYPE: $scope.getType(provider.providable_type), NAME: provider.name })
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -206,9 +215,9 @@ Application.Controllers.controller('AuthentificationController', ['$scope', '$st
|
||||
{ id: provider.id },
|
||||
function () {
|
||||
providers.splice(findIdxById(providers, provider.id), 1);
|
||||
growl.success(_t('authentication_provider_successfully_deleted'));
|
||||
growl.success(_t('app.admin.members.authentication_form.authentication_provider_successfully_deleted'));
|
||||
},
|
||||
function () { growl.error(_t('an_error_occurred_unable_to_delete_the_specified_provider')); }
|
||||
function () { growl.error(_t('app.admin.members.authentication_form.an_error_occurred_unable_to_delete_the_specified_provider')); }
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -254,19 +263,19 @@ Application.Controllers.controller('NewAuthenticationController', ['$scope', '$s
|
||||
// prevent from adding mode than 1
|
||||
for (provider of Array.from(authProvidersPromise)) {
|
||||
if (provider.providable_type === 'DatabaseProvider') {
|
||||
growl.error(_t('a_local_database_provider_already_exists_unable_to_create_another'));
|
||||
growl.error(_t('app.admin.authentication_new.a_local_database_provider_already_exists_unable_to_create_another'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return AuthProvider.save({ auth_provider: $scope.provider }, function (provider) {
|
||||
growl.success(_t('local_provider_successfully_saved'));
|
||||
growl.success(_t('app.admin.authentication_new.local_provider_successfully_saved'));
|
||||
return $state.go('app.admin.members');
|
||||
});
|
||||
// === OAuth2Provider ===
|
||||
} else if ($scope.provider.providable_type === 'OAuth2Provider') {
|
||||
// check the ID mapping
|
||||
if (!check_oauth2_id_is_mapped($scope.provider.providable_attributes.o_auth2_mappings_attributes)) {
|
||||
growl.error(_t('it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider'));
|
||||
growl.error(_t('app.admin.authentication_new.it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider'));
|
||||
return false;
|
||||
}
|
||||
// discourage the use of unsecure SSO
|
||||
@ -277,24 +286,24 @@ Application.Controllers.controller('NewAuthenticationController', ['$scope', '$s
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('security_issue_detected'),
|
||||
msg: _t('beware_the_oauth2_authenticatoin_provider_you_are_about_to_add_isnt_using_HTTPS') +
|
||||
_t('this_is_a_serious_security_issue_on_internet_and_should_never_be_used_except_for_testing_purposes') +
|
||||
_t('do_you_really_want_to_continue')
|
||||
title: _t('app.admin.authentication_new.security_issue_detected'),
|
||||
msg: _t('app.admin.authentication_new.beware_the_oauth2_authenticatoin_provider_you_are_about_to_add_isnt_using_HTTPS') +
|
||||
_t('app.admin.authentication_new.this_is_a_serious_security_issue_on_internet_and_should_never_be_used_except_for_testing_purposes') +
|
||||
_t('app.admin.authentication_new.do_you_really_want_to_continue')
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () { // unsecured http confirmed
|
||||
AuthProvider.save({ auth_provider: $scope.provider }, function (provider) {
|
||||
growl.success(_t('unsecured_oauth2_provider_successfully_added'));
|
||||
growl.success(_t('app.admin.authentication_new.unsecured_oauth2_provider_successfully_added'));
|
||||
return $state.go('app.admin.members');
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
AuthProvider.save({ auth_provider: $scope.provider }, function (provider) {
|
||||
growl.success(_t('oauth2_provider_successfully_added'));
|
||||
growl.success(_t('app.admin.authentication_new.oauth2_provider_successfully_added'));
|
||||
return $state.go('app.admin.members');
|
||||
});
|
||||
}
|
||||
@ -302,7 +311,7 @@ Application.Controllers.controller('NewAuthenticationController', ['$scope', '$s
|
||||
};
|
||||
|
||||
// Using the AuthenticationController
|
||||
return new AuthenticationController($scope, $state, $uibModal, mappingFieldsPromise);
|
||||
return new AuthenticationController($scope, $state, $uibModal, _t, mappingFieldsPromise);
|
||||
}
|
||||
]);
|
||||
|
||||
@ -322,21 +331,21 @@ Application.Controllers.controller('EditAuthenticationController', ['$scope', '$
|
||||
$scope.updateProvider = function () {
|
||||
// check the ID mapping
|
||||
if (!check_oauth2_id_is_mapped($scope.provider.providable_attributes.o_auth2_mappings_attributes)) {
|
||||
growl.error(_t('it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider'));
|
||||
growl.error(_t('app.admin.authentication_edit.it_is_required_to_set_the_matching_between_User.uid_and_the_API_to_add_this_provider'));
|
||||
return false;
|
||||
}
|
||||
return AuthProvider.update(
|
||||
{ id: $scope.provider.id },
|
||||
{ auth_provider: $scope.provider },
|
||||
function (provider) {
|
||||
growl.success(_t('provider_successfully_updated'));
|
||||
growl.success(_t('app.admin.authentication_edit.provider_successfully_updated'));
|
||||
$state.go('app.admin.members');
|
||||
},
|
||||
function () { growl.error(_t('an_error_occurred_unable_to_update_the_provider')); }
|
||||
function () { growl.error(_t('app.admin.authentication_edit.an_error_occurred_unable_to_update_the_provider')); }
|
||||
);
|
||||
};
|
||||
|
||||
// Using the AuthenticationController
|
||||
return new AuthenticationController($scope, $state, $uibModal, mappingFieldsPromise);
|
||||
return new AuthenticationController($scope, $state, $uibModal, _t, mappingFieldsPromise);
|
||||
}
|
||||
]);
|
||||
|
@ -18,19 +18,18 @@
|
||||
* Controller used in the calendar management page
|
||||
*/
|
||||
|
||||
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig',
|
||||
function ($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
|
||||
function ($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// The calendar is divided in slots of 30 minutes
|
||||
let loadingCb;
|
||||
const BASE_SLOT = '00:30:00';
|
||||
|
||||
// The bookings can be positioned every half hours
|
||||
const BOOKING_SNAP = '00:30:00';
|
||||
|
||||
// We do not allow the creation of slots that are not a multiple of 60 minutes
|
||||
const SLOT_MULTIPLE = 60;
|
||||
const SLOT_MULTIPLE = Fablab.slotDuration;
|
||||
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
@ -40,6 +39,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
// currently selected availability
|
||||
$scope.availability = null;
|
||||
|
||||
// corresponding fullCalendar item in the DOM
|
||||
$scope.availabilityDom = null;
|
||||
|
||||
// bind the availabilities slots with full-Calendar events
|
||||
$scope.eventSources = [];
|
||||
$scope.eventSources.push({
|
||||
@ -62,7 +64,10 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
return calendarEventClickCb(event, jsEvent, view);
|
||||
},
|
||||
eventRender (event, element, view) {
|
||||
return eventRenderCb(event, element);
|
||||
return eventRenderCb(event, element, view);
|
||||
},
|
||||
viewRender(view, element) {
|
||||
return viewRenderCb(view, element);
|
||||
},
|
||||
loading (isLoading, view) {
|
||||
return loadingCb(isLoading, view);
|
||||
@ -80,10 +85,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('admin_calendar.confirmation_required'),
|
||||
msg: _t('admin_calendar.do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION'
|
||||
, { GENDER: getGender($scope.currentUser), USER: slot.user.name, DATE: moment(slot.start_at).format('L'), TIME: moment(slot.start_at).format('LT'), RESERVATION: slot.reservable.name }
|
||||
, 'messageformat')
|
||||
title: _t('app.admin.calendar.confirmation_required'),
|
||||
msg: _t('app.admin.calendar.do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION'
|
||||
, { GENDER: getGender($scope.currentUser), USER: slot.user.name, DATE: moment(slot.start_at).format('L'), TIME: moment(slot.start_at).format('LT'), RESERVATION: slot.reservable.name })
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -101,10 +105,10 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
}
|
||||
}
|
||||
// notify the admin
|
||||
return growl.success(_t('admin_calendar.reservation_was_successfully_cancelled'));
|
||||
return growl.success(_t('app.admin.calendar.reservation_was_successfully_cancelled'));
|
||||
},
|
||||
function (data, status) { // failed
|
||||
growl.error(_t('admin_calendar.reservation_cancellation_failed'));
|
||||
growl.error(_t('app.admin.calendar.reservation_cancellation_failed'));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -118,17 +122,17 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
*/
|
||||
$scope.removeMachine = function (machine) {
|
||||
if ($scope.availability.machine_ids.length === 1) {
|
||||
return growl.error(_t('admin_calendar.unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'));
|
||||
return growl.error(_t('app.admin.calendar.unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'));
|
||||
} else {
|
||||
// open a confirmation dialog
|
||||
return dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('admin_calendar.confirmation_required'),
|
||||
msg: _t('admin_calendar.do_you_really_want_to_remove_MACHINE_from_this_slot', { GENDER: getGender($scope.currentUser), MACHINE: machine.name }, 'messageformat') + ' ' +
|
||||
_t('admin_calendar.this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' +
|
||||
_t('admin_calendar.beware_this_cannot_be_reverted')
|
||||
title: _t('app.admin.calendar.confirmation_required'),
|
||||
msg: _t('app.admin.calendar.do_you_really_want_to_remove_MACHINE_from_this_slot', { GENDER: getGender($scope.currentUser), MACHINE: machine.name }) + ' ' +
|
||||
_t('app.admin.calendar.this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + '<br><strong>' +
|
||||
_t('app.admin.calendar.beware_this_cannot_be_reverted') + '</strong>'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -150,16 +154,52 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
$scope.availability.title = data.title;
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('rerenderEvents');
|
||||
// notify the admin
|
||||
return growl.success(_t('admin_calendar.the_machine_was_successfully_removed_from_the_slot'));
|
||||
return growl.success(_t('app.admin.calendar.the_machine_was_successfully_removed_from_the_slot'));
|
||||
}
|
||||
, function (data, status) { // failed
|
||||
growl.error(_t('admin_calendar.deletion_failed'));
|
||||
growl.error(_t('app.admin.calendar.deletion_failed'));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a confirmation modal to remove a plan for the currently selected availability,
|
||||
* @param plan {Object} must contain the machine ID and name
|
||||
*/
|
||||
$scope.removePlan = function (plan) {
|
||||
// open a confirmation dialog
|
||||
return dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('app.admin.calendar.confirmation_required'),
|
||||
msg: _t('app.admin.calendar.do_you_really_want_to_remove_PLAN_from_this_slot', { GENDER: getGender($scope.currentUser), PLAN: plan.name })
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
// the admin has confirmed, remove the plan
|
||||
const plans = _.drop($scope.availability.plan_ids, plan.id);
|
||||
|
||||
return Availability.update({ id: $scope.availability.id }, { availability: { plans_attributes: [{ id: plan.id, _destroy: true }] } }
|
||||
, function (data, status) { // success
|
||||
// update the plan_ids attribute
|
||||
$scope.availability.plan_ids = data.plan_ids;
|
||||
$scope.availability.plans = availabilityPlans();
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('rerenderEvents');
|
||||
// notify the admin
|
||||
return growl.success(_t('app.admin.calendar.the_plan_was_successfully_removed_from_the_slot'));
|
||||
}
|
||||
, function (data, status) { // failed
|
||||
growl.error(_t('app.admin.calendar.deletion_failed'));
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to alert the admin that the export request was acknowledged and is
|
||||
* processing right now.
|
||||
@ -167,7 +207,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
$scope.alertExport = function (type) {
|
||||
Export.status({ category: 'availabilities', type }).then(function (res) {
|
||||
if (!res.data.exists) {
|
||||
return growl.success(_t('admin_calendar.export_is_running_you_ll_be_notified_when_its_ready'));
|
||||
return growl.success(_t('app.admin.calendar.export_is_running_you_ll_be_notified_when_its_ready'));
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -195,8 +235,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('admin_calendar.confirmation_required'),
|
||||
msg: locked ? _t('admin_calendar.do_you_really_want_to_allow_reservations') : _t('admin_calendar.do_you_really_want_to_block_this_slot')
|
||||
title: _t('app.admin.calendar.confirmation_required'),
|
||||
msg: locked ? _t('app.admin.calendar.do_you_really_want_to_allow_reservations') : _t('app.admin.calendar.do_you_really_want_to_block_this_slot')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -208,18 +248,18 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
{ lock: !locked },
|
||||
function (data) { // success
|
||||
$scope.availability = data;
|
||||
growl.success(locked ? _t('admin_calendar.unlocking_success') : _t('admin_calendar.locking_success'));
|
||||
growl.success(locked ? _t('app.admin.calendar.unlocking_success') : _t('app.admin.calendar.locking_success'));
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents');
|
||||
},
|
||||
function (error) { // failed
|
||||
growl.error(locked ? _t('admin_calendar.unlocking_failed') : _t('admin_calendar.locking_failed'));
|
||||
growl.error(locked ? _t('app.admin.calendar.unlocking_failed') : _t('app.admin.calendar.locking_failed'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return growl.error(_t('admin_calendar.unlockable_because_reservations'));
|
||||
return growl.error(_t('app.admin.calendar.unlockable_because_reservations'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -228,93 +268,195 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
*/
|
||||
$scope.removeSlot = function () {
|
||||
// open a confirmation dialog
|
||||
dialogs.confirm(
|
||||
{
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('admin_calendar.confirmation_required'),
|
||||
msg: _t('admin_calendar.do_you_really_want_to_delete_this_slot')
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
// the admin has confirmed, delete the slot
|
||||
Availability.delete(
|
||||
{ id: $scope.availability.id },
|
||||
function () {
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents', $scope.availability.id);
|
||||
|
||||
growl.success(_t('admin_calendar.the_slot_START-END_has_been_successfully_deleted', { START: moment(event.start).format('LL LT'), END: moment(event.end).format('LT') }));
|
||||
$scope.availability = null;
|
||||
},
|
||||
function () {
|
||||
growl.error(_t('admin_calendar.unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member', { START: moment(event.start).format('LL LT'), END: moment(event.end).format('LT') }));
|
||||
});
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "admin/calendar/deleteRecurrent.html" %>',
|
||||
size: 'md',
|
||||
controller: 'DeleteRecurrentAvailabilityController',
|
||||
resolve: {
|
||||
availabilityPromise: ['Availability', function (Availability) { return Availability.get({ id: $scope.availability.id }).$promise; }]
|
||||
}
|
||||
);
|
||||
});
|
||||
// once the dialog was closed, do things depending on the result
|
||||
modalInstance.result.then(function (res) {
|
||||
if (res.status == 'success') {
|
||||
$scope.availability = null;
|
||||
}
|
||||
for (const availability of res.availabilities) {
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents', availability);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/calendar page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupCalendarTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('calendar');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.calendar.welcome.title'),
|
||||
content: _t('app.admin.tour.calendar.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-calendar .fc-view-container',
|
||||
stepId: 'agenda',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.calendar.agenda.title'),
|
||||
content: _t('app.admin.tour.calendar.agenda.content'),
|
||||
placement: 'right',
|
||||
popupClass: 'width-350'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-calendar .export-xls-button',
|
||||
stepId: 'export',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.calendar.export.title'),
|
||||
content: _t('app.admin.tour.calendar.export.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .import-ics-button',
|
||||
stepId: 'import',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.calendar.import.title'),
|
||||
content: _t('app.admin.tour.calendar.import.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 4,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('calendar') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'calendar' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('calendar') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an enumerable meaninful string for the gender of the provider user
|
||||
* @param user {Object} Database user record
|
||||
* @return {string} 'male' or 'female'
|
||||
*/
|
||||
var getGender = function (user) {
|
||||
const getGender = function (user) {
|
||||
if (user.statistic_profile) {
|
||||
if (user.statistic_profile.gender === 'true') { return 'male'; } else { return 'female'; }
|
||||
} else { return 'other'; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a list of plans classified by group
|
||||
*
|
||||
* @returns {array}
|
||||
*/
|
||||
var availabilityPlans = function() {
|
||||
const plansClassifiedByGroup = [];
|
||||
const _plans = _.filter(plansPromise, function (p) { return _.include($scope.availability.plan_ids, p.id) });
|
||||
for (let group of Array.from(groupsPromise)) {
|
||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||
for (let plan of Array.from(_plans)) {
|
||||
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
||||
}
|
||||
if (groupObj.plans.length > 0) {
|
||||
plansClassifiedByGroup.push(groupObj);
|
||||
}
|
||||
}
|
||||
return plansClassifiedByGroup;
|
||||
};
|
||||
|
||||
// Triggered when the admin drag on the agenda to create a new reservable slot.
|
||||
// @see http://fullcalendar.io/docs/selection/select_callback/
|
||||
//
|
||||
var calendarSelectCb = function (start, end, jsEvent, view) {
|
||||
const calendarSelectCb = function (start, end, jsEvent, view) {
|
||||
start = moment.tz(start.toISOString(), Fablab.timezone);
|
||||
end = moment.tz(end.toISOString(), Fablab.timezone);
|
||||
// first we check that the selected slot is an N-hours multiple (ie. not decimal)
|
||||
if (Number.isInteger(parseInt((end.valueOf() - start.valueOf()) / (SLOT_MULTIPLE * 1000), 10) / SLOT_MULTIPLE)) {
|
||||
const today = new Date();
|
||||
if (parseInt((start.valueOf() - today) / (60 * 1000), 10) >= 0) {
|
||||
// then we open a modal window to let the admin specify the slot type
|
||||
const modalInstance = $uibModal.open({
|
||||
templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>',
|
||||
controller: 'CreateEventModalController',
|
||||
resolve: {
|
||||
start () { return start; },
|
||||
end () { return end; },
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
|
||||
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }]
|
||||
} });
|
||||
// when the modal is closed, we send the slot to the server for saving
|
||||
modalInstance.result.then(
|
||||
function (availability) {
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar(
|
||||
'renderEvent',
|
||||
{
|
||||
id: availability.id,
|
||||
title: availability.title,
|
||||
start: availability.start_at,
|
||||
end: availability.end_at,
|
||||
textColor: 'black',
|
||||
backgroundColor: availability.backgroundColor,
|
||||
borderColor: availability.borderColor,
|
||||
tag_ids: availability.tag_ids,
|
||||
tags: availability.tags,
|
||||
machine_ids: availability.machine_ids
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
function () { uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); }
|
||||
);
|
||||
}
|
||||
|
||||
// check if slot is not in the past
|
||||
const today = new Date();
|
||||
if (Math.trunc((start.valueOf() - today) / (60 * 1000)) < 0) {
|
||||
growl.warning(_t('app.admin.calendar.event_in_the_past'));
|
||||
return uiCalendarConfig.calendars.calendar.fullCalendar('unselect');
|
||||
}
|
||||
|
||||
// check that the selected slot is an multiple of SLOT_MULTIPLE (ie. not decimal)
|
||||
const slots = Math.trunc((end.valueOf() - start.valueOf()) / (60 * 1000)) / SLOT_MULTIPLE;
|
||||
if (!Number.isInteger(slots)) {
|
||||
// otherwise, round it to upper decimal
|
||||
const upper = Math.ceil(slots) * SLOT_MULTIPLE;
|
||||
end = moment(start).add(upper, 'minutes');
|
||||
}
|
||||
|
||||
// then we open a modal window to let the admin specify the slot type
|
||||
const modalInstance = $uibModal.open({
|
||||
templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>',
|
||||
controller: 'CreateEventModalController',
|
||||
resolve: {
|
||||
start () { return start; },
|
||||
end () { return end; },
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
|
||||
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
|
||||
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
|
||||
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
|
||||
} });
|
||||
// when the modal is closed, we send the slot to the server for saving
|
||||
modalInstance.result.then(
|
||||
function (availability) {
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar(
|
||||
'renderEvent',
|
||||
{
|
||||
id: availability.id,
|
||||
title: availability.title,
|
||||
start: availability.start_at,
|
||||
end: availability.end_at,
|
||||
textColor: 'black',
|
||||
backgroundColor: availability.backgroundColor,
|
||||
borderColor: availability.borderColor,
|
||||
tag_ids: availability.tag_ids,
|
||||
tags: availability.tags,
|
||||
machine_ids: availability.machine_ids,
|
||||
plan_ids: availability.plan_ids
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
function () { uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); }
|
||||
);
|
||||
|
||||
return uiCalendarConfig.calendars.calendar.fullCalendar('unselect');
|
||||
};
|
||||
|
||||
@ -322,8 +464,15 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
* Triggered when the admin clicks on a availability slot in the agenda.
|
||||
* @see http://fullcalendar.io/docs/mouse/eventClick/
|
||||
*/
|
||||
var calendarEventClickCb = function (event, jsEvent, view) {
|
||||
const calendarEventClickCb = function (event, jsEvent, view) {
|
||||
$scope.availability = event;
|
||||
$scope.availability.plans = availabilityPlans();
|
||||
|
||||
if ($scope.availabilityDom) {
|
||||
$scope.availabilityDom.classList.remove("fc-selected")
|
||||
}
|
||||
$scope.availabilityDom = jsEvent.target.closest('.fc-event');
|
||||
$scope.availabilityDom.classList.add("fc-selected")
|
||||
|
||||
// if the user has clicked on the delete event button, delete the event
|
||||
if ($(jsEvent.target).hasClass('remove-event')) {
|
||||
@ -339,8 +488,10 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
* Append the event tag into the block, just after the event title.
|
||||
* @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
*/
|
||||
var eventRenderCb = function (event, element) {
|
||||
element.find('.fc-content').prepend('<span class="remove-event">x </span>');
|
||||
const eventRenderCb = function (event, element) {
|
||||
if (event.available_type !== 'event') {
|
||||
element.find('.fc-content').prepend('<span class="remove-event">x </span>');
|
||||
}
|
||||
if (event.tags.length > 0) {
|
||||
let html = '';
|
||||
for (let tag of Array.from(event.tags)) {
|
||||
@ -355,12 +506,38 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
* Triggered when resource fetching starts/stops.
|
||||
* @see https://fullcalendar.io/docs/resource_data/loading/
|
||||
*/
|
||||
return loadingCb = function (isLoading, view) {
|
||||
const loadingCb = function (isLoading, view) {
|
||||
if (isLoading) {
|
||||
// we remove existing events when fetching starts to prevent duplicates
|
||||
return uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
|
||||
// we remove existing events when fetching starts to prevent duplicates
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggered when the view is changed
|
||||
* @see https://fullcalendar.io/docs/v3/viewRender#v2
|
||||
*/
|
||||
const viewRenderCb = function(view, element) {
|
||||
// we unselect the current event to keep consistency
|
||||
$scope.availability = null;
|
||||
$scope.availabilityDom = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('calendar');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
|
||||
]);
|
||||
@ -368,9 +545,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
/**
|
||||
* Controller used in the slot creation modal window
|
||||
*/
|
||||
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', 'moment', 'start', 'end', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'Tag', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, Tag, growl, _t) {
|
||||
// $uibModal parameter
|
||||
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'plansPromise', 'groupsPromise', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, $sce, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, tagsPromise, plansPromise, groupsPromise, growl, _t) {
|
||||
// $uibModal parameter
|
||||
$scope.start = start;
|
||||
|
||||
// $uibModal parameter
|
||||
@ -385,8 +562,27 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
// spaces list
|
||||
$scope.spaces = spacesPromise.filter(function (s) { return !s.disabled; });
|
||||
|
||||
// all tags list
|
||||
$scope.tags = tagsPromise;
|
||||
|
||||
$scope.isOnlySubscriptions = false;
|
||||
$scope.selectedPlans = [];
|
||||
$scope.selectedPlansBinding = {};
|
||||
// list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = [];
|
||||
for (let group of Array.from(groupsPromise)) {
|
||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||
for (let plan of Array.from(plansPromise)) {
|
||||
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
||||
}
|
||||
if (groupObj.plans.length > 0) {
|
||||
$scope.plansClassifiedByGroup.push(groupObj);
|
||||
}
|
||||
}
|
||||
|
||||
// machines associated with the created slot
|
||||
$scope.selectedMachines = [];
|
||||
$scope.selectedMachinesBinding = {};
|
||||
|
||||
// training associated with the created slot
|
||||
$scope.selectedTraining = null;
|
||||
@ -416,9 +612,29 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
$scope.availability = {
|
||||
start_at: start,
|
||||
end_at: end,
|
||||
available_type: 'machines' // default
|
||||
available_type: 'machines', // default
|
||||
tag_ids: [],
|
||||
is_recurrent: false,
|
||||
period: 'week',
|
||||
nb_periods: 1,
|
||||
end_date: undefined // recurrence end
|
||||
};
|
||||
|
||||
// recurrent slots
|
||||
$scope.occurrences = [];
|
||||
|
||||
// localized name(s) of the reservable item(s)
|
||||
$scope.reservableName = '';
|
||||
|
||||
// localized name(s) of the selected tag(s)
|
||||
$scope.tagsName = '';
|
||||
|
||||
// localized name(s) of the selected plan(s)
|
||||
$scope.plansName = '';
|
||||
|
||||
// make the duration available for display
|
||||
$scope.slotDuration = Fablab.slotDuration;
|
||||
|
||||
/**
|
||||
* Adds or removes the provided machine from the current slot
|
||||
* @param machine {Object}
|
||||
@ -432,6 +648,49 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Select/unselect all the machines
|
||||
*/
|
||||
$scope.toggleAll = function() {
|
||||
const count = $scope.selectedMachines.length;
|
||||
$scope.selectedMachines = [];
|
||||
$scope.selectedMachinesBinding = {};
|
||||
if (count == 0) {
|
||||
$scope.machines.forEach(function (machine) {
|
||||
$scope.selectedMachines.push(machine);
|
||||
$scope.selectedMachinesBinding[machine.id] = true;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes the provided plan from the current slot
|
||||
* @param plan {Object}
|
||||
*/
|
||||
$scope.toggleSelectPlan = function (plan) {
|
||||
const index = $scope.selectedPlans.indexOf(plan);
|
||||
if (index > -1) {
|
||||
return $scope.selectedPlans.splice(index, 1);
|
||||
} else {
|
||||
return $scope.selectedPlans.push(plan);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Select/unselect all the plans
|
||||
*/
|
||||
$scope.toggleAllPlans = function() {
|
||||
const count = $scope.selectedPlans.length;
|
||||
$scope.selectedPlans = [];
|
||||
$scope.selectedPlansBinding = {};
|
||||
if (count == 0) {
|
||||
plansPromise.forEach(function (plan) {
|
||||
$scope.selectedPlans.push(plan);
|
||||
$scope.selectedPlansBinding[plan.id] = true;
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for the modal window validation: save the slot and closes the modal
|
||||
*/
|
||||
@ -440,7 +699,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
if ($scope.selectedMachines.length > 0) {
|
||||
$scope.availability.machine_ids = $scope.selectedMachines.map(function (m) { return m.id; });
|
||||
} else {
|
||||
growl.error(_t('admin_calendar.you_should_select_at_least_a_machine'));
|
||||
growl.error(_t('app.admin.calendar.you_should_select_at_least_a_machine'));
|
||||
return;
|
||||
}
|
||||
} else if ($scope.availability.available_type === 'training') {
|
||||
@ -448,9 +707,16 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
} else if ($scope.availability.available_type === 'space') {
|
||||
$scope.availability.space_ids = [$scope.selectedSpace.id];
|
||||
}
|
||||
if ($scope.availability.is_recurrent) {
|
||||
$scope.availability.occurrences = $scope.occurrences;
|
||||
}
|
||||
if ($scope.isOnlySubscriptions && $scope.selectedPlans.length > 0) {
|
||||
$scope.availability.plan_ids = $scope.selectedPlans.map(function (p) { return p.id; });
|
||||
}
|
||||
return Availability.save(
|
||||
{ availability: $scope.availability }
|
||||
, function (availability) { $uibModalInstance.close(availability); });
|
||||
{ availability: $scope.availability },
|
||||
function (availability) { $uibModalInstance.close(availability); }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -458,6 +724,8 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
*/
|
||||
$scope.next = function () {
|
||||
if ($scope.step === 1) { $scope.setNbTotalPlaces(); }
|
||||
if ($scope.step === 2) { return validateSelection(); }
|
||||
if ($scope.step === 5) { return validateRecurrence(); }
|
||||
return $scope.step++;
|
||||
};
|
||||
|
||||
@ -495,23 +763,33 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
$scope.selectedSpace = $scope.spaces[0];
|
||||
}
|
||||
|
||||
Tag.query().$promise.then(function (data) { $scope.tags = data; });
|
||||
// when disable is only subscriptions option, reset all selected plans
|
||||
$scope.$watch('isOnlySubscriptions', function(value) {
|
||||
if (!value) {
|
||||
$scope.selectedPlans = [];
|
||||
$scope.selectedPlansBinding = {};
|
||||
}
|
||||
});
|
||||
|
||||
// When we configure a machine availability, do not let the user change the end time, as the total
|
||||
// time must be dividable by 60 minutes (base slot duration). For training availabilities, the user
|
||||
// When we configure a machine/space availability, do not let the user change the end time, as the total
|
||||
// time must be dividable by Fablab.slotDuration minutes (base slot duration). For training availabilities, the user
|
||||
// can configure any duration as it does not matters.
|
||||
$scope.$watch('availability.available_type', function (newValue, oldValue, scope) {
|
||||
if ((newValue === 'machines') || (newValue === 'space')) {
|
||||
$scope.endDateReadOnly = true;
|
||||
const diff = moment($scope.end).diff($scope.start, 'hours'); // the result is rounded down by moment.js
|
||||
$scope.end = moment($scope.start).add(diff, 'hours').toDate();
|
||||
const slots = Math.trunc(($scope.end.valueOf() - $scope.start.valueOf()) / (60 * 1000)) / Fablab.slotDuration;
|
||||
if (!Number.isInteger(slots)) {
|
||||
// otherwise, round it to upper decimal
|
||||
const upper = Math.ceil(slots) * Fablab.slotDuration;
|
||||
$scope.end = moment($scope.start).add(upper, 'minutes').toDate();
|
||||
}
|
||||
return $scope.availability.end_at = $scope.end;
|
||||
} else {
|
||||
return $scope.endDateReadOnly = false;
|
||||
}
|
||||
});
|
||||
|
||||
// When the start date is changed, if we are configuring a machine availability,
|
||||
// When the start date is changed, if we are configuring a machine/space availability,
|
||||
// maintain the relative length of the slot (ie. change the end time accordingly)
|
||||
$scope.$watch('start', function (newValue, oldValue, scope) {
|
||||
// for machine or space availabilities, adjust the end time
|
||||
@ -520,27 +798,293 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
end.add(moment(newValue).diff(oldValue), 'milliseconds');
|
||||
$scope.end = end.toDate();
|
||||
} else { // for training availabilities
|
||||
// prevent the admin from setting the begining after the and
|
||||
if (moment(newValue).add(1, 'hour').isAfter($scope.end)) {
|
||||
// prevent the admin from setting the beginning after the end
|
||||
if (moment(newValue).add(Fablab.slotDuration, 'minutes').isAfter($scope.end)) {
|
||||
$scope.start = oldValue;
|
||||
}
|
||||
}
|
||||
// update availability object
|
||||
return $scope.availability.start_at = $scope.start;
|
||||
$scope.availability.start_at = $scope.start;
|
||||
});
|
||||
|
||||
// Maintain consistency between the end time and the date object in the availability object
|
||||
return $scope.$watch('end', function (newValue, oldValue, scope) {
|
||||
// we prevent the admin from setting the end of the availability before its begining
|
||||
if (moment($scope.start).add(1, 'hour').isAfter(newValue)) {
|
||||
$scope.$watch('end', function (newValue, oldValue, scope) {
|
||||
// we prevent the admin from setting the end of the availability before its beginning
|
||||
if (moment($scope.start).add(Fablab.slotDuration, 'minutes').isAfter(newValue)) {
|
||||
$scope.end = oldValue;
|
||||
}
|
||||
// update availability object
|
||||
return $scope.availability.end_at = $scope.end;
|
||||
$scope.availability.end_at = $scope.end;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that a machine or more was/were selected before continuing to step 3 (adjust time + tags)
|
||||
*/
|
||||
const validateSelection = function () {
|
||||
if ($scope.availability.available_type === 'machines') {
|
||||
if ($scope.selectedMachines.length === 0) {
|
||||
return growl.error(_t('app.admin.calendar.you_should_select_at_least_a_machine'));
|
||||
}
|
||||
}
|
||||
$scope.step++;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that the recurrence parameters were correctly set before continuing to step 5 (summary)
|
||||
*/
|
||||
const validateRecurrence = function () {
|
||||
if ($scope.availability.is_recurrent) {
|
||||
if (!$scope.availability.period) {
|
||||
return growl.error(_t('app.admin.calendar.select_period'));
|
||||
}
|
||||
if (!$scope.availability.nb_periods || $scope.availability.nb_periods < 1) {
|
||||
return growl.error(_t('app.admin.calendar.select_nb_period'));
|
||||
}
|
||||
if (!$scope.availability.end_date) {
|
||||
return growl.error(_t('app.admin.calendar.select_end_date'));
|
||||
}
|
||||
}
|
||||
// settings are ok
|
||||
computeOccurrences();
|
||||
computeNames();
|
||||
$scope.step++;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the various occurrences of the availability, according to the recurrence settings
|
||||
*/
|
||||
const computeOccurrences = function () {
|
||||
$scope.occurrences = [];
|
||||
|
||||
if ($scope.availability.is_recurrent) {
|
||||
const date = moment($scope.availability.start_at);
|
||||
const diff = moment($scope.availability.end_at).diff($scope.availability.start_at);
|
||||
const end = moment($scope.availability.end_date).endOf('day');
|
||||
while (date.isBefore(end)) {
|
||||
const occur_end = moment(date).add(diff, 'ms');
|
||||
$scope.occurrences.push({
|
||||
start_at: date.toDate(),
|
||||
end_at: occur_end.toDate()
|
||||
});
|
||||
date.add($scope.availability.nb_periods, $scope.availability.period);
|
||||
}
|
||||
} else {
|
||||
$scope.occurrences.push({
|
||||
start_at: $scope.availability.start_at,
|
||||
end_at: $scope.availability.end_at
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const computeNames = function () {
|
||||
$scope.reservableName = '';
|
||||
switch ($scope.availability.available_type) {
|
||||
case 'machines':
|
||||
$scope.reservableName = localizedList($scope.selectedMachines)
|
||||
break;
|
||||
case 'training':
|
||||
$scope.reservableName = `<strong>${$scope.selectedTraining.name}</strong>`;
|
||||
break;
|
||||
case 'space':
|
||||
$scope.reservableName = `<strong>${$scope.selectedSpace.name}</strong>`;
|
||||
break;
|
||||
default:
|
||||
$scope.reservableName = `<span class="warning">${_t("app.admin.calendar.none")}</span>`;
|
||||
}
|
||||
const tags = $scope.tags.filter(function (t) {
|
||||
return $scope.availability.tag_ids.indexOf(t.id) > -1;
|
||||
})
|
||||
$scope.tagsName = localizedList(tags);
|
||||
if ($scope.isOnlySubscriptions && $scope.selectedPlans.length > 0) {
|
||||
$scope.plansName = localizedList($scope.selectedPlans);
|
||||
}
|
||||
}
|
||||
|
||||
const localizedList = function (items) {
|
||||
if (items.length === 0) return `<span class="text-gray text-italic">${_t("app.admin.calendar.none")}</span>`;
|
||||
|
||||
const names = items.map(function (i) { return $sce.trustAsHtml(`<strong>${i.name}</strong>`); });
|
||||
if (items.length > 1) return names.slice(0, -1).join(', ') + ` ${_t('app.admin.calendar.and')} ` + names[names.length - 1];
|
||||
|
||||
return names[0];
|
||||
}
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller used in the slot deletion modal window
|
||||
*/
|
||||
Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$scope', '$uibModalInstance', 'Availability', 'availabilityPromise', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, Availability, availabilityPromise, growl, _t) {
|
||||
|
||||
// is the current slot (to be deleted) recurrent?
|
||||
$scope.isRecurrent = availabilityPromise.is_recurrent;
|
||||
|
||||
// with recurrent slots: how many slots should we delete?
|
||||
$scope.deleteMode = 'single';
|
||||
|
||||
/**
|
||||
* Confirmation callback
|
||||
*/
|
||||
$scope.ok = function () {
|
||||
const { id, start_at, end_at } = availabilityPromise;
|
||||
// the admin has confirmed, delete the slot
|
||||
Availability.delete(
|
||||
{ id, mode: $scope.deleteMode },
|
||||
function (res) {
|
||||
// delete success
|
||||
if (res.deleted > 1) {
|
||||
growl.success(_t(
|
||||
'app.admin.calendar.slots_deleted',
|
||||
{START: moment(start_at).format('LL LT'), COUNT: res.deleted - 1}
|
||||
));
|
||||
} else {
|
||||
growl.success(_t(
|
||||
'app.admin.calendar.slot_successfully_deleted',
|
||||
{START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')}
|
||||
));
|
||||
}
|
||||
$uibModalInstance.close({
|
||||
status: 'success',
|
||||
availabilities: res.details.map(function (d) { return d.availability.id })
|
||||
});
|
||||
},
|
||||
function (res) {
|
||||
// not everything was deleted
|
||||
const { data } = res;
|
||||
if (data.total > 1) {
|
||||
growl.warning(_t(
|
||||
'app.admin.calendar.slots_not_deleted',
|
||||
{TOTAL: data.total, COUNT: data.total - data.deleted}
|
||||
));
|
||||
} else {
|
||||
growl.error(_t(
|
||||
'app.admin.calendar.unable_to_delete_the_slot',
|
||||
{START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')}
|
||||
));
|
||||
}
|
||||
$uibModalInstance.close({
|
||||
status: 'failed',
|
||||
availabilities: data.details.filter(function (d) { return d.status }).map(function (d) { return d.availability.id })
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancellation callback
|
||||
*/
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Controller used in the iCalendar (ICS) imports management page
|
||||
*/
|
||||
|
||||
Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'dialogs', 'growl', '_t',
|
||||
function ($scope, iCalendars, ICalendar, dialogs, growl, _t) {
|
||||
// list of ICS sources
|
||||
$scope.calendars = iCalendars;
|
||||
|
||||
// configuration of a new ICS source
|
||||
$scope.newCalendar = {
|
||||
color: undefined,
|
||||
text_color: undefined,
|
||||
url: undefined,
|
||||
name: undefined,
|
||||
text_hidden: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the new iCalendar in database
|
||||
*/
|
||||
$scope.save = function () {
|
||||
ICalendar.save({}, { i_calendar: $scope.newCalendar }, function (data) {
|
||||
// success
|
||||
$scope.calendars.push(data);
|
||||
$scope.newCalendar.url = undefined;
|
||||
$scope.newCalendar.name = undefined;
|
||||
$scope.newCalendar.color = null;
|
||||
$scope.newCalendar.text_color = null;
|
||||
$scope.newCalendar.text_hidden = false;
|
||||
}, function (error) {
|
||||
// failed
|
||||
growl.error(_t('app.admin.icalendar.create_error'));
|
||||
console.error(error);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a CSS-like style of the given calendar configuration
|
||||
* @param calendar
|
||||
*/
|
||||
$scope.calendarStyle = function (calendar) {
|
||||
return {
|
||||
'border-color': calendar.color,
|
||||
'color': calendar.text_color,
|
||||
'width': calendar.text_hidden ? '50px' : 'auto',
|
||||
'height': calendar.text_hidden ? '21px' : 'auto'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given calendar from the database
|
||||
* @param calendar
|
||||
*/
|
||||
$scope.delete = function (calendar) {
|
||||
dialogs.confirm(
|
||||
{
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('app.admin.icalendar.confirmation_required'),
|
||||
msg: _t('app.admin.icalendar.confirm_delete_import')
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
ICalendar.delete(
|
||||
{ id: calendar.id },
|
||||
function () {
|
||||
// success
|
||||
const idx = $scope.calendars.indexOf(calendar);
|
||||
$scope.calendars.splice(idx, 1);
|
||||
growl.info(_t('app.admin.icalendar.delete_success'));
|
||||
}, function (error) {
|
||||
// failed
|
||||
growl.error(_t('app.admin.icalendar.delete_failed'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously re-fetches the events from the given calendar
|
||||
* @param calendar
|
||||
*/
|
||||
$scope.sync = function (calendar) {
|
||||
ICalendar.sync(
|
||||
{ id: calendar.id },
|
||||
function () {
|
||||
// success
|
||||
growl.info(_t('app.admin.icalendar.refresh'));
|
||||
}, function (error) {
|
||||
// failed
|
||||
growl.error(_t('app.admin.icalendar.sync_failed'));
|
||||
console.error(error);
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
@ -1,19 +1,8 @@
|
||||
/* eslint-disable
|
||||
no-return-assign,
|
||||
no-undef,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
/* COMMON CODE */
|
||||
|
||||
// The validity per user defines how many time a user may ba able to use the same coupon
|
||||
// Here are the various options for this parameter
|
||||
const userValidities = ['once', 'forever'];
|
||||
const VALIDITIES = ['once', 'forever'];
|
||||
|
||||
/**
|
||||
* Controller used in the coupon creation page
|
||||
@ -27,7 +16,7 @@ Application.Controllers.controller('NewCouponController', ['$scope', '$state', '
|
||||
};
|
||||
|
||||
// Options for the validity per user
|
||||
$scope.validities = userValidities;
|
||||
$scope.validities = VALIDITIES;
|
||||
|
||||
// Default parameters for AngularUI-Bootstrap datepicker (used for coupon validity limit selection)
|
||||
$scope.datePicker = {
|
||||
@ -39,6 +28,13 @@ Application.Controllers.controller('NewCouponController', ['$scope', '$state', '
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a localized human-readable name for the provided validity
|
||||
*/
|
||||
$scope.validityName = function (validity) {
|
||||
return _t(`app.shared.coupon.${validity}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows/hides the validity limit datepicker
|
||||
* @param $event {Object} jQuery event object
|
||||
@ -46,17 +42,17 @@ Application.Controllers.controller('NewCouponController', ['$scope', '$state', '
|
||||
$scope.toggleDatePicker = function ($event) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
return $scope.datePicker.opened = !$scope.datePicker.opened;
|
||||
$scope.datePicker.opened = !$scope.datePicker.opened;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to save the new coupon in $scope.coupon and redirect the user to the listing page
|
||||
*/
|
||||
return $scope.saveCoupon = () =>
|
||||
$scope.saveCoupon = () =>
|
||||
Coupon.save({ coupon: $scope.coupon }, coupon => $state.go('app.admin.pricing')
|
||||
, function (err) {
|
||||
growl.error(_t('unable_to_create_the_coupon_check_code_already_used'));
|
||||
return console.error(err);
|
||||
growl.error(_t('app.admin.coupons_new.unable_to_create_the_coupon_check_code_already_used'));
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
]);
|
||||
@ -75,7 +71,7 @@ Application.Controllers.controller('EditCouponController', ['$scope', '$state',
|
||||
$scope.coupon = couponPromise;
|
||||
|
||||
// Options for the validity per user
|
||||
$scope.validities = userValidities;
|
||||
$scope.validities = VALIDITIES;
|
||||
|
||||
// Mapping for validation errors
|
||||
$scope.errors = {};
|
||||
@ -90,6 +86,13 @@ Application.Controllers.controller('EditCouponController', ['$scope', '$state',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a localized human-readable name for the provided validity
|
||||
*/
|
||||
$scope.validityName = function (validity) {
|
||||
return _t(`app.shared.coupon.${validity}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows/hides the validity limit datepicker
|
||||
* @param $event {Object} jQuery event object
|
||||
@ -97,7 +100,7 @@ Application.Controllers.controller('EditCouponController', ['$scope', '$state',
|
||||
$scope.toggleDatePicker = function ($event) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
return $scope.datePicker.opened = !$scope.datePicker.opened;
|
||||
$scope.datePicker.opened = !$scope.datePicker.opened;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -105,10 +108,10 @@ Application.Controllers.controller('EditCouponController', ['$scope', '$state',
|
||||
*/
|
||||
$scope.updateCoupon = function () {
|
||||
$scope.errors = {};
|
||||
return Coupon.update({ id: $scope.coupon.id }, { coupon: $scope.coupon }, coupon => $state.go('app.admin.pricing')
|
||||
Coupon.update({ id: $scope.coupon.id }, { coupon: $scope.coupon }, coupon => $state.go('app.admin.pricing')
|
||||
, function (err) {
|
||||
growl.error(_t('unable_to_update_the_coupon_an_error_occurred'));
|
||||
return $scope.errors = err.data;
|
||||
growl.error(_t('app.admin.coupons_edit.unable_to_update_the_coupon_an_error_occurred'));
|
||||
$scope.errors = err.data;
|
||||
});
|
||||
};
|
||||
|
||||
@ -120,7 +123,7 @@ Application.Controllers.controller('EditCouponController', ['$scope', '$state',
|
||||
const initialize = function () {
|
||||
// parse the date if any
|
||||
if (couponPromise.valid_until) {
|
||||
return $scope.coupon.valid_until = moment(couponPromise.valid_until).toDate();
|
||||
$scope.coupon.valid_until = moment(couponPromise.valid_until).toDate();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -56,14 +56,7 @@ class EventsController {
|
||||
* @param content {Object} JSON - The upload's result
|
||||
*/
|
||||
$scope.submited = function (content) {
|
||||
if ((content.id == null)) {
|
||||
$scope.alerts = [];
|
||||
angular.forEach(content, function (v, k) {
|
||||
angular.forEach(v, function (err) { $scope.alerts.push({ msg: k + ': ' + err, type: 'danger' }); });
|
||||
});
|
||||
} else {
|
||||
$state.go('app.public.events_list');
|
||||
}
|
||||
$scope.onSubmited(content);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -160,8 +153,8 @@ class EventsController {
|
||||
/**
|
||||
* Controller used in the events listing page (admin view)
|
||||
*/
|
||||
Application.Controllers.controller('AdminEventsController', ['$scope', '$state', 'dialogs', '$uibModal', 'growl', 'Event', 'Category', 'EventTheme', 'AgeRange', 'PriceCategory', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t',
|
||||
function ($scope, $state, dialogs, $uibModal, growl, Event, Category, EventTheme, AgeRange, PriceCategory, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t) {
|
||||
Application.Controllers.controller('AdminEventsController', ['$scope', '$state', 'dialogs', '$uibModal', 'growl', 'Event', 'Category', 'EventTheme', 'AgeRange', 'PriceCategory', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t', 'Member', 'uiTourService',
|
||||
function ($scope, $state, dialogs, $uibModal, growl, Event, Category, EventTheme, AgeRange, PriceCategory, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t, Member, uiTourService) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// By default, the pagination mode is activated to limit the page size
|
||||
@ -196,6 +189,9 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
$scope.eventsScope =
|
||||
{ selected: '' };
|
||||
|
||||
// default tab: events list
|
||||
$scope.tabs = { active: 0 };
|
||||
|
||||
/**
|
||||
* Adds a bucket of events to the bottom of the page, grouped by month
|
||||
*/
|
||||
@ -228,26 +224,26 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
*/
|
||||
$scope.removeElement = function (model, index) {
|
||||
if ((model === 'category') && (getModel(model)[1].length === 1)) {
|
||||
growl.error(_t('at_least_one_category_is_required') + ' ' + _t('unable_to_delete_the_last_one'));
|
||||
growl.error(_t('app.admin.events.at_least_one_category_is_required') + ' ' + _t('app.admin.events.unable_to_delete_the_last_one'));
|
||||
return false;
|
||||
}
|
||||
if (getModel(model)[1][index].related_to > 0) {
|
||||
growl.error(_t('unable_to_delete_ELEMENT_already_in_use_NUMBER_times', { ELEMENT: model, NUMBER: getModel(model)[1][index].related_to }, 'messageformat'));
|
||||
growl.error(_t('app.admin.events.unable_to_delete_ELEMENT_already_in_use_NUMBER_times', { ELEMENT: model, NUMBER: getModel(model)[1][index].related_to }));
|
||||
return false;
|
||||
}
|
||||
return dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_ELEMENT', { ELEMENT: model }, 'messageformat')
|
||||
title: _t('app.admin.events.confirmation_required'),
|
||||
msg: _t('app.admin.events.do_you_really_want_to_delete_this_ELEMENT', { ELEMENT: model })
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
, function () { // delete confirmed
|
||||
getModel(model)[0].delete(getModel(model)[1][index], null, function () { getModel(model)[1].splice(index, 1); }
|
||||
, function () { growl.error(_t('unable_to_delete_an_error_occured')); });
|
||||
, function () { growl.error(_t('app.admin.events.unable_to_delete_an_error_occured')); });
|
||||
});
|
||||
};
|
||||
|
||||
@ -292,10 +288,10 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
// save the price category to the API
|
||||
PriceCategory.save(p_cat, function (cat) {
|
||||
$scope.priceCategories.push(cat);
|
||||
return growl.success(_t('price_category_successfully_created'));
|
||||
return growl.success(_t('app.admin.events.price_category_successfully_created'));
|
||||
}
|
||||
, function (err) {
|
||||
growl.error(_t('unable_to_add_the_price_category_check_name_already_used'));
|
||||
growl.error(_t('app.admin.events.unable_to_add_the_price_category_check_name_already_used'));
|
||||
return console.error(err);
|
||||
});
|
||||
});
|
||||
@ -308,7 +304,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
*/
|
||||
$scope.editPriceCategory = function (id, index) {
|
||||
if ($scope.priceCategories[index].id !== id) {
|
||||
return growl.error(_t('unexpected_error_occurred_please_refresh'));
|
||||
return growl.error(_t('app.admin.events.unexpected_error_occurred_please_refresh'));
|
||||
} else {
|
||||
return $uibModal.open({
|
||||
templateUrl: '<%= asset_path "admin/events/price_form.html" %>',
|
||||
@ -320,10 +316,10 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
// update the price category to the API
|
||||
PriceCategory.update({ id }, { price_category: p_cat }, function (cat) {
|
||||
$scope.priceCategories[index] = cat;
|
||||
return growl.success(_t('price_category_successfully_updated'));
|
||||
return growl.success(_t('app.admin.events.price_category_successfully_updated'));
|
||||
}
|
||||
, function (err) {
|
||||
growl.error(_t('unable_to_update_the_price_category'));
|
||||
growl.error(_t('app.admin.events.unable_to_update_the_price_category'));
|
||||
return console.error(err);
|
||||
});
|
||||
});
|
||||
@ -337,17 +333,17 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
*/
|
||||
$scope.removePriceCategory = function (id, index) {
|
||||
if ($scope.priceCategories[index].id !== id) {
|
||||
return growl.error(_t('unexpected_error_occurred_please_refresh'));
|
||||
return growl.error(_t('app.admin.events.unexpected_error_occurred_please_refresh'));
|
||||
} else if ($scope.priceCategories[index].events > 0) {
|
||||
return growl.error(_t('unable_to_delete_this_price_category_because_it_is_already_used'));
|
||||
return growl.error(_t('app.admin.events.unable_to_delete_this_price_category_because_it_is_already_used'));
|
||||
} else {
|
||||
return dialogs.confirm(
|
||||
{
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_price_category')
|
||||
title: _t('app.admin.events.confirmation_required'),
|
||||
msg: _t('app.admin.events.do_you_really_want_to_delete_this_price_category')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -356,10 +352,10 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
PriceCategory.remove(
|
||||
{ id },
|
||||
function () { // successfully deleted
|
||||
growl.success(_t('price_category_successfully_deleted'));
|
||||
growl.success(_t('app.admin.events.price_category_successfully_deleted'));
|
||||
$scope.priceCategories.splice(index, 1);
|
||||
},
|
||||
function () { growl.error(_t('price_category_deletion_failed')); }
|
||||
function () { growl.error(_t('app.admin.events.price_category_deletion_failed')); }
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -378,12 +374,127 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
return $scope.page = 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/events page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupEventsTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('events');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.events.welcome.title'),
|
||||
content: _t('app.admin.tour.events.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.events-management .events-list',
|
||||
stepId: 'list',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.events.list.title'),
|
||||
content: _t('app.admin.tour.events.list.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.events-management .events-list-filter',
|
||||
stepId: 'filter',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.events.filter.title'),
|
||||
content: _t('app.admin.tour.events.filter.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.events-management .events-categories',
|
||||
stepId: 'categories',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.events.categories.title'),
|
||||
content: _t('app.admin.tour.events.categories.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.events-management .events-themes',
|
||||
stepId: 'themes',
|
||||
order: 4,
|
||||
title: _t('app.admin.tour.events.themes.title'),
|
||||
content: _t('app.admin.tour.events.themes.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.events-management .events-age-ranges',
|
||||
stepId: 'ages',
|
||||
order: 5,
|
||||
title: _t('app.admin.tour.events.ages.title'),
|
||||
content: _t('app.admin.tour.events.ages.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.events-management .prices-tab',
|
||||
stepId: 'prices',
|
||||
order: 6,
|
||||
title: _t('app.admin.tour.events.prices.title'),
|
||||
content: _t('app.admin.tour.events.prices.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 7,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on step change, change the active tab if needed
|
||||
uitour.on('stepChanged', function (nextStep) {
|
||||
if (nextStep.stepId === 'list' || nextStep.stepId === 'filter') { $scope.tabs.active = 0; }
|
||||
if (nextStep.stepId === 'categories' || nextStep.stepId === 'ages') { $scope.tabs.active = 1; }
|
||||
if (nextStep.stepId === 'prices') { $scope.tabs.active = 2; }
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('events') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'events' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('events') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () { paginationCheck(eventsPromise, $scope.events); };
|
||||
const initialize = function () {
|
||||
paginationCheck(eventsPromise, $scope.events);
|
||||
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('events');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if all events are already displayed OR if the button 'load more events'
|
||||
@ -391,7 +502,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
* @param lastEvents {Array} last events loaded onto the diplay (ie. last "page")
|
||||
* @param events {Array} full list of events displayed on the page (not only the last retrieved)
|
||||
*/
|
||||
var paginationCheck = function (lastEvents, events) {
|
||||
const paginationCheck = function (lastEvents, events) {
|
||||
if (lastEvents.length > 0) {
|
||||
if (events.length >= lastEvents[0].nb_total_events) {
|
||||
return $scope.paginateActive = false;
|
||||
@ -408,7 +519,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
* @param name {string} 'category', 'theme' or 'age_range'
|
||||
* @return {[Object, Array]} model and datastore
|
||||
*/
|
||||
var getModel = function (name) {
|
||||
const getModel = function (name) {
|
||||
switch (name) {
|
||||
case 'category': return [Category, $scope.categories];
|
||||
case 'theme': return [EventTheme, $scope.themes];
|
||||
@ -431,7 +542,16 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
|
||||
$scope.event = eventPromise;
|
||||
|
||||
// list of reservations for the current event
|
||||
return $scope.reservations = reservationsPromise;
|
||||
$scope.reservations = reservationsPromise;
|
||||
|
||||
/**
|
||||
* Test if the provided reservation has been cancelled
|
||||
* @param reservation {Reservation}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.isCancelled = function(reservation) {
|
||||
return !!(reservation.slots[0].canceled_at);
|
||||
}
|
||||
}]);
|
||||
|
||||
/**
|
||||
@ -474,13 +594,25 @@ Application.Controllers.controller('NewEventController', ['$scope', '$state', 'C
|
||||
|
||||
// Possible types of recurrences for an event
|
||||
$scope.recurrenceTypes = [
|
||||
{ label: _t('none'), value: 'none' },
|
||||
{ label: _t('every_days'), value: 'day' },
|
||||
{ label: _t('every_week'), value: 'week' },
|
||||
{ label: _t('every_month'), value: 'month' },
|
||||
{ label: _t('every_year'), value: 'year' }
|
||||
{ label: _t('app.admin.events_new.none'), value: 'none' },
|
||||
{ label: _t('app.admin.events_new.every_days'), value: 'day' },
|
||||
{ label: _t('app.admin.events_new.every_week'), value: 'week' },
|
||||
{ label: _t('app.admin.events_new.every_month'), value: 'month' },
|
||||
{ label: _t('app.admin.events_new.every_year'), value: 'year' }
|
||||
];
|
||||
|
||||
// triggered when the new event form was submitted to the API and have received an answer
|
||||
$scope.onSubmited = function(content) {
|
||||
if ((content.id == null)) {
|
||||
$scope.alerts = [];
|
||||
angular.forEach(content, function (v, k) {
|
||||
angular.forEach(v, function (err) { $scope.alerts.push({ msg: k + ': ' + err, type: 'danger' }); });
|
||||
});
|
||||
} else {
|
||||
$state.go('app.public.events_list');
|
||||
}
|
||||
};
|
||||
|
||||
// Using the EventsController
|
||||
return new EventsController($scope, $state);
|
||||
}
|
||||
@ -489,9 +621,9 @@ Application.Controllers.controller('NewEventController', ['$scope', '$state', 'C
|
||||
/**
|
||||
* Controller used in the events edition page
|
||||
*/
|
||||
Application.Controllers.controller('EditEventController', ['$scope', '$state', '$stateParams', 'CSRF', 'eventPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise',
|
||||
function ($scope, $state, $stateParams, CSRF, eventPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise) {
|
||||
/* PUBLIC SCOPE */
|
||||
Application.Controllers.controller('EditEventController', ['$scope', '$state', '$stateParams', 'CSRF', 'eventPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '$uibModal', 'growl', '_t',
|
||||
function ($scope, $state, $stateParams, CSRF, eventPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, $uibModal, growl, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// API URL where the form will be posted
|
||||
$scope.actionUrl = `/api/events/${$stateParams.id}`;
|
||||
@ -502,6 +634,9 @@ Application.Controllers.controller('EditEventController', ['$scope', '$state', '
|
||||
// Retrieve the event details, in case of error the user is redirected to the events listing
|
||||
$scope.event = eventPromise;
|
||||
|
||||
// We'll keep track of the initial dates here, for later comparison
|
||||
$scope.initialDates = {};
|
||||
|
||||
// List of categories for the events
|
||||
$scope.categories = categoriesPromise;
|
||||
|
||||
@ -514,6 +649,83 @@ Application.Controllers.controller('EditEventController', ['$scope', '$state', '
|
||||
// List of age ranges
|
||||
$scope.ageRanges = ageRangesPromise;
|
||||
|
||||
// Default edit-mode for periodic event
|
||||
$scope.editMode = 'single';
|
||||
|
||||
// show edit-mode modal if event is recurrent
|
||||
$scope.isShowEditModeModal = $scope.event.recurrence_events.length > 0;
|
||||
|
||||
$scope.editRecurrent = function (e) {
|
||||
if ($scope.isShowEditModeModal && $scope.event.recurrence_events.length > 0) {
|
||||
e.preventDefault();
|
||||
|
||||
// open a choice edit-mode dialog
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "events/editRecurrent.html" %>',
|
||||
size: 'md',
|
||||
controller: 'EditRecurrentEventController',
|
||||
resolve: {
|
||||
editMode: function () { return $scope.editMode; },
|
||||
initialDates: function () { return $scope.initialDates; },
|
||||
currentEvent: function () { return $scope.event; }
|
||||
}
|
||||
});
|
||||
// submit form event by edit-mode
|
||||
modalInstance.result.then(function(res) {
|
||||
$scope.isShowEditModeModal = false;
|
||||
$scope.editMode = res.editMode;
|
||||
e.target.click();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// triggered when the edit event form was submitted to the API and have received an answer
|
||||
$scope.onSubmited = function(data) {
|
||||
if (data.total === data.updated) {
|
||||
if (data.updated > 1) {
|
||||
growl.success(_t(
|
||||
'app.admin.events_edit.events_updated',
|
||||
{COUNT: data.updated - 1}
|
||||
));
|
||||
} else {
|
||||
growl.success(_t(
|
||||
'app.admin.events_edit.event_successfully_updated'
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if (data.total > 1) {
|
||||
growl.warning(_t(
|
||||
'app.admin.events_edit.events_not_updated',
|
||||
{TOTAL: data.total, COUNT: data.total - data.updated}
|
||||
));
|
||||
if (_.find(data.details, { error: 'EventPriceCategory' })) {
|
||||
growl.error(_t(
|
||||
'app.admin.events_edit.error_deleting_reserved_price'
|
||||
));
|
||||
} else {
|
||||
growl.error(_t(
|
||||
'app.admin.events_edit.other_error'
|
||||
));
|
||||
}
|
||||
} else {
|
||||
growl.error(_t(
|
||||
'app.admin.events_edit.unable_to_update_the_event'
|
||||
));
|
||||
if (data.details[0].error === 'EventPriceCategory') {
|
||||
growl.error(_t(
|
||||
'app.admin.events_edit.error_deleting_reserved_price'
|
||||
));
|
||||
} else {
|
||||
growl.error(_t(
|
||||
'app.admin.events_edit.other_error'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
$state.go('app.public.events_list');
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -526,6 +738,11 @@ Application.Controllers.controller('EditEventController', ['$scope', '$state', '
|
||||
$scope.event.start_date = moment($scope.event.start_date).toDate();
|
||||
$scope.event.end_date = moment($scope.event.end_date).toDate();
|
||||
|
||||
$scope.initialDates = {
|
||||
start: new Date($scope.event.start_date.valueOf()),
|
||||
end: new Date($scope.event.end_date.valueOf())
|
||||
};
|
||||
|
||||
// Using the EventsController
|
||||
return new EventsController($scope, $state);
|
||||
};
|
||||
@ -534,3 +751,36 @@ Application.Controllers.controller('EditEventController', ['$scope', '$state', '
|
||||
return initialize();
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller used in the event edit-mode modal window
|
||||
*/
|
||||
Application.Controllers.controller('EditRecurrentEventController', ['$scope', '$uibModalInstance', 'editMode', 'growl', 'initialDates', 'currentEvent', '_t',
|
||||
function ($scope, $uibModalInstance, editMode, growl, initialDates, currentEvent, _t) {
|
||||
// with recurrent slots: how many slots should we update?
|
||||
$scope.editMode = editMode;
|
||||
|
||||
/**
|
||||
* Confirmation callback
|
||||
*/
|
||||
$scope.ok = function () {
|
||||
$uibModalInstance.close({
|
||||
editMode: $scope.editMode
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if any of the dates of the event has changed
|
||||
*/
|
||||
$scope.hasDateChanged = function() {
|
||||
return (!moment(initialDates.start).isSame(currentEvent.start_date, 'day') || !moment(initialDates.end).isSame(currentEvent.end_date, 'day'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancellation callback
|
||||
*/
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
@ -25,10 +25,10 @@ Application.Controllers.controller('GraphsController', ['$scope', '$state', '$ro
|
||||
const CHART_HEIGHT = 500;
|
||||
|
||||
// Label of the charts' horizontal axes
|
||||
const X_AXIS_LABEL = _t('date');
|
||||
const X_AXIS_LABEL = _t('app.admin.stats_graphs.date');
|
||||
|
||||
// Label of the charts' vertical axes
|
||||
const Y_AXIS_LABEL = _t('number');
|
||||
const Y_AXIS_LABEL = _t('app.admin.stats_graphs.number');
|
||||
|
||||
// Colors for the line charts. Each new line uses the next color in this array
|
||||
const CHART_COLORS = ['#b35a94', '#1c5794', '#00b49e', '#6fac48', '#ebcf4a', '#fd7e33', '#ca3436', '#a26e3a'];
|
||||
@ -193,9 +193,9 @@ Application.Controllers.controller('GraphsController', ['$scope', '$state', '$ro
|
||||
}
|
||||
} else if ($scope.display.interval === 'week') {
|
||||
if ((typeof x === 'number') || d instanceof Date) {
|
||||
return d3.time.format(_t('week_short') + ' %U')(moment(d).toDate());
|
||||
return d3.time.format(_t('app.admin.stats_graphs.week_short') + ' %U')(moment(d).toDate());
|
||||
} else if (typeof d === 'number') {
|
||||
return _t('week_of_START_to_END', { START: moment(d).format('L'), END: moment(d).add(6, 'days').format('L') });
|
||||
return _t('app.admin.stats_graphs.week_of_START_to_END', { START: moment(d).format('L'), END: moment(d).add(6, 'days').format('L') });
|
||||
} else { // typeof d == 'string'
|
||||
return d;
|
||||
}
|
||||
@ -653,7 +653,7 @@ Application.Controllers.controller('GraphsController', ['$scope', '$state', '$ro
|
||||
|
||||
// common for each charts
|
||||
chart.margin({ left: 100, right: 100 });
|
||||
chart.noData(_t('no_data_for_this_period'));
|
||||
chart.noData(_t('app.admin.stats_graphs.no_data_for_this_period'));
|
||||
chart.height(CHART_HEIGHT);
|
||||
|
||||
// add new chart to the page
|
||||
|
@ -54,15 +54,15 @@ Application.Controllers.controller('GroupsController', ['$scope', 'groupsPromise
|
||||
*/
|
||||
$scope.saveGroup = function (data, id) {
|
||||
if (id != null) {
|
||||
return Group.update({ id }, { group: data }, response => growl.success(_t('group_form.changes_successfully_saved'))
|
||||
, error => growl.error(_t('group_form.an_error_occurred_while_saving_changes')));
|
||||
return Group.update({ id }, { group: data }, response => growl.success(_t('app.admin.members.group_form.changes_successfully_saved'))
|
||||
, error => growl.error(_t('app.admin.members.group_form.an_error_occurred_while_saving_changes')));
|
||||
} else {
|
||||
return Group.save({ group: data }, function (resp) {
|
||||
growl.success(_t('group_form.new_group_successfully_saved'));
|
||||
growl.success(_t('app.admin.members.group_form.new_group_successfully_saved'));
|
||||
return $scope.groups[$scope.groups.length - 1].id = resp.id;
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('.group_forman_error_occurred_when_saving_the_new_group'));
|
||||
growl.error(_t('app.admin.members.group_form.an_error_occurred_when_saving_the_new_group'));
|
||||
return $scope.groups.splice($scope.groups.length - 1, 1);
|
||||
});
|
||||
}
|
||||
@ -74,10 +74,10 @@ Application.Controllers.controller('GroupsController', ['$scope', 'groupsPromise
|
||||
*/
|
||||
$scope.removeGroup = index =>
|
||||
Group.delete({ id: $scope.groups[index].id }, function (resp) {
|
||||
growl.success(_t('group_form.group_successfully_deleted'));
|
||||
growl.success(_t('app.admin.members.group_form.group_successfully_deleted'));
|
||||
return $scope.groups.splice(index, 1);
|
||||
}
|
||||
, error => growl.error(_t('group_form.unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it')));
|
||||
, error => growl.error(_t('app.admin.members.group_form.unable_to_delete_group_because_some_users_and_or_groups_are_still_linked_to_it')));
|
||||
|
||||
/**
|
||||
* Enable/disable the group at the specified index
|
||||
@ -86,13 +86,13 @@ Application.Controllers.controller('GroupsController', ['$scope', 'groupsPromise
|
||||
return $scope.toggleDisableGroup = function (index) {
|
||||
const group = $scope.groups[index];
|
||||
if (!group.disabled && (group.users > 0)) {
|
||||
return growl.error(_t('group_form.unable_to_disable_group_with_users', { USERS: group.users }, 'messageformat'));
|
||||
return growl.error(_t('app.admin.members.group_form.unable_to_disable_group_with_users', { USERS: group.users }));
|
||||
} else {
|
||||
return Group.update({ id: group.id }, { group: { disabled: !group.disabled } }, function (response) {
|
||||
$scope.groups[index] = response;
|
||||
return growl.success(_t('group_form.group_successfully_enabled_disabled', { STATUS: response.disabled }, 'messageformat'));
|
||||
return growl.success(_t('app.admin.members.group_form.group_successfully_enabled_disabled', { STATUS: response.disabled }));
|
||||
}
|
||||
, error => growl.error(_t('group_form.unable_to_enable_disable_group', { STATUS: !group.disabled }, 'messageformat')));
|
||||
, error => growl.error(_t('app.admin.members.group_form.unable_to_enable_disable_group', { STATUS: !group.disabled })));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -17,8 +17,8 @@
|
||||
/**
|
||||
* Controller used in the admin invoices listing page
|
||||
*/
|
||||
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t',
|
||||
function ($scope, $state, Invoice, AccountingPeriod, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t) {
|
||||
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t', 'Member', 'uiTourService',
|
||||
function ($scope, $state, Invoice, AccountingPeriod, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t, Member, uiTourService) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// number of invoices loaded each time we click on 'load more...'
|
||||
@ -28,11 +28,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
|
||||
// default active tab
|
||||
$scope.tabs = {
|
||||
listing: { active: !Fablab.withoutInvoices },
|
||||
settings: { active: Fablab.withoutInvoices }
|
||||
active: Fablab.withoutInvoices ? 1 : 0
|
||||
};
|
||||
|
||||
// List of all users invoices
|
||||
// List of all invoices
|
||||
$scope.invoices = invoices;
|
||||
|
||||
// Invoices filters
|
||||
@ -215,7 +214,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
$scope.invoices.unshift(res.avoir);
|
||||
return Invoice.get({ id: invoice.id }, function (data) {
|
||||
invoice.has_avoir = data.has_avoir;
|
||||
return growl.success(_t('invoices.refund_invoice_successfully_created'));
|
||||
return growl.success(_t('app.admin.invoices.refund_invoice_successfully_created'));
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -246,7 +245,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate an order nmuber sample from the parametrized model
|
||||
* Generate an order number sample from the parametrized model
|
||||
* @returns {string} invoice reference sample
|
||||
*/
|
||||
$scope.mkNumber = function () {
|
||||
@ -289,10 +288,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
modalInstance.result.then(function (model) {
|
||||
Setting.update({ name: 'invoice_reference' }, { value: model }, function (data) {
|
||||
$scope.invoice.reference.model = model;
|
||||
growl.success(_t('invoices.invoice_reference_successfully_saved'));
|
||||
growl.success(_t('app.admin.invoices.invoice_reference_successfully_saved'));
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_saving_invoice_reference'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_invoice_reference'));
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
@ -327,24 +326,24 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
Setting.update({ name: 'invoice_code-value' }, { value: result.model }, function (data) {
|
||||
$scope.invoice.code.model = result.model;
|
||||
if (result.active) {
|
||||
return growl.success(_t('invoices.invoicing_code_succesfully_saved'));
|
||||
return growl.success(_t('app.admin.invoices.invoicing_code_succesfully_saved'));
|
||||
}
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_saving_the_invoicing_code'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_invoicing_code'));
|
||||
return console.error(error);
|
||||
});
|
||||
|
||||
return Setting.update({ name: 'invoice_code-active' }, { value: result.active ? 'true' : 'false' }, function (data) {
|
||||
$scope.invoice.code.active = result.active;
|
||||
if (result.active) {
|
||||
return growl.success(_t('invoices.code_successfully_activated'));
|
||||
return growl.success(_t('app.admin.invoices.code_successfully_activated'));
|
||||
} else {
|
||||
return growl.success(_t('invoices.code_successfully_disabled'));
|
||||
return growl.success(_t('app.admin.invoices.code_successfully_disabled'));
|
||||
}
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_activating_the_invoicing_code'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_activating_the_invoicing_code'));
|
||||
return console.error(error);
|
||||
});
|
||||
});
|
||||
@ -373,10 +372,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
return modalInstance.result.then(function (model) {
|
||||
Setting.update({ name: 'invoice_order-nb' }, { value: model }, function (data) {
|
||||
$scope.invoice.number.model = model;
|
||||
return growl.success(_t('invoices.order_number_successfully_saved'));
|
||||
return growl.success(_t('app.admin.invoices.order_number_successfully_saved'));
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_saving_the_order_number'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_order_number'));
|
||||
return console.error(error);
|
||||
});
|
||||
});
|
||||
@ -431,24 +430,24 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
Setting.update({ name: 'invoice_VAT-rate' }, { value: result.rate + '' }, function (data) {
|
||||
$scope.invoice.VAT.rate = result.rate;
|
||||
if (result.active) {
|
||||
return growl.success(_t('invoices.VAT_rate_successfully_saved'));
|
||||
return growl.success(_t('app.admin.invoices.VAT_rate_successfully_saved'));
|
||||
}
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_saving_the_VAT_rate'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_VAT_rate'));
|
||||
return console.error(error);
|
||||
});
|
||||
|
||||
return Setting.update({ name: 'invoice_VAT-active' }, { value: result.active ? 'true' : 'false' }, function (data) {
|
||||
$scope.invoice.VAT.active = result.active;
|
||||
if (result.active) {
|
||||
return growl.success(_t('invoices.VAT_successfully_activated'));
|
||||
return growl.success(_t('app.admin.invoices.VAT_successfully_activated'));
|
||||
} else {
|
||||
return growl.success(_t('invoices.VAT_successfully_disabled'));
|
||||
return growl.success(_t('app.admin.invoices.VAT_successfully_disabled'));
|
||||
}
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_activating_the_VAT'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_activating_the_VAT'));
|
||||
return console.error(error);
|
||||
});
|
||||
});
|
||||
@ -461,10 +460,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
const parsed = parseHtml($scope.invoice.text.content);
|
||||
return Setting.update({ name: 'invoice_text' }, { value: parsed }, function (data) {
|
||||
$scope.invoice.text.content = parsed;
|
||||
return growl.success(_t('invoices.text_successfully_saved'));
|
||||
return growl.success(_t('app.admin.invoices.text_successfully_saved'));
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_saving_the_text'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_text'));
|
||||
return console.error(error);
|
||||
});
|
||||
};
|
||||
@ -476,10 +475,10 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
const parsed = parseHtml($scope.invoice.legals.content);
|
||||
return Setting.update({ name: 'invoice_legals' }, { value: parsed }, function (data) {
|
||||
$scope.invoice.legals.content = parsed;
|
||||
return growl.success(_t('invoices.address_and_legal_information_successfully_saved'));
|
||||
return growl.success(_t('app.admin.invoices.address_and_legal_information_successfully_saved'));
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_saving_the_address_and_the_legal_information'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_address_and_the_legal_information'));
|
||||
return console.error(error);
|
||||
});
|
||||
};
|
||||
@ -552,7 +551,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
$scope.save = function() {
|
||||
Setting.bulkUpdate(
|
||||
{ settings: Object.values($scope.settings) },
|
||||
function () { growl.success(_t('invoices.codes_customization_success')); },
|
||||
function () { growl.success(_t('app.admin.invoices.codes_customization_success')); },
|
||||
function (error) {
|
||||
growl.error('unexpected_error_occurred');
|
||||
console.error(error);
|
||||
@ -560,6 +559,126 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/invoices page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupInvoicesTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('invoices');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.invoices.welcome.title'),
|
||||
content: _t('app.admin.tour.invoices.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
if (!Fablab.withoutInvoices && $scope.invoices.length > 0) {
|
||||
uitour.createStep({
|
||||
selector: '.invoices-management .invoices-list',
|
||||
stepId: 'list',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.invoices.list.title'),
|
||||
content: _t('app.admin.tour.invoices.list.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.invoices-management .invoices-list .chained-indicator',
|
||||
stepId: 'chained',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.invoices.chained.title'),
|
||||
content: _t('app.admin.tour.invoices.chained.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.invoices-management .invoices-list .download-button',
|
||||
stepId: 'download',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.invoices.download.title'),
|
||||
content: _t('app.admin.tour.invoices.download.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.invoices-management .invoices-list .refund-button',
|
||||
stepId: 'refund',
|
||||
order: 4,
|
||||
title: _t('app.admin.tour.invoices.refund.title'),
|
||||
content: _t('app.admin.tour.invoices.refund.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
}
|
||||
uitour.createStep({
|
||||
selector: '.invoices-management .invoices-settings',
|
||||
stepId: 'settings',
|
||||
order: 5,
|
||||
title: _t('app.admin.tour.invoices.settings.title'),
|
||||
content: _t('app.admin.tour.invoices.settings.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.invoices-management .accounting-codes-tab',
|
||||
stepId: 'codes',
|
||||
order: 6,
|
||||
title: _t('app.admin.tour.invoices.codes.title'),
|
||||
content: _t('app.admin.tour.invoices.codes.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .export-accounting-button',
|
||||
stepId: 'export',
|
||||
order: 7,
|
||||
title: _t('app.admin.tour.invoices.export.title'),
|
||||
content: _t('app.admin.tour.invoices.export.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .close-accounting-periods-button',
|
||||
stepId: 'periods',
|
||||
order: 8,
|
||||
title: _t('app.admin.tour.invoices.periods.title'),
|
||||
content: _t('app.admin.tour.invoices.periods.content'),
|
||||
placement: 'bottom',
|
||||
popupClass: 'shift-left-50'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 9,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on step change, change the active tab if needed
|
||||
uitour.on('stepChanged', function (nextStep) {
|
||||
if (nextStep.stepId === 'list' || nextStep.stepId === 'settings') {
|
||||
$scope.tabs.active = 0;
|
||||
}
|
||||
if (nextStep.stepId === 'settings') {
|
||||
$scope.tabs.active = 1;
|
||||
}
|
||||
if (nextStep.stepId === 'codes' || nextStep.stepId === 'export') {
|
||||
$scope.tabs.active = 2;
|
||||
}
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('invoices') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'invoices' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('invoices') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -586,19 +705,24 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
};
|
||||
|
||||
// Watch the logo, when a change occurs, save it
|
||||
return $scope.$watch('invoice.logo', function () {
|
||||
$scope.$watch('invoice.logo', function () {
|
||||
if ($scope.invoice.logo && $scope.invoice.logo.filesize) {
|
||||
return Setting.update(
|
||||
{ name: 'invoice_logo' },
|
||||
{ value: $scope.invoice.logo.base64 },
|
||||
function (data) { growl.success(_t('invoices.logo_successfully_saved')); },
|
||||
function (data) { growl.success(_t('app.admin.invoices.logo_successfully_saved')); },
|
||||
function (error) {
|
||||
growl.error(_t('invoices.an_error_occurred_while_saving_the_logo'));
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_logo'));
|
||||
return console.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -666,6 +790,18 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('invoices');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
@ -696,17 +832,17 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
|
||||
|
||||
// Possible refunding methods
|
||||
$scope.avoirModes = [
|
||||
{ name: _t('invoices.none'), value: 'none' },
|
||||
{ name: _t('invoices.by_cash'), value: 'cash' },
|
||||
{ name: _t('invoices.by_cheque'), value: 'cheque' },
|
||||
{ name: _t('invoices.by_transfer'), value: 'transfer' },
|
||||
{ name: _t('invoices.by_wallet'), value: 'wallet' }
|
||||
{ name: _t('app.admin.invoices.none'), value: 'none' },
|
||||
{ name: _t('app.admin.invoices.by_cash'), value: 'cash' },
|
||||
{ name: _t('app.admin.invoices.by_cheque'), value: 'cheque' },
|
||||
{ name: _t('app.admin.invoices.by_transfer'), value: 'transfer' },
|
||||
{ name: _t('app.admin.invoices.by_wallet'), value: 'wallet' }
|
||||
];
|
||||
|
||||
// If a subscription was took with the current invoice, should it be canceled or not
|
||||
$scope.subscriptionExpireOptions = {};
|
||||
$scope.subscriptionExpireOptions[_t('yes')] = true;
|
||||
$scope.subscriptionExpireOptions[_t('no')] = false;
|
||||
$scope.subscriptionExpireOptions[_t('app.shared.buttons.yes')] = true;
|
||||
$scope.subscriptionExpireOptions[_t('app.shared.buttons.no')] = false;
|
||||
|
||||
// AngularUI-Bootstrap datepicker parameters to define when to refund
|
||||
$scope.datePicker = {
|
||||
@ -742,7 +878,7 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
|
||||
}
|
||||
|
||||
if ($scope.avoir.invoice_items_ids.length === 0) {
|
||||
return growl.error(_t('invoices.you_must_select_at_least_one_element_to_create_a_refund'));
|
||||
return growl.error(_t('app.admin.invoices.you_must_select_at_least_one_element_to_create_a_refund'));
|
||||
} else {
|
||||
return Invoice.save(
|
||||
{ avoir: $scope.avoir },
|
||||
@ -750,7 +886,7 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
|
||||
$uibModalInstance.close({ avoir, invoice: $scope.invoice });
|
||||
},
|
||||
function (err) { // failed
|
||||
growl.error(_t('invoices.unable_to_create_the_refund'));
|
||||
growl.error(_t('app.admin.invoices.unable_to_create_the_refund'));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -791,7 +927,7 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal
|
||||
});
|
||||
|
||||
if (invoice.stripe) {
|
||||
return $scope.avoirModes.push({ name: _t('invoices.online_payment'), value: 'stripe' });
|
||||
return $scope.avoirModes.push({ name: _t('app.admin.invoices.online_payment'), value: 'stripe' });
|
||||
}
|
||||
};
|
||||
|
||||
@ -861,16 +997,16 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('invoices.confirmation_required'),
|
||||
title: _t('app.admin.invoices.confirmation_required'),
|
||||
msg: $sce.trustAsHtml(
|
||||
_t(
|
||||
'invoices.confirm_close_START_END',
|
||||
{ START: moment.utc($scope.period.start_at).format('LL'), END: moment.utc($scope.period.end_at).format('LL') }
|
||||
)
|
||||
+ '<br/><br/><strong>'
|
||||
+ _t('invoices.period_must_match_fiscal_year')
|
||||
+ _t('app.admin.invoices.period_must_match_fiscal_year')
|
||||
+ '</strong><br/><br/>'
|
||||
+ _t('invoices.this_may_take_a_while')
|
||||
+ _t('app.admin.invoices.this_may_take_a_while')
|
||||
)
|
||||
};
|
||||
}
|
||||
@ -895,7 +1031,7 @@ Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$ui
|
||||
},
|
||||
function(error) {
|
||||
$scope.pendingCreation = false;
|
||||
growl.error(_t('invoices.failed_to_close_period'));
|
||||
growl.error(_t('app.admin.invoices.failed_to_close_period'));
|
||||
$scope.errors = error.data;
|
||||
}
|
||||
);
|
||||
@ -982,7 +1118,7 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope',
|
||||
|
||||
Export.status(statusQry).then(function (res) {
|
||||
if (!res.data.exists) {
|
||||
growl.success(_t('invoices.export_is_running'));
|
||||
growl.success(_t('app.admin.invoices.export_is_running'));
|
||||
}
|
||||
$uibModalInstance.close(res);
|
||||
});
|
||||
|
@ -126,8 +126,8 @@ class MembersController {
|
||||
/**
|
||||
* Controller used in the members/groups management page
|
||||
*/
|
||||
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export',
|
||||
function ($scope, $sce, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member, Export) {
|
||||
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export', 'uiTourService',
|
||||
function ($scope, $sce, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member, Export, uiTourService) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// number of users loaded each time we click on 'load more...'
|
||||
@ -146,7 +146,15 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
// currently displayed page of members
|
||||
page: 1,
|
||||
// true when all members where loaded
|
||||
noMore: false
|
||||
noMore: false,
|
||||
// default filter for members
|
||||
memberFilter: 'all',
|
||||
// options for members filtering
|
||||
memberFilters: [
|
||||
'all',
|
||||
'not_confirmed',
|
||||
'inactive_for_3_years'
|
||||
]
|
||||
};
|
||||
|
||||
// admins list
|
||||
@ -155,6 +163,9 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
// Admins ordering/sorting. Default: not sorted
|
||||
$scope.orderAdmin = null;
|
||||
|
||||
// default tab: members list
|
||||
$scope.tabs = { active: 0 };
|
||||
|
||||
/**
|
||||
* Change the members ordering criterion to the one provided
|
||||
* @param orderBy {string} ordering criterion
|
||||
@ -182,6 +193,35 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ask for confirmation then delete the specified user
|
||||
* @param memberId {number} identifier of the user to delete
|
||||
*/
|
||||
$scope.deleteMember = function(memberId) {
|
||||
dialogs.confirm(
|
||||
{
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('app.admin.members.confirmation_required'),
|
||||
msg: $sce.trustAsHtml(_t('app.admin.members.confirm_delete_member') + '<br/><br/>' + _t('app.admin.members.this_may_take_a_while_please_wait'))
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () { // cancel confirmed
|
||||
Member.delete(
|
||||
{ id: memberId },
|
||||
function () {
|
||||
$scope.members.splice(findItemIdxById($scope.members, memberId), 1);
|
||||
return growl.success(_t('app.admin.members.member_successfully_deleted'));
|
||||
},
|
||||
function (error) { growl.error(_t('app.admin.members.unable_to_delete_the_member')); }
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for confirmation then delete the specified administrator
|
||||
* @param admins {Array} full list of administrators
|
||||
@ -193,8 +233,8 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: $sce.trustAsHtml(_t('do_you_really_want_to_delete_this_administrator_this_cannot_be_undone') + '<br/><br/>' + _t('this_may_take_a_while_please_wait'))
|
||||
title: _t('app.admin.members.confirmation_required'),
|
||||
msg: $sce.trustAsHtml(_t('app.admin.members.do_you_really_want_to_delete_this_administrator_this_cannot_be_undone') + '<br/><br/>' + _t('app.admin.members.this_may_take_a_while_please_wait'))
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -203,10 +243,10 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
Admin.delete(
|
||||
{ id: admin.id },
|
||||
function () {
|
||||
admins.splice(findAdminIdxById(admins, admin.id), 1);
|
||||
return growl.success(_t('administrator_successfully_deleted'));
|
||||
admins.splice(findItemIdxById(admins, admin.id), 1);
|
||||
return growl.success(_t('app.admin.members.administrator_successfully_deleted'));
|
||||
},
|
||||
function (error) { growl.error(_t('unable_to_delete_the_administrator')); }
|
||||
function (error) { growl.error(_t('app.admin.members.unable_to_delete_the_administrator')); }
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -232,6 +272,14 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
}, 300);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback when the member filter changes: reload the search results
|
||||
*/
|
||||
$scope.updateMemberFilter = function () {
|
||||
resetSearchMember();
|
||||
memberSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to alert the admin that the export request was acknowledged and is
|
||||
* processing right now.
|
||||
@ -239,11 +287,154 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
$scope.alertExport = function (type) {
|
||||
Export.status({ category: 'users', type }).then(function (res) {
|
||||
if (!res.data.exists) {
|
||||
return growl.success(_t('export_is_running_you_ll_be_notified_when_its_ready'));
|
||||
return growl.success(_t('app.admin.members.export_is_running_you_ll_be_notified_when_its_ready'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/members page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupMembersTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('members');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.members.welcome.title'),
|
||||
content: _t('app.admin.tour.members.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.members-management .members-list',
|
||||
stepId: 'list',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.members.list.title'),
|
||||
content: _t('app.admin.tour.members.list.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.members-management .search-members',
|
||||
stepId: 'search',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.members.search.title'),
|
||||
content: _t('app.admin.tour.members.search.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.members-management .filter-members',
|
||||
stepId: 'filter',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.members.filter.title'),
|
||||
content: _t('app.admin.tour.members.filter.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
if ($scope.members.length > 0) {
|
||||
uitour.createStep({
|
||||
selector: '.members-management .members-list .buttons',
|
||||
stepId: 'actions',
|
||||
order: 4,
|
||||
title: _t('app.admin.tour.members.actions.title'),
|
||||
content: _t('app.admin.tour.members.actions.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
}
|
||||
uitour.createStep({
|
||||
selector: '.members-management .exports-buttons',
|
||||
stepId: 'exports',
|
||||
order: 5,
|
||||
title: _t('app.admin.tour.members.exports.title'),
|
||||
content: _t('app.admin.tour.members.exports.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .import-members',
|
||||
stepId: 'import',
|
||||
order: 6,
|
||||
title: _t('app.admin.tour.members.import.title'),
|
||||
content: _t('app.admin.tour.members.import.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.members-management .admins-tab',
|
||||
stepId: 'admins',
|
||||
order: 7,
|
||||
title: _t('app.admin.tour.members.admins.title'),
|
||||
content: _t('app.admin.tour.members.admins.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.members-management .groups-tab',
|
||||
stepId: 'groups',
|
||||
order: 8,
|
||||
title: _t('app.admin.tour.members.groups.title'),
|
||||
content: _t('app.admin.tour.members.groups.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.members-management .labels-tab',
|
||||
stepId: 'labels',
|
||||
order: 9,
|
||||
title: _t('app.admin.tour.members.labels.title'),
|
||||
content: _t('app.admin.tour.members.labels.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.members-management .sso-tab',
|
||||
stepId: 'sso',
|
||||
order: 10,
|
||||
title: _t('app.admin.tour.members.sso.title'),
|
||||
content: _t('app.admin.tour.members.sso.content'),
|
||||
placement: 'bottom',
|
||||
popupClass: 'shift-left-50'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 11,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on step change, change the active tab if needed
|
||||
uitour.on('stepChanged', function (nextStep) {
|
||||
if (nextStep.stepId === 'list' || nextStep.stepId === 'import') {
|
||||
$scope.tabs.active = 0;
|
||||
}
|
||||
if (nextStep.stepId === 'admins') {
|
||||
$scope.tabs.active = 1;
|
||||
}
|
||||
if (nextStep.stepId === 'groups') {
|
||||
$scope.tabs.active = 2;
|
||||
}
|
||||
if (nextStep.stepId === 'labels') {
|
||||
$scope.tabs.active = 3;
|
||||
}
|
||||
if (nextStep.stepId === 'sso') {
|
||||
$scope.tabs.active = 4;
|
||||
}
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('members') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'members' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('members') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -253,6 +444,9 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
if (!membersPromise[0] || (membersPromise[0].maxMembers <= $scope.members.length)) {
|
||||
return $scope.member.noMore = true;
|
||||
}
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -261,13 +455,13 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
var searchTimeout = null;
|
||||
|
||||
/**
|
||||
* Iterate through the provided array and return the index of the requested admin
|
||||
* @param admins {Array} full list of users with role 'admin'
|
||||
* @param id {Number} user id of the admin to retrieve in the list
|
||||
* @returns {Number} index of the requested admin, in the provided array
|
||||
* Iterate through the provided array and return the index of the requested item
|
||||
* @param items {Array} full list of users with role 'admin'
|
||||
* @param id {Number} id of the item to retrieve in the list
|
||||
* @returns {Number} index of the requested item, in the provided array
|
||||
*/
|
||||
var findAdminIdxById = function (admins, id) {
|
||||
return (admins.map(function (admin) { return admin.id; })).indexOf(id);
|
||||
var findItemIdxById = function (items, id) {
|
||||
return (items.map(function (item) { return item.id; })).indexOf(id);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -288,6 +482,7 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
query: {
|
||||
search: $scope.member.searchText,
|
||||
order_by: $scope.member.order,
|
||||
filter: $scope.member.memberFilter,
|
||||
page: $scope.member.page,
|
||||
size: USERS_PER_PAGE
|
||||
}
|
||||
@ -304,6 +499,18 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('members');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
@ -395,10 +602,10 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
{ id: subscription.id },
|
||||
{ subscription: { expired_at: $scope.new_expired_at, free } },
|
||||
function (_subscription) {
|
||||
growl.success(_t('you_successfully_changed_the_expiration_date_of_the_user_s_subscription'));
|
||||
growl.success(_t('app.admin.members_edit.you_successfully_changed_the_expiration_date_of_the_user_s_subscription'));
|
||||
return $uibModalInstance.close(_subscription);
|
||||
},
|
||||
function (error) { growl.error(_t('a_problem_occurred_while_saving_the_date')); }
|
||||
function (error) { growl.error(_t('app.admin.members_edit.a_problem_occurred_while_saving_the_date')); }
|
||||
);
|
||||
};
|
||||
|
||||
@ -442,12 +649,12 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
$scope.ok = function () {
|
||||
$scope.subscription.user_id = user.id;
|
||||
return Subscription.save({ }, { subscription: $scope.subscription }, function (_subscription) {
|
||||
growl.success(_t('subscription_successfully_purchased'));
|
||||
growl.success(_t('app.admin.members_edit.subscription_successfully_purchased'));
|
||||
$uibModalInstance.close(_subscription);
|
||||
return $state.reload();
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('a_problem_occurred_while_taking_the_subscription'));
|
||||
growl.error(_t('app.admin.members_edit.a_problem_occurred_while_taking_the_subscription'));
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
@ -458,7 +665,7 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
}]
|
||||
});
|
||||
// once the form was validated succesfully ...
|
||||
// once the form was validated successfully ...
|
||||
return modalInstance.result.then(function (subscription) { $scope.subscription = subscription; });
|
||||
};
|
||||
|
||||
@ -507,11 +714,11 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
avoir_description: $scope.description
|
||||
},
|
||||
function (_wallet) {
|
||||
growl.success(_t('wallet_credit_successfully'));
|
||||
growl.success(_t('app.shared.wallet.wallet_credit_successfully'));
|
||||
return $uibModalInstance.close(_wallet);
|
||||
},
|
||||
function (error) {
|
||||
growl.error(_t('a_problem_occurred_for_wallet_credit'));
|
||||
growl.error(_t('app.shared.wallet.a_problem_occurred_for_wallet_credit'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
@ -721,11 +928,12 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
|
||||
{},
|
||||
{ admin: $scope.admin },
|
||||
function () {
|
||||
growl.success(_t('administrator_successfully_created_he_will_receive_his_connection_directives_by_email', { GENDER: getGender($scope.admin) }, 'messageformat'));
|
||||
growl.success(_t('app.admin.admins_new.administrator_successfully_created_he_will_receive_his_connection_directives_by_email', { GENDER: getGender($scope.admin) }));
|
||||
return $state.go('app.admin.members');
|
||||
}
|
||||
, function (error) {
|
||||
console.log(error);
|
||||
growl.error(_t('app.admin.admins_new.failed_to_create_admin') + JSON.stringify(error.data ? error.data : error));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -10,9 +10,9 @@
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clientsPromise', 'growl', 'OpenAPIClient', 'dialogs', '_t',
|
||||
function ($scope, clientsPromise, growl, OpenAPIClient, dialogs, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clientsPromise', 'growl', 'OpenAPIClient', 'dialogs', '_t', 'Member', 'uiTourService',
|
||||
function ($scope, clientsPromise, growl, OpenAPIClient, dialogs, _t, Member, uiTourService) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// clients list
|
||||
$scope.clients = clientsPromise;
|
||||
@ -37,12 +37,12 @@ Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clien
|
||||
if (client.id != null) {
|
||||
OpenAPIClient.update({ id: client.id }, { open_api_client: client }, function (clientResp) {
|
||||
client = clientResp;
|
||||
return growl.success(_t('client_successfully_updated'));
|
||||
return growl.success(_t('app.admin.open_api_clients.client_successfully_updated'));
|
||||
});
|
||||
} else {
|
||||
OpenAPIClient.save({ open_api_client: client }, function (client) {
|
||||
$scope.clients.push(client);
|
||||
return growl.success(_t('client_successfully_created'));
|
||||
return growl.success(_t('app.admin.open_api_clients.client_successfully_created'));
|
||||
});
|
||||
}
|
||||
|
||||
@ -61,8 +61,8 @@ Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clien
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_open_api_client')
|
||||
title: _t('app.admin.open_api_clients.confirmation_required'),
|
||||
msg: _t('app.admin.open_api_clients.do_you_really_want_to_delete_this_open_api_client')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -70,17 +70,17 @@ Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clien
|
||||
, () =>
|
||||
OpenAPIClient.delete({ id: $scope.clients[index].id }, function () {
|
||||
$scope.clients.splice(index, 1);
|
||||
return growl.success(_t('client_successfully_deleted'));
|
||||
return growl.success(_t('app.admin.open_api_clients.client_successfully_deleted'));
|
||||
})
|
||||
);
|
||||
|
||||
return $scope.resetToken = client =>
|
||||
$scope.resetToken = client =>
|
||||
dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_revoke_this_open_api_access')
|
||||
title: _t('app.admin.open_api_clients.confirmation_required'),
|
||||
msg: _t('app.admin.open_api_clients.do_you_really_want_to_revoke_this_open_api_access')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -88,9 +88,85 @@ Application.Controllers.controller('OpenAPIClientsController', ['$scope', 'clien
|
||||
, () =>
|
||||
OpenAPIClient.resetToken({ id: client.id }, {}, function (clientResp) {
|
||||
client.token = clientResp.token;
|
||||
return growl.success(_t('access_successfully_revoked'));
|
||||
return growl.success(_t('app.admin.open_api_clients.access_successfully_revoked'));
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/open_api_clients page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupOpenAPITour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('open-api');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.open_api.welcome.title'),
|
||||
content: _t('app.admin.tour.open_api.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .documentation-button',
|
||||
stepId: 'doc',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.open_api.doc.title'),
|
||||
content: _t('app.admin.tour.open_api.doc.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('open-api') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'open-api' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, and if the display behavior is not configured to manual triggering only, show the tour now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('open-api') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('open-api');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
|
||||
]);
|
||||
|
@ -121,7 +121,7 @@ Application.Controllers.controller('NewPlanController', ['$scope', '$uibModal',
|
||||
$uibModalInstance.close($scope.partner);
|
||||
},
|
||||
function (error) {
|
||||
growl.error(_t('new_plan.unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name'));
|
||||
growl.error(_t('app.admin.plans.new.unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
@ -143,9 +143,9 @@ Application.Controllers.controller('NewPlanController', ['$scope', '$uibModal',
|
||||
*/
|
||||
$scope.afterSubmit = function (content) {
|
||||
if ((content.id == null) && (content.plan_ids == null)) {
|
||||
return growl.error(_t('new_plan.unable_to_create_the_subscription_please_try_again'));
|
||||
return growl.error(_t('app.admin.plans.new.unable_to_create_the_subscription_please_try_again'));
|
||||
} else {
|
||||
growl.success(_t('new_plan.successfully_created_subscriptions_dont_forget_to_redefine_prices'));
|
||||
growl.success(_t('app.admin.plans.new.successfully_created_subscriptions_dont_forget_to_redefine_prices'));
|
||||
if (content.plan_ids != null) {
|
||||
return $state.go('app.admin.pricing');
|
||||
} else {
|
||||
@ -237,9 +237,9 @@ Application.Controllers.controller('EditPlanController', ['$scope', 'groups', 'p
|
||||
*/
|
||||
$scope.afterSubmit = function (content) {
|
||||
if ((content.id == null) && (content.plan_ids == null)) {
|
||||
return growl.error(_t('edit_plan.unable_to_save_subscription_changes_please_try_again'));
|
||||
return growl.error(_t('app.admin.plans.edit.unable_to_save_subscription_changes_please_try_again'));
|
||||
} else {
|
||||
growl.success(_t('edit_plan.subscription_successfully_changed'));
|
||||
growl.success(_t('app.admin.plans.edit.subscription_successfully_changed'));
|
||||
return $state.go('app.admin.pricing');
|
||||
}
|
||||
};
|
||||
|
@ -18,9 +18,10 @@
|
||||
/**
|
||||
* Controller used in the prices edition page
|
||||
*/
|
||||
Application.Controllers.controller('EditPricingController', ['$scope', '$state', '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', '_t',
|
||||
function ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
Application.Controllers.controller('EditPricingController', ['$scope', '$state', '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', '_t', 'Member', 'uiTourService',
|
||||
function ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, _t, Member, uiTourService) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// List of machines prices (not considering any plan)
|
||||
$scope.machinesPrices = machinesPricesPromise;
|
||||
|
||||
@ -84,7 +85,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
|
||||
// Default: we do not filter coupons
|
||||
$scope.filter = {
|
||||
coupon: 'all',
|
||||
coupon: 'all'
|
||||
};
|
||||
|
||||
// Available status for filtering coupons
|
||||
@ -96,6 +97,16 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
'active'
|
||||
];
|
||||
|
||||
// default tab: plans list
|
||||
$scope.tabs = { active: 0 };
|
||||
|
||||
/**
|
||||
* Retrieve a training price from all the trainings prices
|
||||
* @param trainingsPricings {Array<Object>} all trainings prices
|
||||
* @param trainingId {number}
|
||||
* @param groupId {number}
|
||||
* @returns {float}
|
||||
*/
|
||||
$scope.findTrainingsPricing = function (trainingsPricings, trainingId, groupId) {
|
||||
for (let trainingsPricing of Array.from(trainingsPricings)) {
|
||||
if ((trainingsPricing.training_id === trainingId) && (trainingsPricing.group_id === groupId)) {
|
||||
@ -104,11 +115,17 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the price of a training for the given parameters
|
||||
* @param data {float} the new price
|
||||
* @param trainingsPricing {Object} the training pricing to update
|
||||
* @returns {Promise|string}
|
||||
*/
|
||||
$scope.updateTrainingsPricing = function (data, trainingsPricing) {
|
||||
if (data != null) {
|
||||
return TrainingsPricing.update({ id: trainingsPricing.id }, { trainings_pricing: { amount: data } }).$promise;
|
||||
} else {
|
||||
return _t('pricing.please_specify_a_number');
|
||||
return _t('app.admin.pricing.please_specify_a_number');
|
||||
}
|
||||
};
|
||||
|
||||
@ -146,7 +163,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
*/
|
||||
$scope.showTrainings = function (trainings) {
|
||||
if (!angular.isArray(trainings) || !(trainings.length > 0)) {
|
||||
return _t('pricing.none');
|
||||
return _t('app.admin.pricing.none');
|
||||
}
|
||||
|
||||
const selected = [];
|
||||
@ -155,7 +172,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
return selected.push(t.name);
|
||||
}
|
||||
});
|
||||
if (selected.length) { return selected.join(' | '); } else { return _t('pricing.none'); }
|
||||
if (selected.length) { return selected.join(' | '); } else { return _t('app.admin.pricing.none'); }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -170,7 +187,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
{ training_credit_nb: newdata.training_credits }
|
||||
, angular.noop() // do nothing in case of success
|
||||
, function (error) {
|
||||
growl.error(_t('pricing.an_error_occurred_while_saving_the_number_of_credits'));
|
||||
growl.error(_t('app.admin.pricing.an_error_occurred_while_saving_the_number_of_credits'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
@ -190,11 +207,11 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
return $scope.trainingCreditsGroups[planId].splice($scope.trainingCreditsGroups[planId].indexOf(tc.id), 1);
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('pricing.an_error_occurred_while_deleting_credit_with_the_TRAINING', { TRAINING: tc.creditable.name }));
|
||||
growl.error(_t('app.admin.pricing.an_error_occurred_while_deleting_credit_with_the_TRAINING', { TRAINING: tc.creditable.name }));
|
||||
console.error(error);
|
||||
});
|
||||
} else {
|
||||
return growl.error(_t('pricing.an_error_occurred_unable_to_find_the_credit_to_revoke'));
|
||||
return growl.error(_t('app.admin.pricing.an_error_occurred_unable_to_find_the_credit_to_revoke'));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -215,7 +232,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
}
|
||||
, function (error) { // failed
|
||||
const training = getTrainingFromId(newTrainingId);
|
||||
growl.error(_t('pricing.an_error_occurred_while_creating_credit_with_the_TRAINING', { TRAINING: training.name }));
|
||||
growl.error(_t('app.admin.pricing.an_error_occurred_while_creating_credit_with_the_TRAINING', { TRAINING: training.name }));
|
||||
return console.error(error);
|
||||
});
|
||||
}
|
||||
@ -250,7 +267,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
* @returns {String}
|
||||
*/
|
||||
$scope.showCreditableName = function (credit) {
|
||||
let selected = _t('pricing.not_set');
|
||||
let selected = _t('app.admin.pricing.not_set');
|
||||
if (credit && credit.creditable_id) {
|
||||
const object = $scope.getCreditable(credit);
|
||||
selected = object.name;
|
||||
@ -295,7 +312,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
$scope.saveMachineCredit = function (data, id) {
|
||||
for (let mc of Array.from($scope.machineCredits)) {
|
||||
if ((mc.plan_id === data.plan_id) && (mc.creditable_id === data.creditable_id) && ((id === null) || (mc.id !== id))) {
|
||||
growl.error(_t('pricing.error_a_credit_linking_this_machine_with_that_subscription_already_exists'));
|
||||
growl.error(_t('app.admin.pricing.error_a_credit_linking_this_machine_with_that_subscription_already_exists'));
|
||||
if (!id) {
|
||||
$scope.machineCredits.pop();
|
||||
}
|
||||
@ -304,18 +321,18 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
return Credit.update({ id }, { credit: data }, function () { growl.success(_t('pricing.changes_have_been_successfully_saved')); });
|
||||
return Credit.update({ id }, { credit: data }, function () { growl.success(_t('app.admin.pricing.changes_have_been_successfully_saved')); });
|
||||
} else {
|
||||
data.creditable_type = 'Machine';
|
||||
return Credit.save(
|
||||
{ credit: data }
|
||||
, function (resp) {
|
||||
$scope.machineCredits[$scope.machineCredits.length - 1].id = resp.id;
|
||||
return growl.success(_t('pricing.credit_was_successfully_saved'));
|
||||
return growl.success(_t('app.admin.pricing.credit_was_successfully_saved'));
|
||||
}
|
||||
, function (err) {
|
||||
$scope.machineCredits.pop();
|
||||
growl.error(_t('pricing.error_creating_credit'));
|
||||
growl.error(_t('app.admin.pricing.error_creating_credit'));
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
@ -365,7 +382,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
$scope.saveSpaceCredit = function (data, id) {
|
||||
for (let sc of Array.from($scope.spaceCredits)) {
|
||||
if ((sc.plan_id === data.plan_id) && (sc.creditable_id === data.creditable_id) && ((id === null) || (sc.id !== id))) {
|
||||
growl.error(_t('pricing.error_a_credit_linking_this_space_with_that_subscription_already_exists'));
|
||||
growl.error(_t('app.admin.pricing.error_a_credit_linking_this_space_with_that_subscription_already_exists'));
|
||||
if (!id) {
|
||||
$scope.spaceCredits.pop();
|
||||
}
|
||||
@ -374,18 +391,18 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
return Credit.update({ id }, { credit: data }, function () { growl.success(_t('pricing.changes_have_been_successfully_saved')); });
|
||||
return Credit.update({ id }, { credit: data }, function () { growl.success(_t('app.admin.pricing.changes_have_been_successfully_saved')); });
|
||||
} else {
|
||||
data.creditable_type = 'Space';
|
||||
return Credit.save(
|
||||
{ credit: data }
|
||||
, function (resp) {
|
||||
$scope.spaceCredits[$scope.spaceCredits.length - 1].id = resp.id;
|
||||
return growl.success(_t('pricing.credit_was_successfully_saved'));
|
||||
return growl.success(_t('app.admin.pricing.credit_was_successfully_saved'));
|
||||
}
|
||||
, function (err) {
|
||||
$scope.spaceCredits.pop();
|
||||
return growl.error(_t('pricing.error_creating_credit'));
|
||||
return growl.error(_t('app.admin.pricing.error_creating_credit'));
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -419,8 +436,8 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
*/
|
||||
$scope.getPlanType = function (type) {
|
||||
if (type === 'PartnerPlan') {
|
||||
return _t('pricing.partner');
|
||||
} else { return _t('pricing.standard'); }
|
||||
return _t('app.admin.pricing.partner');
|
||||
} else { return _t('app.admin.pricing.standard'); }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -453,7 +470,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
if (data != null) {
|
||||
return Price.update({ id: price.id }, { price: { amount: data } }).$promise;
|
||||
} else {
|
||||
return _t('pricing.please_specify_a_number');
|
||||
return _t('app.admin.pricing.please_specify_a_number');
|
||||
}
|
||||
};
|
||||
|
||||
@ -471,8 +488,8 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('pricing.confirmation_required'),
|
||||
msg: _t('pricing.do_you_really_want_to_delete_this_subscription_plan')
|
||||
title: _t('app.admin.pricing.confirmation_required'),
|
||||
msg: _t('app.admin.pricing.do_you_really_want_to_delete_this_subscription_plan')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -482,12 +499,12 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
Plan.delete(
|
||||
{ id },
|
||||
function (res) {
|
||||
growl.success(_t('pricing.subscription_plan_was_successfully_deleted'));
|
||||
growl.success(_t('app.admin.pricing.subscription_plan_was_successfully_deleted'));
|
||||
return $scope.plans.splice(findItemIdxById(plans, id), 1);
|
||||
},
|
||||
function (error) {
|
||||
if (error.statusText) { console.error(`[EditPricingController::deletePlan] Error: ${error.statusText}`); }
|
||||
growl.error(_t('pricing.unable_to_delete_the_specified_subscription_an_error_occurred'));
|
||||
growl.error(_t('app.admin.pricing.unable_to_delete_the_specified_subscription_an_error_occurred'));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -519,8 +536,8 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('pricing.confirmation_required'),
|
||||
msg: _t('pricing.do_you_really_want_to_delete_this_coupon')
|
||||
title: _t('app.admin.pricing.confirmation_required'),
|
||||
msg: _t('app.admin.pricing.do_you_really_want_to_delete_this_coupon')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -528,16 +545,16 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
, function () {
|
||||
// the admin has confirmed, delete the coupon
|
||||
Coupon.delete({ id }, function (res) {
|
||||
growl.success(_t('coupon_was_successfully_deleted'));
|
||||
growl.success(_t('app.admin.pricing.coupon_was_successfully_deleted'));
|
||||
return $scope.coupons.splice(findItemIdxById(coupons, id), 1);
|
||||
}
|
||||
|
||||
, function (error) {
|
||||
if (error.statusText) { console.error(`[EditPricingController::deleteCoupon] Error: ${error.statusText}`); }
|
||||
if (error.status === 422) {
|
||||
return growl.error(_t('pricing.unable_to_delete_the_specified_coupon_already_in_use'));
|
||||
return growl.error(_t('app.admin.pricing.unable_to_delete_the_specified_coupon_already_in_use'));
|
||||
} else {
|
||||
return growl.error(_t('pricing.unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'));
|
||||
return growl.error(_t('app.admin.pricing.unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -566,11 +583,11 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
// Callback to validate sending of the coupon
|
||||
$scope.ok = function () {
|
||||
Coupon.send({ coupon_code: coupon.code, user_id: $scope.ctrl.member.id }, function (res) {
|
||||
growl.success(_t('pricing.coupon_successfully_sent_to_USER', { USER: $scope.ctrl.member.name }));
|
||||
growl.success(_t('app.admin.pricing.coupon_successfully_sent_to_USER', { USER: $scope.ctrl.member.name }));
|
||||
return $uibModalInstance.close({ user_id: $scope.ctrl.member.id });
|
||||
}
|
||||
, function (err) {
|
||||
growl.error(_t('pricing.an_error_occurred_unable_to_send_the_coupon'));
|
||||
growl.error(_t('app.admin.pricing.an_error_occurred_unable_to_send_the_coupon'));
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
@ -600,6 +617,107 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/pricing page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupPricingTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('pricing');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.pricing.welcome.title'),
|
||||
content: _t('app.admin.tour.pricing.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.plans-pricing .new-plan-button',
|
||||
stepId: 'new_plan',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.pricing.new_plan.title'),
|
||||
content: _t('app.admin.tour.pricing.new_plan.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.plans-pricing .trainings-tab',
|
||||
stepId: 'trainings',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.pricing.trainings.title'),
|
||||
content: _t('app.admin.tour.pricing.trainings.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.plans-pricing .machines-tab',
|
||||
stepId: 'machines',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.pricing.machines.title'),
|
||||
content: _t('app.admin.tour.pricing.machines.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
if (!Fablab.withoutSpaces) {
|
||||
uitour.createStep({
|
||||
selector: '.plans-pricing .spaces-tab',
|
||||
stepId: 'spaces',
|
||||
order: 4,
|
||||
title: _t('app.admin.tour.pricing.spaces.title'),
|
||||
content: _t('app.admin.tour.pricing.spaces.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
}
|
||||
uitour.createStep({
|
||||
selector: '.plans-pricing .credits-tab',
|
||||
stepId: 'credits',
|
||||
order: 5,
|
||||
title: _t('app.admin.tour.pricing.credits.title'),
|
||||
content: _t('app.admin.tour.pricing.credits.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.plans-pricing .coupons-tab',
|
||||
stepId: 'coupons',
|
||||
order: 6,
|
||||
title: _t('app.admin.tour.pricing.coupons.title'),
|
||||
content: _t('app.admin.tour.pricing.coupons.content'),
|
||||
placement: 'bottom',
|
||||
popupClass: 'shift-left-50'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 7,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on step change, change the active tab if needed
|
||||
uitour.on('stepChanged', function (nextStep) {
|
||||
if (nextStep.stepId === 'new_plan') { $scope.tabs.active = 0; }
|
||||
if (nextStep.stepId === 'trainings') { $scope.tabs.active = 1; }
|
||||
if (nextStep.stepId === 'machines') { $scope.tabs.active = 2; }
|
||||
if (nextStep.stepId === 'spaces') { $scope.tabs.active = 3; }
|
||||
if (nextStep.stepId === 'credits') { $scope.tabs.active = 4; }
|
||||
if (nextStep.stepId === 'coupons') { $scope.tabs.active = 5; }
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('pricing') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'pricing' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('pricing') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -608,6 +726,11 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
const initialize = function () {
|
||||
$scope.trainingCreditsGroups = groupCreditsByPlan($scope.trainingCredits);
|
||||
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
|
||||
// adds empty array for plan which hasn't any credits yet
|
||||
return (function () {
|
||||
const result = [];
|
||||
@ -622,6 +745,18 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('pricing');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve an item index by its ID from the given array of objects
|
||||
* @param items {Array<{id:number}>}
|
||||
|
@ -12,9 +12,9 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('ProjectElementsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise',
|
||||
function ($scope, $state, Component, Licence, Theme, componentsPromise, licencesPromise, themesPromise) {
|
||||
// Materials list (plastic, wood ...)
|
||||
Application.Controllers.controller('ProjectElementsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise', '_t', 'Member', 'uiTourService',
|
||||
function ($scope, $state, Component, Licence, Theme, componentsPromise, licencesPromise, themesPromise, _t, Member, uiTourService) {
|
||||
// Materials list (plastic, wood ...)
|
||||
$scope.components = componentsPromise;
|
||||
|
||||
// Licences list (Creative Common ...)
|
||||
@ -149,12 +149,89 @@ Application.Controllers.controller('ProjectElementsController', ['$scope', '$sta
|
||||
* @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||
* @param index {number} licence index in the $scope.licences array
|
||||
*/
|
||||
return $scope.cancelLicence = function (rowform, index) {
|
||||
$scope.cancelLicence = function (rowform, index) {
|
||||
if ($scope.licences[index].id != null) {
|
||||
return rowform.$cancel();
|
||||
} else {
|
||||
return $scope.licences.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/project_elements page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupProjectElementsTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('project-elements');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.project_elements.welcome.title'),
|
||||
content: _t('app.admin.tour.project_elements.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .abuses-button',
|
||||
stepId: 'abuses',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.project_elements.abuses.title'),
|
||||
content: _t('app.admin.tour.project_elements.abuses.content'),
|
||||
placement: 'bottom',
|
||||
popupClass: 'shift-left-40'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('project-elements') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'project-elements' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('project-elements') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('project-elements');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
]);
|
||||
|
@ -12,8 +12,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('SettingsController', ['$scope', '$filter', '$uibModal', 'Setting', 'growl', 'settingsPromise', 'privacyDraftsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'profileImageFile', 'CSRF', '_t',
|
||||
function ($scope, $filter, $uibModal, Setting, growl, settingsPromise, privacyDraftsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, profileImageFile, CSRF, _t) {
|
||||
Application.Controllers.controller('SettingsController', ['$scope', '$rootScope', '$filter', '$uibModal', 'dialogs', 'Setting', 'growl', 'settingsPromise', 'privacyDraftsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'profileImageFile', 'CSRF', '_t', 'Member', 'uiTourService',
|
||||
function ($scope, $rootScope, $filter, $uibModal, dialogs, Setting, growl, settingsPromise, privacyDraftsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, profileImageFile, CSRF, _t, Member, uiTourService) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// timepickers steps configuration
|
||||
@ -48,16 +48,22 @@ Application.Controllers.controller('SettingsController', ['$scope', '$filter', '
|
||||
cgv: false
|
||||
};
|
||||
|
||||
// default tab: general
|
||||
$scope.tabs = { active: 0 };
|
||||
|
||||
// full history of privacy policy drafts
|
||||
$scope.privacyDraftsHistory = [];
|
||||
|
||||
// various configurable settings
|
||||
$scope.twitterSetting = { name: 'twitter_name', value: settingsPromise.twitter_name };
|
||||
$scope.linkName = { name: 'link_name', value: settingsPromise.link_name };
|
||||
$scope.aboutTitleSetting = { name: 'about_title', value: settingsPromise.about_title };
|
||||
$scope.aboutBodySetting = { name: 'about_body', value: settingsPromise.about_body };
|
||||
$scope.privacyDpoSetting = { name: 'privacy_dpo', value: settingsPromise.privacy_dpo };
|
||||
$scope.aboutContactsSetting = { name: 'about_contacts', value: settingsPromise.about_contacts };
|
||||
$scope.homeBlogpostSetting = { name: 'home_blogpost', value: settingsPromise.home_blogpost };
|
||||
$scope.homeContent = { name: 'home_content', value: settingsPromise.home_content };
|
||||
$scope.homeCss = { name: 'home_css', value: settingsPromise.home_css };
|
||||
$scope.machineExplicationsAlert = { name: 'machine_explications_alert', value: settingsPromise.machine_explications_alert };
|
||||
$scope.trainingExplicationsAlert = { name: 'training_explications_alert', value: settingsPromise.training_explications_alert };
|
||||
$scope.trainingInformationMessage = { name: 'training_information_message', value: settingsPromise.training_information_message };
|
||||
@ -123,12 +129,48 @@ Application.Controllers.controller('SettingsController', ['$scope', '$filter', '
|
||||
value: (settingsPromise.display_name_enable === 'true')
|
||||
};
|
||||
|
||||
$scope.fabAnalytics = {
|
||||
name: 'fab_analytics',
|
||||
value: (settingsPromise.fab_analytics === 'true')
|
||||
};
|
||||
|
||||
// By default, we display the currently published privacy policy
|
||||
$scope.privacyPolicy = {
|
||||
version: null,
|
||||
bodyTemp: settingsPromise.privacy_body
|
||||
};
|
||||
|
||||
// Extend the options for summernote editor, with special tools for home page
|
||||
$scope.summernoteOptsHomePage = angular.copy($rootScope.summernoteOpts);
|
||||
$scope.summernoteOptsHomePage.toolbar[5][1].push('nugget'); // toolbar -> insert -> nugget
|
||||
$scope.summernoteOptsHomePage.nugget = {
|
||||
label: '\uF12E',
|
||||
tooltip: _t('app.admin.settings.home_items'),
|
||||
list: [
|
||||
`<div id="news">${_t('app.admin.settings.item_news')}</div>`,
|
||||
`<div id="projects">${_t('app.admin.settings.item_projects')}</div>`,
|
||||
`<div id="twitter">${_t('app.admin.settings.item_twitter')}</div>`,
|
||||
`<div id="members">${_t('app.admin.settings.item_members')}</div>`,
|
||||
`<div id="events">${_t('app.admin.settings.item_events')}</div>`
|
||||
]
|
||||
}
|
||||
$scope.summernoteOptsHomePage.height = 400;
|
||||
|
||||
// codemirror editor
|
||||
$scope.codeMirrorEditor = null;
|
||||
|
||||
// Options for codemirror editor, used for custom css
|
||||
$scope.codemirrorOpts = {
|
||||
matchBrackets : true,
|
||||
lineNumbers: true,
|
||||
mode: 'sass'
|
||||
}
|
||||
|
||||
// Show or hide advanced settings
|
||||
$scope.advancedSettings = {
|
||||
open: false
|
||||
}
|
||||
|
||||
/**
|
||||
* For use with 'ng-class', returns the CSS class name for the uploads previews.
|
||||
* The preview may show a placeholder or the content of the file depending on the upload state.
|
||||
@ -166,7 +208,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$filter', '
|
||||
Setting.update(
|
||||
{ name: setting.name },
|
||||
{ value },
|
||||
function () { growl.success(_t('settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`settings.${setting.name}`) })); },
|
||||
function () { growl.success(_t('app.admin.settings.customization_of_SETTING_successfully_saved', { SETTING: _t(`app.admin.settings.${setting.name}`) })); },
|
||||
function (error) { console.log(error); }
|
||||
);
|
||||
};
|
||||
@ -191,7 +233,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$filter', '
|
||||
// reset history
|
||||
$scope.privacyDraftsHistory = [];
|
||||
data.setting.history.forEach(function (draft) {
|
||||
$scope.privacyDraftsHistory.push({ id: draft.id, name: _t('settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: draft.created_at }), content: draft.value });
|
||||
$scope.privacyDraftsHistory.push({ id: draft.id, name: _t('app.admin.settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: draft.created_at }), content: draft.value });
|
||||
});
|
||||
if (type === 'privacy_draft') {
|
||||
const orderedHistory = $filter('orderBy')(data.setting.history, 'created_at');
|
||||
@ -219,7 +261,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$filter', '
|
||||
angular.forEach(v, function(err) { growl.error(err); })
|
||||
});
|
||||
} else {
|
||||
growl.success(_t('settings.file_successfully_updated'));
|
||||
growl.success(_t('app.admin.settings.file_successfully_updated'));
|
||||
if (content.custom_asset.name === 'cgu-file') {
|
||||
$scope.cguFile = content.custom_asset;
|
||||
$scope.methods.cgu = 'put';
|
||||
@ -273,6 +315,162 @@ Application.Controllers.controller('SettingsController', ['$scope', '$filter', '
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a modal showing a sample of the collected data if FabAnalytics is enabled
|
||||
*/
|
||||
$scope.analyticsModal = function() {
|
||||
$uibModal.open({
|
||||
templateUrl: '<%= asset_path "admin/settings/analyticsModal.html" %>',
|
||||
controller: 'AnalyticsModalController',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
analyticsData: ['FabAnalytics', function (FabAnalytics) { return FabAnalytics.data().$promise; }]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the home page to its initial state (factory value)
|
||||
*/
|
||||
$scope.resetHomePage = function () {
|
||||
dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('app.admin.settings.confirmation_required'),
|
||||
msg: _t('app.admin.settings.confirm_reset_home_page')
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
, function () { // confirmed
|
||||
Setting.reset({ name: 'home_content' }, function (data) {
|
||||
$scope.homeContent.value = data.value;
|
||||
growl.success(_t('app.admin.settings.home_content_reset'));
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered when the codemirror editor is loaded into the DOM
|
||||
* @param editor codemirror instance
|
||||
*/
|
||||
$scope.codemirrorLoaded = function (editor) {
|
||||
$scope.codeMirrorEditor = editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/settings page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupSettingsTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('settings');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.settings.welcome.title'),
|
||||
content: _t('app.admin.tour.settings.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .home-page-content h4',
|
||||
stepId: 'home',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.settings.home.title'),
|
||||
content: _t('app.admin.tour.settings.home.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .home-page-content .note-toolbar .note-insert div',
|
||||
stepId: 'components',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.settings.components.title'),
|
||||
content: _t('app.admin.tour.settings.components.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .home-page-content .note-toolbar .btn-codeview',
|
||||
stepId: 'codeview',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.settings.codeview.title'),
|
||||
content: _t('app.admin.tour.settings.codeview.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .reset-button',
|
||||
stepId: 'reset',
|
||||
order: 4,
|
||||
title: _t('app.admin.tour.settings.reset.title'),
|
||||
content: _t('app.admin.tour.settings.reset.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .home-page-style',
|
||||
stepId: 'css',
|
||||
order: 5,
|
||||
title: _t('app.admin.tour.settings.css.title'),
|
||||
content: _t('app.admin.tour.settings.css.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .about-page-tab',
|
||||
stepId: 'about',
|
||||
order: 6,
|
||||
title: _t('app.admin.tour.settings.about.title'),
|
||||
content: _t('app.admin.tour.settings.about.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .privacy-page-tab',
|
||||
stepId: 'privacy',
|
||||
order: 7,
|
||||
title: _t('app.admin.tour.settings.privacy.title'),
|
||||
content: _t('app.admin.tour.settings.privacy.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.admin-settings .history-select',
|
||||
stepId: 'draft',
|
||||
order: 8,
|
||||
title: _t('app.admin.tour.settings.draft.title'),
|
||||
content: _t('app.admin.tour.settings.draft.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 9,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on step change, change the active tab if needed
|
||||
uitour.on('stepChanged', function (nextStep) {
|
||||
if (nextStep.stepId === 'home' || nextStep.stepId === 'css') { $scope.tabs.active = 1; }
|
||||
if (nextStep.stepId === 'about') { $scope.tabs.active = 2; }
|
||||
if (nextStep.stepId === 'privacy' || nextStep.stepId === 'draft') { $scope.tabs.active = 3; }
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('settings') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'settings' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('settings') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -316,10 +514,30 @@ Application.Controllers.controller('SettingsController', ['$scope', '$filter', '
|
||||
}
|
||||
|
||||
privacyDraftsPromise.setting.history.forEach(function (draft) {
|
||||
$scope.privacyDraftsHistory.push({ id: draft.id, name: _t('settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: moment(draft.created_at).format('L LT') }), content: draft.value });
|
||||
$scope.privacyDraftsHistory.push({ id: draft.id, name: _t('app.admin.settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: moment(draft.created_at).format('L LT') }), content: draft.value });
|
||||
});
|
||||
|
||||
// refresh codemirror to display the fetched setting
|
||||
$scope.$watch('advancedSettings.open', function (newValue) {
|
||||
if (newValue) $scope.codeMirrorEditor.refresh();
|
||||
})
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('settings');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
// init the controller (call at the end !)
|
||||
return initialize();
|
||||
}
|
||||
@ -347,7 +565,7 @@ Application.Controllers.controller('SavePolicyController', ['$scope', '$uibModal
|
||||
*/
|
||||
$scope.publish = function () {
|
||||
saveCb({ name: 'privacy_body', value: privacyPolicy.bodyTemp });
|
||||
growl.info(_t('settings.privacy.users_notified'));
|
||||
growl.info(_t('app.admin.settings.privacy.users_notified'));
|
||||
$uibModalInstance.close('privacy_body');
|
||||
};
|
||||
/**
|
||||
@ -358,3 +576,18 @@ Application.Controllers.controller('SavePolicyController', ['$scope', '$uibModal
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller used in the "what do we collect?" modal, about FabAnalytics
|
||||
*/
|
||||
Application.Controllers.controller('AnalyticsModalController', ['$scope', '$uibModalInstance', 'analyticsData',
|
||||
function ($scope,$uibModalInstance, analyticsData) {
|
||||
// analytics data sample
|
||||
$scope.data = analyticsData;
|
||||
|
||||
// callback to close the modal
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss();
|
||||
}
|
||||
}
|
||||
])
|
||||
|
@ -15,8 +15,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('StatisticsController', ['$scope', '$state', '$rootScope', '$uibModal', 'es', 'Member', '_t', 'membersPromise', 'statisticsPromise',
|
||||
function ($scope, $state, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise) {
|
||||
Application.Controllers.controller('StatisticsController', ['$scope', '$state', '$rootScope', '$uibModal', 'es', 'Member', '_t', 'membersPromise', 'statisticsPromise', 'uiTourService',
|
||||
function ($scope, $state, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise, uiTourService) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// search window size
|
||||
@ -130,6 +130,13 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a localized name for the given field
|
||||
*/
|
||||
$scope.customFieldName = function (field) {
|
||||
return _t(`app.admin.statistics.${field}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to open the datepicker (interval start)
|
||||
* @param $event {Object} jQuery event object
|
||||
@ -174,11 +181,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
if (tab.table) {
|
||||
if ((tab.es_type_key === 'subscription') && $rootScope.fablabWithoutPlans) {
|
||||
return true;
|
||||
} else if ((tab.es_type_key === 'space') && $rootScope.fablabWithoutSpaces) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else return (tab.es_type_key === 'space') && $rootScope.fablabWithoutSpaces;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
@ -215,10 +218,10 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
*/
|
||||
$scope.formatSex = function (sex) {
|
||||
if (sex === 'male') {
|
||||
return _t('man');
|
||||
return _t('app.admin.statistics.man');
|
||||
}
|
||||
if (sex === 'female') {
|
||||
return _t('woman');
|
||||
return _t('app.admin.statistics.woman');
|
||||
}
|
||||
};
|
||||
|
||||
@ -335,6 +338,63 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
.result['finally'](null).then(function (info) { console.log(info); });
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/statistics page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupStatisticsTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('statistics');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.statistics.welcome.title'),
|
||||
content: _t('app.admin.tour.statistics.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .export-button',
|
||||
stepId: 'export',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.statistics.export.title'),
|
||||
content: _t('app.admin.tour.statistics.export.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.heading .charts-button',
|
||||
stepId: 'trending',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.statistics.trending.title'),
|
||||
content: _t('app.admin.tour.statistics.trending.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('statistics') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'statistics' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('statistics') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -348,6 +408,22 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
return $scope.preventRefresh = true;
|
||||
}
|
||||
});
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('statistics');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -355,7 +431,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
* @param $event {Object} jQuery event object
|
||||
* @param datePicker {Object} settings object of the concerned datepicker. Must have an 'opened' property
|
||||
*/
|
||||
var toggleDatePicker = function ($event, datePicker) {
|
||||
const toggleDatePicker = function ($event, datePicker) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
return datePicker.opened = !datePicker.opened;
|
||||
@ -364,8 +440,8 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
/**
|
||||
* Force update the statistics table, querying elasticSearch according to the current config values
|
||||
*/
|
||||
var refreshStats = function () {
|
||||
if ($scope.selectedIndex && !$scope.preventRefresh) {
|
||||
const refreshStats = function () {
|
||||
if ($scope.selectedIndex && !$scope.preventRefresh && $scope.type.active) {
|
||||
$scope.data = [];
|
||||
$scope.sumCA = 0;
|
||||
$scope.averageAge = 0;
|
||||
@ -397,14 +473,14 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
};
|
||||
|
||||
/**
|
||||
* Run the elasticSearch query to retreive the /stats/type aggregations
|
||||
* Run the elasticSearch query to retrieve the /stats/type aggregations
|
||||
* @param index {String} elasticSearch document type (account|event|machine|project|subscription|training)
|
||||
* @param type {String} statistics type (month|year|booking|hour|user|project)
|
||||
* @param custom {{key:{string}, value:{string}}|null} custom filter property or null to disable this filter
|
||||
* @param callback {function} function be to run after results were retrieved, it will receive
|
||||
* two parameters : results {Object}, error {String} (if any)
|
||||
*/
|
||||
var queryElasticStats = function (index, type, custom, callback) {
|
||||
const queryElasticStats = function (index, type, custom, callback) {
|
||||
// handle invalid callback
|
||||
if (typeof (callback) !== 'function') {
|
||||
console.error('[statisticsController::queryElasticStats] Error: invalid callback provided');
|
||||
@ -443,7 +519,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
* @param intervalEnd {moment} statitics interval ending (moment.js type)
|
||||
* @param sortings {Array|null} elasticSearch criteria for sorting the results
|
||||
*/
|
||||
var buildElasticDataQuery = function (type, custom, ageMin, ageMax, intervalBegin, intervalEnd, sortings) {
|
||||
const buildElasticDataQuery = function (type, custom, ageMin, ageMax, intervalBegin, intervalEnd, sortings) {
|
||||
const q = {
|
||||
'query': {
|
||||
'bool': {
|
||||
@ -518,7 +594,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
* @param custom {Object} if custom is empty or undefined, an empty string will be returned
|
||||
* @returns {{match:*}|string}
|
||||
*/
|
||||
var buildElasticCustomCriterion = function (custom) {
|
||||
const buildElasticCustomCriterion = function (custom) {
|
||||
if (custom) {
|
||||
const criterion = {
|
||||
'match': {}
|
||||
@ -539,7 +615,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
* Parse the provided criteria array and return the corresponding elasticSearch syntax
|
||||
* @param criteria {Array} array of {key_to_sort:order}
|
||||
*/
|
||||
var buildElasticSortCriteria = function (criteria) {
|
||||
const buildElasticSortCriteria = function (criteria) {
|
||||
const crits = [];
|
||||
angular.forEach(criteria, function (value, key) {
|
||||
if ((typeof value !== 'undefined') && (value !== null) && (value !== 'none')) {
|
||||
@ -552,22 +628,27 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
};
|
||||
|
||||
/**
|
||||
* Fullfil the list of available options in the custom filter panel. The list will be based on common
|
||||
* Fulfill the list of available options in the custom filter panel. The list will be based on common
|
||||
* properties and on index-specific properties (additional_fields)
|
||||
*/
|
||||
var buildCustomFiltersList = function () {
|
||||
const buildCustomFiltersList = function () {
|
||||
$scope.filters = [
|
||||
{ key: 'date', label: _t('date'), values: ['input_date'] },
|
||||
{ key: 'userId', label: _t('user_id'), values: ['input_number'] },
|
||||
{ key: 'gender', label: _t('gender'), values: [{ key: 'male', label: _t('man') }, { key: 'female', label: _t('woman') }] },
|
||||
{ key: 'age', label: _t('age'), values: ['input_number'] },
|
||||
{ key: 'subType', label: _t('type'), values: $scope.type.active.subtypes },
|
||||
{ key: 'ca', label: _t('revenue'), values: ['input_number'] }
|
||||
{ key: 'date', label: _t('app.admin.statistics.date'), values: ['input_date'] },
|
||||
{ key: 'userId', label: _t('app.admin.statistics.user_id'), values: ['input_number'] },
|
||||
{ key: 'gender', label: _t('app.admin.statistics.gender'), values: [{ key: 'male', label: _t('app.admin.statistics.man') }, { key: 'female', label: _t('app.admin.statistics.woman') }] },
|
||||
{ key: 'age', label: _t('app.admin.statistics.age'), values: ['input_number'] },
|
||||
{ key: 'ca', label: _t('app.admin.statistics.revenue'), values: ['input_number'] }
|
||||
];
|
||||
|
||||
if (!$scope.type.active.simple) {
|
||||
const f = { key: 'stat', label: $scope.type.active.label, values: ['input_number'] };
|
||||
$scope.filters.push(f);
|
||||
// if no plans were created, there's no types for statisticIndex=subscriptions
|
||||
if ($scope.type.active) {
|
||||
$scope.filters.splice(4, 0, { key: 'subType', label: _t('app.admin.statistics.type'), values: $scope.type.active.subtypes })
|
||||
|
||||
|
||||
if (!$scope.type.active.simple) {
|
||||
const f = { key: 'stat', label: $scope.type.active.label, values: ['input_number'] };
|
||||
$scope.filters.push(f);
|
||||
}
|
||||
}
|
||||
|
||||
return angular.forEach($scope.selectedIndex.additional_fields, function (field) {
|
||||
@ -588,7 +669,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
|
||||
* Build and return an object according to the custom filter set by the user, used to request elasticsearch
|
||||
* @return {Object|null}
|
||||
*/
|
||||
var buildCustomFilterQuery = function () {
|
||||
const buildCustomFilterQuery = function () {
|
||||
let custom = null;
|
||||
if (!angular.isUndefinedOrNull($scope.customFilter.criterion) &&
|
||||
!angular.isUndefinedOrNull($scope.customFilter.criterion.key) &&
|
||||
@ -709,7 +790,7 @@ Application.Controllers.controller('ExportStatisticsController', [ '$scope', '$u
|
||||
|
||||
Export.status(statusQry).then(function (res) {
|
||||
if (!res.data.exists) {
|
||||
return growl.success(_t('export_is_running_you_ll_be_notified_when_its_ready'));
|
||||
return growl.success(_t('app.admin.statistics.export_is_running_you_ll_be_notified_when_its_ready'));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
Application.Controllers.controller('TagsController', ['$scope', 'tagsPromise', 'Tag', 'growl', '_t', function ($scope, tagsPromise, Tag, growl, _t) {
|
||||
Application.Controllers.controller('TagsController', ['$scope', 'tagsPromise', 'Tag', 'dialogs', 'growl', '_t', function ($scope, tagsPromise, Tag, dialogs, growl, _t) {
|
||||
// List of users's tags
|
||||
$scope.tags = tagsPromise;
|
||||
|
||||
@ -44,15 +44,15 @@ Application.Controllers.controller('TagsController', ['$scope', 'tagsPromise', '
|
||||
*/
|
||||
$scope.saveTag = function (data, id) {
|
||||
if (id != null) {
|
||||
return Tag.update({ id }, { tag: data }, response => growl.success(_t('changes_successfully_saved'))
|
||||
, error => growl.error(_t('an_error_occurred_while_saving_changes')));
|
||||
return Tag.update({ id }, { tag: data }, response => growl.success(_t('app.admin.members.tag_form.changes_successfully_saved'))
|
||||
, error => growl.error(_t('app.admin.members.tag_form.an_error_occurred_while_saving_changes')));
|
||||
} else {
|
||||
return Tag.save({ tag: data }, function (resp) {
|
||||
growl.success(_t('new_tag_successfully_saved'));
|
||||
growl.success(_t('app.admin.members.tag_form.new_tag_successfully_saved'));
|
||||
return $scope.tags[$scope.tags.length - 1].id = resp.id;
|
||||
}
|
||||
, function (error) {
|
||||
growl.error(_t('an_error_occurred_while_saving_the_new_tag'));
|
||||
growl.error(_t('app.admin.members.tag_form.an_error_occurred_while_saving_the_new_tag'));
|
||||
return $scope.tags.splice($scope.tags.length - 1, 1);
|
||||
});
|
||||
}
|
||||
@ -62,13 +62,24 @@ Application.Controllers.controller('TagsController', ['$scope', 'tagsPromise', '
|
||||
* Deletes the tag at the specified index
|
||||
* @param index {number} tag index in the $scope.tags array
|
||||
*/
|
||||
return $scope.removeTag = index =>
|
||||
// TODO add confirmation : les utilisateurs seront déasociés
|
||||
Tag.delete({ id: $scope.tags[index].id }, function (resp) {
|
||||
growl.success(_t('tag_successfully_deleted'));
|
||||
return $scope.tags.splice(index, 1);
|
||||
$scope.removeTag = index =>
|
||||
dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('app.admin.members.tag_form.confirmation_required'),
|
||||
msg: _t('app.admin.members.tag_form.confirm_delete_tag_html')
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
, error => growl.error(_t('an_error_occurred_and_the_tag_deletion_failed')));
|
||||
, () => {
|
||||
Tag.delete({ id: $scope.tags[index].id }, function (resp) {
|
||||
growl.success(_t('app.admin.members.tag_form.tag_successfully_deleted'));
|
||||
return $scope.tags.splice(index, 1);
|
||||
}
|
||||
, error => growl.error(_t('app.admin.members.tag_form.an_error_occurred_and_the_tag_deletion_failed')));
|
||||
});
|
||||
}
|
||||
|
||||
]);
|
||||
|
@ -150,22 +150,19 @@ Application.Controllers.controller('EditTrainingController', [ '$scope', '$state
|
||||
/**
|
||||
* Controller used in the trainings management page, allowing admins users to see and manage the list of trainings and reservations.
|
||||
*/
|
||||
Application.Controllers.controller('TrainingsAdminController', ['$scope', '$state', '$uibModal', 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl', 'dialogs',
|
||||
function ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl, dialogs) {
|
||||
/* PUBLIC SCOPE */
|
||||
Application.Controllers.controller('TrainingsAdminController', ['$scope', '$state', '$uibModal', 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl', 'dialogs', 'Member', 'uiTourService',
|
||||
function ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl, dialogs, Member, uiTourService) {
|
||||
|
||||
// list of trainings
|
||||
let groupAvailabilities;
|
||||
$scope.trainings = trainingsPromise;
|
||||
|
||||
// simplified list of machines
|
||||
$scope.machines = machinesPromise;
|
||||
|
||||
// Training to monitor, binded with drop-down selection
|
||||
$scope.monitoring =
|
||||
{ training: null };
|
||||
// Training to monitor, bound with drop-down selection
|
||||
$scope.monitoring = { training: null };
|
||||
|
||||
// list of training availabilies, grouped by date
|
||||
// list of training availabilities, grouped by date
|
||||
$scope.groupedAvailabilities = {};
|
||||
|
||||
// default: accordions are not open
|
||||
@ -184,6 +181,9 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
|
||||
'all'
|
||||
];
|
||||
|
||||
// default tab: trainings list
|
||||
$scope.tabs = { active: 0 };
|
||||
|
||||
/**
|
||||
* In the trainings listing tab, return the stringified list of machines associated with the provided training
|
||||
* @param training {Object} Training object, inherited from $resource
|
||||
@ -196,7 +196,7 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
|
||||
return selected.push(m.name);
|
||||
}
|
||||
});
|
||||
if (selected.length) { return selected.join(', '); } else { return _t('none'); }
|
||||
if (selected.length) { return selected.join(', '); } else { return _t('app.admin.trainings.none'); }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -276,8 +276,8 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_training')
|
||||
title: _t('app.admin.trainings.confirmation_required'),
|
||||
msg: _t('app.admin.trainings.do_you_really_want_to_delete_this_training')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -285,10 +285,10 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
|
||||
function () { // deletion confirmed
|
||||
training.$delete(function () {
|
||||
$scope.trainings.splice(index, 1);
|
||||
growl.info(_t('training_successfully_deleted'));
|
||||
growl.info(_t('app.admin.trainings.training_successfully_deleted'));
|
||||
},
|
||||
function (error) {
|
||||
growl.warning(_t('unable_to_delete_the_training_because_some_users_alredy_booked_it'));
|
||||
growl.warning(_t('app.admin.trainings.unable_to_delete_the_training_because_some_users_already_booked_it'));
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
@ -335,14 +335,107 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the admin/trainings page.
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupTrainingsTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('trainings');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.admin.tour.trainings.welcome.title'),
|
||||
content: _t('app.admin.tour.trainings.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.trainings-monitoring .manage-trainings',
|
||||
stepId: 'trainings',
|
||||
order: 1,
|
||||
title: _t('app.admin.tour.trainings.trainings.title'),
|
||||
content: _t('app.admin.tour.trainings.trainings.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.trainings-monitoring .filter-trainings',
|
||||
stepId: 'filter',
|
||||
order: 2,
|
||||
title: _t('app.admin.tour.trainings.filter.title'),
|
||||
content: _t('app.admin.tour.trainings.filter.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.trainings-monitoring .post-tracking',
|
||||
stepId: 'tracking',
|
||||
order: 3,
|
||||
title: _t('app.admin.tour.trainings.tracking.title'),
|
||||
content: _t('app.admin.tour.trainings.tracking.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 4,
|
||||
title: _t('app.admin.tour.conclusion.title'),
|
||||
content: _t('app.admin.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on step change, change the active tab if needed
|
||||
uitour.on('stepChanged', function (nextStep) {
|
||||
if (nextStep.stepId === 'filter' || nextStep.stepId === 'machines') { $scope.tabs.active = 0; }
|
||||
if (nextStep.stepId === 'tracking') { $scope.tabs.active = 1; }
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('trainings') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'trainings' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('trainings') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('trainings');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Group the trainings availabilities by trainings and by dates and return the resulting tree
|
||||
* @param trainings {Array} $scope.trainings is expected here
|
||||
* @returns {Object} Tree constructed as /training_name/year/month/day/[availabilities]
|
||||
*/
|
||||
return groupAvailabilities = function (trainings) {
|
||||
const groupAvailabilities = function (trainings) {
|
||||
const tree = {};
|
||||
for (let training of Array.from(trainings)) {
|
||||
tree[training.name] = {};
|
||||
@ -367,6 +460,9 @@ Application.Controllers.controller('TrainingsAdminController', ['$scope', '$stat
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
|
||||
]);
|
||||
|
@ -12,8 +12,8 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
|
||||
Application.Controllers.controller('ApplicationController', ['$rootScope', '$scope', '$window', '$locale', 'Session', 'AuthService', 'Auth', '$uibModal', '$state', 'growl', 'Notification', '$interval', 'Setting', '_t', 'Version',
|
||||
function ($rootScope, $scope, $window, $locale, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version) {
|
||||
Application.Controllers.controller('ApplicationController', ['$rootScope', '$scope', '$window', '$locale', '$timeout', 'Session', 'AuthService', 'Auth', '$uibModal', '$state', 'growl', 'Notification', '$interval', 'Setting', '_t', 'Version',
|
||||
function ($rootScope, $scope, $window, $locale, $timeout, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// User's notifications will get refreshed every 30s
|
||||
@ -23,7 +23,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
|
||||
// Fab-manager's version
|
||||
$scope.version =
|
||||
{ version: '' };
|
||||
{ current: '' };
|
||||
|
||||
// currency symbol for the current locale (cf. angular-i18n)
|
||||
$rootScope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
@ -37,11 +37,11 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
$rootScope.currentUser = user;
|
||||
Session.create(user);
|
||||
getNotifications();
|
||||
// fab-manager's app-version
|
||||
// Fab-manager's app-version
|
||||
if (user.role === 'admin') {
|
||||
return $scope.version = Version.get();
|
||||
} else {
|
||||
return $scope.version = { version: '' };
|
||||
return $scope.version = { current: '' };
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -149,7 +149,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
$uibModalInstance.close(user);
|
||||
} else {
|
||||
// the user was not saved in database, something wrong occurred
|
||||
growl.error(_t('unexpected_error_occurred'));
|
||||
growl.error(_t('app.public.common.unexpected_error_occurred'));
|
||||
}
|
||||
}, function (error) {
|
||||
// creation failed...
|
||||
@ -169,7 +169,12 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
}]
|
||||
}).result['finally'](null).then(function (user) {
|
||||
// when the account was created successfully, set the session to the newly created account
|
||||
$scope.setCurrentUser(user);
|
||||
if(Fablab.userConfirmationNeededToSignIn) {
|
||||
Auth._currentUser = null;
|
||||
growl.info(_t('app.public.common.you_will_receive_confirmation_instructions_by_email_detailed'));
|
||||
} else {
|
||||
$scope.setCurrentUser(user);
|
||||
}
|
||||
});
|
||||
<% end %>
|
||||
};
|
||||
@ -204,7 +209,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
};
|
||||
}]
|
||||
}).result['finally'](null).then(function () {
|
||||
growl.success(_t('your_password_was_successfully_changed'));
|
||||
growl.success(_t('app.public.common.your_password_was_successfully_changed'));
|
||||
return Auth.login().then(function (user) {
|
||||
$scope.setCurrentUser(user);
|
||||
}, function (error) {
|
||||
@ -262,6 +267,36 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
toggler.toggleClass('active');
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the modal dialog showing that an upgrade is available
|
||||
*/
|
||||
$scope.versionModal = function() {
|
||||
if ($scope.version.up_to_date) return;
|
||||
if ($rootScope.currentUser.role !== 'admin') return;
|
||||
|
||||
$uibModal.open({
|
||||
templateUrl: '<%= asset_path "admin/versions/upgradeModal.html" %>',
|
||||
controller: 'VersionModalController',
|
||||
resolve: {
|
||||
version() { return $scope.version; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the contextual help "feature tour".
|
||||
* @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
*/
|
||||
$scope.help = function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// we wrap the event triggering into a $timeout to prevent conflicting with current $apply
|
||||
$timeout(function () {
|
||||
var evt = new KeyboardEvent('keydown', { key: 'F1' });
|
||||
window.dispatchEvent(evt);
|
||||
});
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
@ -299,8 +334,11 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
// we stop polling notifications when the page is not in foreground
|
||||
onPageVisible(function (state) { $rootScope.toCheckNotifications = (state === 'visible'); });
|
||||
|
||||
Setting.get({ name: 'fablab_name' }, function (data) { $scope.fablabName = data.setting.value; });
|
||||
Setting.get({ name: 'name_genre' }, function (data) { $scope.nameGenre = data.setting.value; });
|
||||
Setting.query({ names: "['fablab_name', 'name_genre', 'link_name']" }, function (settings) {
|
||||
$scope.fablabName = settings.fablab_name;
|
||||
$scope.nameGenre = settings.name_genre;
|
||||
$scope.linkName = settings.link_name;
|
||||
});
|
||||
|
||||
// shorthands
|
||||
$scope.isAuthenticated = Auth.isAuthenticated;
|
||||
@ -309,10 +347,10 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreive once the notifications from the server and display a message popup for each new one.
|
||||
* Retrieve once the notifications from the server and display a message popup for each new one.
|
||||
* Then, periodically check for new notifications.
|
||||
*/
|
||||
var getNotifications = function () {
|
||||
const getNotifications = function () {
|
||||
$rootScope.toCheckNotifications = true;
|
||||
if (!$rootScope.checkNotificationsIsInit && !!$rootScope.currentUser) {
|
||||
setTimeout(function () {
|
||||
@ -325,7 +363,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
angular.forEach(notifications.notifications, function (n) { toDisplay.push(n); });
|
||||
|
||||
if (toDisplay.length < notifications.totals.unread) {
|
||||
toDisplay.push({ message: { description: _t('and_NUMBER_other_notifications', { NUMBER: notifications.totals.unread - toDisplay.length }, 'messageformat') } });
|
||||
toDisplay.push({ message: { description: _t('app.public.common.and_NUMBER_other_notifications', { NUMBER: notifications.totals.unread - toDisplay.length }) } });
|
||||
}
|
||||
|
||||
angular.forEach(toDisplay, function (notification) { growl.info(notification.message.description); });
|
||||
@ -353,7 +391,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
/**
|
||||
* Open the modal window allowing the user to log in.
|
||||
*/
|
||||
var openLoginModal = function (toState, toParams, callback) {
|
||||
const openLoginModal = function (toState, toParams, callback) {
|
||||
<% active_provider = AuthProvider.active %>
|
||||
<% if active_provider.providable_type != DatabaseProvider.name %>
|
||||
$window.location.href = '/sso-redirect';
|
||||
@ -375,7 +413,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
console.error(`Authentication failed: ${JSON.stringify(error)}`);
|
||||
$scope.alerts = [];
|
||||
return $scope.alerts.push({
|
||||
msg: _t('wrong_email_or_password'),
|
||||
msg: error.data.error,
|
||||
type: 'danger'
|
||||
});
|
||||
});
|
||||
@ -388,6 +426,11 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
return $uibModalInstance.dismiss('signup');
|
||||
};
|
||||
|
||||
$scope.openConfirmationNewModal = function(e) {
|
||||
e.preventDefault();
|
||||
return $uibModalInstance.dismiss('confirmationNew');
|
||||
};
|
||||
|
||||
return $scope.openResetPassword = function (e) {
|
||||
e.preventDefault();
|
||||
return $uibModalInstance.dismiss('resetPassword');
|
||||
@ -418,13 +461,31 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
$scope.alerts = [];
|
||||
return $http.post('/users/password.json', { user: $scope.user }).then(function () { $uibModalInstance.close(); }).catch(function () {
|
||||
$scope.alerts.push({
|
||||
msg: _t('your_email_address_is_unknown'),
|
||||
msg: _t('app.public.common.your_email_address_is_unknown'),
|
||||
type: 'danger'
|
||||
});
|
||||
});
|
||||
};
|
||||
}]
|
||||
}).result['finally'](null).then(function () { growl.info(_t('you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password')); });
|
||||
}).result['finally'](null).then(function () { growl.info(_t('app.public.common.you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password')); });
|
||||
} else if (reason === 'confirmationNew') {
|
||||
// open the 'reset password' modal
|
||||
return $uibModal.open({
|
||||
templateUrl: '<%= asset_path "shared/ConfirmationNewModal.html" %>',
|
||||
size: 'sm',
|
||||
controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) {
|
||||
$scope.user = { email: '' };
|
||||
return $scope.submitConfirmationNewForm = function () {
|
||||
$scope.alerts = [];
|
||||
return $http.post('/users/confirmation.json', { user: $scope.user }).then(function () { $uibModalInstance.close(); }).catch(function (res) {
|
||||
$scope.alerts.push({
|
||||
msg: res.data.errors.email[0],
|
||||
type: 'danger'
|
||||
});
|
||||
});
|
||||
};
|
||||
}]
|
||||
}).result['finally'](null).then(function () { growl.info(_t('app.public.common.you_will_receive_confirmation_instructions_by_email_detailed')); });
|
||||
}
|
||||
});
|
||||
// otherwise the user just closed the modal
|
||||
@ -437,7 +498,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
* When the status changes, the callback is triggered with the new status as parameter
|
||||
* Inspired by http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active#answer-1060034
|
||||
*/
|
||||
var onPageVisible = function (callback) {
|
||||
const onPageVisible = function (callback) {
|
||||
let hidden = 'hidden';
|
||||
|
||||
const onchange = function (evt) {
|
||||
@ -485,3 +546,16 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
return initialize();
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller used in the modal showing details about the version and the upgrades
|
||||
*/
|
||||
Application.Controllers.controller('VersionModalController', ['$scope', '$uibModalInstance', 'version', function ($scope, $uibModalInstance, version) {
|
||||
// version infos (current version + upgrade infos from hub)
|
||||
$scope.version = version;
|
||||
|
||||
// callback to close the modal
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss();
|
||||
}
|
||||
}]);
|
||||
|
@ -16,8 +16,8 @@
|
||||
* Controller used in the public calendar global
|
||||
*/
|
||||
|
||||
Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise',
|
||||
function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) {
|
||||
Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'iCalendarPromise',
|
||||
function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, iCalendarPromise) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
let currentMachineEvent = null;
|
||||
machinesPromise.forEach(m => m.checked = true);
|
||||
@ -38,6 +38,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
// List of spaces
|
||||
$scope.spaces = spacesPromise.filter(t => !t.disabled);
|
||||
|
||||
// List of external iCalendar sources
|
||||
$scope.externals = iCalendarPromise.map(e => Object.assign(e, { checked: true }));
|
||||
|
||||
// add availabilities source to event sources
|
||||
$scope.eventSources = [];
|
||||
|
||||
@ -48,10 +51,41 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
trainings: isSelectAll('trainings', scope),
|
||||
machines: isSelectAll('machines', scope),
|
||||
spaces: isSelectAll('spaces', scope),
|
||||
externals: isSelectAll('externals', scope),
|
||||
evt: filter.evt,
|
||||
dispo: filter.dispo
|
||||
});
|
||||
return $scope.calendarConfig.events = availabilitySourceUrl();
|
||||
$scope.calendarConfig.events = availabilitySourceUrl();
|
||||
// external iCalendar events sources
|
||||
$scope.externals.forEach(e => {
|
||||
if (e.checked) {
|
||||
if (!$scope.eventSources.some(evt => evt.id === e.id)) {
|
||||
$scope.eventSources.push({
|
||||
id: e.id,
|
||||
url: `/api/i_calendar/${e.id}/events`,
|
||||
textColor: e.text_color || '#000',
|
||||
color: e.color
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if ($scope.eventSources.some(evt => evt.id === e.id)) {
|
||||
const idx = $scope.eventSources.findIndex(evt => evt.id === e.id);
|
||||
$scope.eventSources.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('refetchEventSources');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a CSS-like style of the given calendar configuration
|
||||
* @param calendar
|
||||
*/
|
||||
$scope.calendarStyle = function (calendar) {
|
||||
return {
|
||||
'border-color': calendar.color,
|
||||
'color': calendar.text_color
|
||||
};
|
||||
};
|
||||
|
||||
// a variable for formation/machine/event/dispo checkbox is or not checked
|
||||
@ -59,6 +93,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
trainings: isSelectAll('trainings', $scope),
|
||||
machines: isSelectAll('machines', $scope),
|
||||
spaces: isSelectAll('spaces', $scope),
|
||||
externals: isSelectAll('externals', $scope),
|
||||
evt: true,
|
||||
dispo: true
|
||||
};
|
||||
@ -85,6 +120,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
spaces () {
|
||||
return $scope.spaces;
|
||||
},
|
||||
externals () {
|
||||
return $scope.externals;
|
||||
},
|
||||
filter () {
|
||||
return $scope.filter;
|
||||
},
|
||||
@ -95,10 +133,11 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
return $scope.filterAvailabilities;
|
||||
}
|
||||
},
|
||||
controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'filter', 'toggleFilter', 'filterAvailabilities', function ($scope, $uibModalInstance, trainings, machines, spaces, filter, toggleFilter, filterAvailabilities) {
|
||||
controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'externals', 'filter', 'toggleFilter', 'filterAvailabilities', function ($scope, $uibModalInstance, trainings, machines, spaces, externals, filter, toggleFilter, filterAvailabilities) {
|
||||
$scope.trainings = trainings;
|
||||
$scope.machines = machines;
|
||||
$scope.spaces = spaces;
|
||||
$scope.externals = externals;
|
||||
$scope.filter = filter;
|
||||
|
||||
$scope.toggleFilter = (type, filter) => toggleFilter(type, filter);
|
||||
@ -114,78 +153,11 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
const calendarEventClickCb = function (event, jsEvent, view) {
|
||||
// current calendar object
|
||||
const { calendar } = uiCalendarConfig.calendars;
|
||||
if (event.available_type === 'machines') {
|
||||
currentMachineEvent = event;
|
||||
calendar.fullCalendar('changeView', 'agendaDay');
|
||||
return calendar.fullCalendar('gotoDate', event.start);
|
||||
} else if (event.available_type === 'space') {
|
||||
calendar.fullCalendar('changeView', 'agendaDay');
|
||||
return calendar.fullCalendar('gotoDate', event.start);
|
||||
} else if (event.available_type === 'event') {
|
||||
return $state.go('app.public.events_show', { id: event.event_id });
|
||||
} else if (event.available_type === 'training') {
|
||||
return $state.go('app.public.training_show', { id: event.training_id });
|
||||
} else {
|
||||
if (event.machine_id) {
|
||||
return $state.go('app.public.machines_show', { id: event.machine_id });
|
||||
} else if (event.space_id) {
|
||||
return $state.go('app.public.space_show', { id: event.space_id });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// agendaDay view: disable slotEventOverlap
|
||||
// agendaWeek view: enable slotEventOverlap
|
||||
const toggleSlotEventOverlap = function (view) {
|
||||
// set defaultView, because when we change slotEventOverlap
|
||||
// ui-calendar will trigger rerender calendar
|
||||
$scope.calendarConfig.defaultView = view.type;
|
||||
const today = currentMachineEvent ? currentMachineEvent.start : moment().utc().startOf('day');
|
||||
if ((today > view.intervalStart) && (today < view.intervalEnd) && (today !== view.intervalStart)) {
|
||||
$scope.calendarConfig.defaultDate = today;
|
||||
} else {
|
||||
$scope.calendarConfig.defaultDate = view.intervalStart;
|
||||
}
|
||||
if (view.type === 'agendaDay') {
|
||||
return $scope.calendarConfig.slotEventOverlap = false;
|
||||
} else {
|
||||
return $scope.calendarConfig.slotEventOverlap = true;
|
||||
}
|
||||
};
|
||||
|
||||
// function is called when calendar view is rendered or changed
|
||||
const viewRenderCb = function (view, element) {
|
||||
toggleSlotEventOverlap(view);
|
||||
if (view.type === 'agendaDay') {
|
||||
// get availabilties by 1 day for show machine slots
|
||||
return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents');
|
||||
}
|
||||
};
|
||||
|
||||
const eventRenderCb = function (event, element) {
|
||||
if (event.tags.length > 0) {
|
||||
let html = '';
|
||||
for (let tag of Array.from(event.tags)) {
|
||||
html += `<span class='label label-success text-white'>${tag.name}</span> `;
|
||||
}
|
||||
element.find('.fc-title').append(`<br/>${html}`);
|
||||
}
|
||||
};
|
||||
|
||||
const getFilter = function () {
|
||||
const t = $scope.trainings.filter(t => t.checked).map(t => t.id);
|
||||
const m = $scope.machines.filter(m => m.checked).map(m => m.id);
|
||||
const s = $scope.spaces.filter(s => s.checked).map(s => s.id);
|
||||
return { t, m, s, evt: $scope.filter.evt, dispo: $scope.filter.dispo };
|
||||
};
|
||||
|
||||
var availabilitySourceUrl = () => `/api/availabilities/public?${$.param(getFilter())}`;
|
||||
|
||||
const initialize = () =>
|
||||
// fullCalendar (v2) configuration
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = () => {
|
||||
// fullCalendar (v2) configuration
|
||||
$scope.calendarConfig = CalendarConfig({
|
||||
events: availabilitySourceUrl(),
|
||||
slotEventOverlap: true,
|
||||
@ -207,6 +179,97 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
return eventRenderCb(event, element);
|
||||
}
|
||||
});
|
||||
$scope.externals.forEach(e => {
|
||||
if (e.checked) {
|
||||
$scope.eventSources.push({
|
||||
id: e.id,
|
||||
url: `/api/i_calendar/${e.id}/events`,
|
||||
textColor: e.text_color || '#000',
|
||||
color: e.color
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when an event object is clicked in the fullCalendar view
|
||||
*/
|
||||
const calendarEventClickCb = function (event, jsEvent, view) {
|
||||
// current calendar object
|
||||
const { calendar } = uiCalendarConfig.calendars;
|
||||
if (event.available_type === 'machines') {
|
||||
currentMachineEvent = event;
|
||||
calendar.fullCalendar('changeView', 'agendaDay');
|
||||
calendar.fullCalendar('gotoDate', event.start);
|
||||
} else if (event.available_type === 'space') {
|
||||
calendar.fullCalendar('changeView', 'agendaDay');
|
||||
calendar.fullCalendar('gotoDate', event.start);
|
||||
} else if (event.available_type === 'event') {
|
||||
$state.go('app.public.events_show', { id: event.event_id });
|
||||
} else if (event.available_type === 'training') {
|
||||
$state.go('app.public.training_show', { id: event.training_id });
|
||||
} else {
|
||||
if (event.machine_id) {
|
||||
$state.go('app.public.machines_show', { id: event.machine_id });
|
||||
} else if (event.space_id) {
|
||||
$state.go('app.public.space_show', { id: event.space_id });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// agendaDay view: disable slotEventOverlap
|
||||
// agendaWeek view: enable slotEventOverlap
|
||||
const toggleSlotEventOverlap = function (view) {
|
||||
// set defaultView, because when we change slotEventOverlap
|
||||
// ui-calendar will trigger rerender calendar
|
||||
$scope.calendarConfig.defaultView = view.type;
|
||||
const today = currentMachineEvent ? currentMachineEvent.start : moment().utc().startOf('day');
|
||||
if ((today > view.intervalStart) && (today < view.intervalEnd) && (today !== view.intervalStart)) {
|
||||
$scope.calendarConfig.defaultDate = today;
|
||||
} else {
|
||||
$scope.calendarConfig.defaultDate = view.intervalStart;
|
||||
}
|
||||
if (view.type === 'agendaDay') {
|
||||
return $scope.calendarConfig.slotEventOverlap = false;
|
||||
} else {
|
||||
return $scope.calendarConfig.slotEventOverlap = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is called when calendar view is rendered or changed
|
||||
* @see https://fullcalendar.io/docs/v3/viewRender#v2
|
||||
*/
|
||||
const viewRenderCb = function (view, element) {
|
||||
toggleSlotEventOverlap(view);
|
||||
if (view.type === 'agendaDay') {
|
||||
// get availabilties by 1 day for show machine slots
|
||||
return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered by fullCalendar when it is about to render an event.
|
||||
* @see https://fullcalendar.io/docs/v3/eventRender#v2
|
||||
*/
|
||||
const eventRenderCb = function (event, element) {
|
||||
if (event.tags && event.tags.length > 0) {
|
||||
let html = '';
|
||||
for (let tag of Array.from(event.tags)) {
|
||||
html += `<span class='label label-success text-white'>${tag.name}</span> `;
|
||||
}
|
||||
element.find('.fc-title').append(`<br/>${html}`);
|
||||
}
|
||||
};
|
||||
|
||||
const getFilter = function () {
|
||||
const t = $scope.trainings.filter(t => t.checked).map(t => t.id);
|
||||
const m = $scope.machines.filter(m => m.checked).map(m => m.id);
|
||||
const s = $scope.spaces.filter(s => s.checked).map(s => s.id);
|
||||
return { t, m, s, evt: $scope.filter.evt, dispo: $scope.filter.dispo };
|
||||
};
|
||||
|
||||
var availabilitySourceUrl = () => `/api/availabilities/public?${$.param(getFilter())}`;
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
|
@ -126,13 +126,16 @@ Application.Controllers.controller('EventsController', ['$scope', '$state', 'Eve
|
||||
}
|
||||
]);
|
||||
|
||||
Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$stateParams', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise',
|
||||
function ($scope, $state, $stateParams, $rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, eventPromise, growl, _t, Wallet, helpers, dialogs, priceCategoriesPromise, settingsPromise) {
|
||||
/* PUBLIC SCOPE */
|
||||
Application.Controllers.controller('ShowEventController', ['$scope', '$state', '$stateParams', '$rootScope', 'Event', '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'Slot', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'dialogs', 'priceCategoriesPromise', 'settingsPromise',
|
||||
function ($scope, $state, $stateParams, $rootScope, Event, $uibModal, Member, Reservation, Price, CustomAsset, Slot, eventPromise, growl, _t, Wallet, helpers, dialogs, priceCategoriesPromise, settingsPromise) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// reservations for the currently shown event
|
||||
$scope.reservations = [];
|
||||
|
||||
// current date & time
|
||||
$scope.now = moment();
|
||||
|
||||
// user to deal with
|
||||
$scope.ctrl =
|
||||
{ member: {} };
|
||||
@ -166,6 +169,12 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
// Global config: delay in hours before a booking while changing the booking slot is forbidden
|
||||
$scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay);
|
||||
|
||||
// Global config: is the user authorized to cancel his booking slots?
|
||||
$scope.enableBookingCancel = settingsPromise.booking_cancel_enable === 'true';
|
||||
|
||||
// Global config: delay in hours from now when restrictions occurs about cancelling reservations
|
||||
$scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay);
|
||||
|
||||
// Message displayed to the end user about rules that applies to events reservations
|
||||
$scope.eventExplicationsAlert = settingsPromise.event_explications_alert;
|
||||
|
||||
@ -174,26 +183,22 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
* @param event {$resource} angular's Event $resource
|
||||
*/
|
||||
$scope.deleteEvent = function (event) {
|
||||
dialogs.confirm({
|
||||
// open a confirmation dialog
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "events/deleteRecurrent.html" %>',
|
||||
size: 'md',
|
||||
controller: 'DeleteRecurrentEventController',
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_event')
|
||||
};
|
||||
}
|
||||
eventPromise: ['Event', function (Event) { return Event.get({ id: $scope.event.id }).$promise; }]
|
||||
}
|
||||
}, function () {
|
||||
// the admin has confirmed, delete
|
||||
event.$delete(function () {
|
||||
});
|
||||
// once the dialog was closed, do things depending on the result
|
||||
modalInstance.result.then(function (res) {
|
||||
if (res.status == 'success') {
|
||||
$state.go('app.public.events_list');
|
||||
return growl.info(_t('event_successfully_deleted'));
|
||||
}, function (error) {
|
||||
console.error(error);
|
||||
growl.error(_t('unable_to_delete_the_event_because_some_users_alredy_booked_it'));
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -241,13 +246,34 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
$scope.reserveSuccess = false;
|
||||
if (!$scope.isAuthenticated()) {
|
||||
return $scope.login(null, function (user) {
|
||||
$scope.reserve.toReserve = !$scope.reserve.toReserve;
|
||||
if (user.role !== 'admin') {
|
||||
return $scope.ctrl.member = user;
|
||||
}
|
||||
const sameTimeReservations = findReservationsAtSameTime();
|
||||
if (sameTimeReservations.length > 0) {
|
||||
showReserveSlotSameTimeModal(sameTimeReservations, function(res) {
|
||||
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
|
||||
});
|
||||
} else {
|
||||
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
|
||||
if ($scope.currentUser.role === 'admin') {
|
||||
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
|
||||
} else {
|
||||
Member.get({ id: $scope.currentUser.id }, function (member) {
|
||||
$scope.ctrl.member = member;
|
||||
const sameTimeReservations = findReservationsAtSameTime();
|
||||
if (sameTimeReservations.length > 0) {
|
||||
showReserveSlotSameTimeModal(sameTimeReservations, function(res) {
|
||||
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
|
||||
});
|
||||
} else {
|
||||
return $scope.reserve.toReserve = !$scope.reserve.toReserve;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -279,7 +305,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
const amountToPay = helpers.getAmountToPay($scope.reserve.amountTotal, wallet.amount);
|
||||
if (($scope.currentUser.role !== 'admin') && (amountToPay > 0)) {
|
||||
if ($rootScope.fablabWithoutOnlinePayment) {
|
||||
growl.error(_t('online_payment_disabled'));
|
||||
growl.error(_t('app.public.events_show.online_payment_disabled'));
|
||||
} else {
|
||||
return payByStripe(reservation);
|
||||
}
|
||||
@ -291,7 +317,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
});
|
||||
} else {
|
||||
// otherwise we alert, this error musn't occur when the current user is not admin
|
||||
return growl.error(_t('please_select_a_member_first'));
|
||||
return growl.error(_t('app.public.events_show.please_select_a_member_first'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -341,16 +367,50 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to cancel a reservation
|
||||
* @param reservation {{id:number, reservable_id:number, nb_reserve_places:number}}
|
||||
*/
|
||||
$scope.cancelReservation = function(reservation) {
|
||||
dialogs.confirm({
|
||||
resolve: {
|
||||
object: function() {
|
||||
return {
|
||||
title: _t('app.public.events_show.cancel_the_reservation'),
|
||||
msg: _t('app.public.events_show.do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets')
|
||||
};
|
||||
}
|
||||
}
|
||||
}, function() { // cancel confirmed
|
||||
Slot.cancel({
|
||||
id: reservation.slots[0].id
|
||||
}, function() { // successfully canceled
|
||||
let index;
|
||||
growl.success(_t('app.public.events_show.reservation_was_successfully_cancelled'));
|
||||
index = $scope.reservations.indexOf(reservation);
|
||||
$scope.event.nb_free_places = $scope.event.nb_free_places + reservation.total_booked_seats;
|
||||
$scope.reservations[index].slots[0].canceled_at = new Date();
|
||||
}, function(error) {
|
||||
growl.warning(_t('app.public.events_show.cancellation_failed'));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the provided reservation has been cancelled
|
||||
* @param reservation {Reservation}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.isCancelled = function(reservation) {
|
||||
return !!(reservation.slots[0].canceled_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to alter an already booked reservation date. A modal window will be opened to allow the user to choose
|
||||
* a new date for his reservation (if any available)
|
||||
* @param reservation {{id:number, reservable_id:number, nb_reserve_places:number}}
|
||||
* @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
*/
|
||||
$scope.modifyReservation = function (reservation, e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$scope.modifyReservation = function (reservation) {
|
||||
const index = $scope.reservations.indexOf(reservation);
|
||||
return $uibModal.open({
|
||||
templateUrl: '<%= asset_path "events/modify_event_reservation_modal.html" %>',
|
||||
@ -364,9 +424,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
$scope.reservation = angular.copy(reservation);
|
||||
|
||||
// set the reservable_id to the first available event
|
||||
for (e of Array.from(event.recurrence_events)) {
|
||||
if (e.nb_free_places > reservation.total_booked_seats) {
|
||||
$scope.reservation.reservable_id = e.id;
|
||||
for (evt of Array.from(event.recurrence_events)) {
|
||||
if (evt.nb_free_places > reservation.total_booked_seats) {
|
||||
$scope.reservation.reservable_id = evt.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -419,7 +479,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
|
||||
/**
|
||||
* Checks if the provided reservation is able to be moved (date change)
|
||||
* @param reservation {{total_booked_seats:number}}
|
||||
* @param reservation {{slots:[], total_booked_seats:number}}
|
||||
*/
|
||||
$scope.reservationCanModify = function (reservation) {
|
||||
const slotStart = moment(reservation.slots[0].start_at);
|
||||
@ -432,6 +492,17 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
return (isAble && $scope.enableBookingMove && (slotStart.diff(now, 'hours') >= $scope.moveBookingDelay));
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the provided reservation is able to be cancelled
|
||||
* @param reservation {{slots:[]}}
|
||||
*/
|
||||
$scope.reservationCanCancel = function(reservation) {
|
||||
var now, slotStart;
|
||||
slotStart = moment(reservation.slots[0].start_at);
|
||||
now = moment();
|
||||
return $scope.enableBookingCancel && slotStart.diff(now, "hours") >= $scope.cancelBookingDelay;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the total amount for the current reservation according to the previously set parameters
|
||||
* and assign the result in $scope.reserve.amountTotal
|
||||
@ -567,7 +638,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the current reservation to the default values. This implies to reservation form to be hidden.
|
||||
* Set the current reservation to the default values. This implies the reservation form to be hidden.
|
||||
*/
|
||||
var resetEventReserve = function () {
|
||||
if ($scope.event) {
|
||||
@ -696,12 +767,12 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
|
||||
// Button label
|
||||
if ($scope.amount > 0) {
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }, 'messageformat');
|
||||
$scope.validButtonName = _t('app.public.events_show.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
|
||||
} else {
|
||||
if ((price.price > 0) && ($scope.walletAmount === 0)) {
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) }, 'messageformat');
|
||||
$scope.validButtonName = _t('app.public.events_show.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) });
|
||||
} else {
|
||||
$scope.validButtonName = _t('confirm');
|
||||
$scope.validButtonName = _t('app.shared.buttons.confirm');
|
||||
}
|
||||
}
|
||||
|
||||
@ -735,7 +806,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
|
||||
/**
|
||||
* What to do after the payment was successful
|
||||
* @param resveration {Object} booked reservation
|
||||
* @param reservation {Object} booked reservation
|
||||
*/
|
||||
var afterPayment = function (reservation) {
|
||||
$scope.event.nb_free_places = $scope.event.nb_free_places - reservation.total_booked_seats;
|
||||
@ -748,6 +819,48 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find user's reservations, the same date at the same time, with event
|
||||
*/
|
||||
var findReservationsAtSameTime = function () {
|
||||
let sameTimeReservations = [
|
||||
'training_reservations',
|
||||
'machine_reservations',
|
||||
'space_reservations',
|
||||
'events_reservations'
|
||||
].map(function(k) {
|
||||
return _.filter($scope.ctrl.member[k], function(r) {
|
||||
if (r.reservable_type === 'Event' && r.reservable.id === $scope.event.id) {
|
||||
return false;
|
||||
}
|
||||
return moment($scope.event.start_time).isSame(r.start_at) ||
|
||||
(moment($scope.event.end_time).isAfter(r.start_at) && moment($scope.event.end_time).isBefore(r.end_at)) ||
|
||||
(moment($scope.event.start_time).isAfter(r.start_at) && moment($scope.event.start_time).isBefore(r.end_at)) ||
|
||||
(moment($scope.event.start_time).isBefore(r.start_at) && moment($scope.event.end_time).isAfter(r.end_at));
|
||||
});
|
||||
});
|
||||
return _.union.apply(null, sameTimeReservations);
|
||||
};
|
||||
|
||||
/**
|
||||
* A modal for show reservations the same date at the same time
|
||||
*
|
||||
* @param sameTimeReservations {Array} reservations the same date at the same time
|
||||
* @param callback {function} callback will invoke when user confirm
|
||||
*/
|
||||
var showReserveSlotSameTimeModal = function(sameTimeReservations, callback) {
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "shared/_reserve_slot_same_time.html" %>',
|
||||
size: 'md',
|
||||
controller: 'ReserveSlotSameTimeController',
|
||||
resolve: {
|
||||
sameTimeReservations: function() { return sameTimeReservations; },
|
||||
}
|
||||
});
|
||||
modalInstance.result.then(callback);
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
@ -762,3 +875,70 @@ function __range__ (left, right, inclusive) {
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Controller used in the event deletion modal window
|
||||
*/
|
||||
Application.Controllers.controller('DeleteRecurrentEventController', ['$scope', '$uibModalInstance', 'Event', 'eventPromise', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, Event, eventPromise, growl, _t) {
|
||||
|
||||
// is the current event (to be deleted) recurrent?
|
||||
$scope.isRecurrent = eventPromise.recurrence_events.length > 0;
|
||||
|
||||
// with recurrent slots: how many slots should we delete?
|
||||
$scope.deleteMode = 'single';
|
||||
|
||||
/**
|
||||
* Confirmation callback
|
||||
*/
|
||||
$scope.ok = function () {
|
||||
const { id, start_at, end_at } = eventPromise;
|
||||
// the admin has confirmed, delete the slot
|
||||
Event.delete(
|
||||
{ id, mode: $scope.deleteMode },
|
||||
function (res) {
|
||||
// delete success
|
||||
if (res.deleted > 1) {
|
||||
growl.success(_t(
|
||||
'app.public.events_show.events_deleted',
|
||||
{COUNT: res.deleted - 1}
|
||||
));
|
||||
} else {
|
||||
growl.success(_t(
|
||||
'app.public.events_show.event_successfully_deleted'
|
||||
));
|
||||
}
|
||||
$uibModalInstance.close({
|
||||
status: 'success',
|
||||
events: res.details.map(function (d) { return d.event.id })
|
||||
});
|
||||
},
|
||||
function (res) {
|
||||
// not everything was deleted
|
||||
const { data } = res;
|
||||
if (data.total > 1) {
|
||||
growl.warning(_t(
|
||||
'app.public.events_show.events_not_deleted',
|
||||
{TOTAL: data.total, COUNT: data.total - data.deleted}
|
||||
));
|
||||
} else {
|
||||
growl.error(_t(
|
||||
'app.public.events_show.unable_to_delete_the_event'
|
||||
));
|
||||
}
|
||||
$uibModalInstance.close({
|
||||
status: 'failed',
|
||||
availabilities: data.details.filter(function (d) { return d.status }).map(function (d) { return d.event.id })
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancellation callback
|
||||
*/
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
@ -1,59 +1,324 @@
|
||||
/* eslint-disable
|
||||
no-undef,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', 'Twitter', 'lastMembersPromise', 'lastProjectsPromise', 'upcomingEventsPromise', 'homeBlogpostPromise', 'twitterNamePromise',
|
||||
function ($scope, $stateParams, Twitter, lastMembersPromise, lastProjectsPromise, upcomingEventsPromise, homeBlogpostPromise, twitterNamePromise) {
|
||||
Application.Controllers.controller('HomeController', ['$scope', '$stateParams', 'settingsPromise', 'Member', 'uiTourService', '_t',
|
||||
function ($scope, $stateParams, settingsPromise, Member, uiTourService, _t) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// The last registered members who confirmed their addresses
|
||||
$scope.lastMembers = lastMembersPromise;
|
||||
// Home page HTML content
|
||||
$scope.homeContent = null;
|
||||
|
||||
// The last tweets from the Fablab official twitter account
|
||||
$scope.lastTweets = [];
|
||||
|
||||
// The last projects published/documented on the plateform
|
||||
$scope.lastProjects = lastProjectsPromise;
|
||||
|
||||
// The closest upcoming events
|
||||
$scope.upcomingEvents = upcomingEventsPromise;
|
||||
|
||||
// The admin blogpost
|
||||
$scope.homeBlogpost = homeBlogpostPromise.setting.value;
|
||||
|
||||
// Twitter username
|
||||
$scope.twitterName = twitterNamePromise.setting.value;
|
||||
// Status of the components in the home page (exists or not?)
|
||||
$scope.status = {
|
||||
news: false,
|
||||
projects: false,
|
||||
twitter: false,
|
||||
members: false,
|
||||
events: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the provided event run on a single day or not
|
||||
* @param event {Object} single event from the $scope.upcomingEvents array
|
||||
* @returns {boolean} false if the event runs on more that 1 day
|
||||
*/
|
||||
$scope.isOneDayEvent = event => moment(event.start_date).isSame(event.end_date, 'day');
|
||||
* Setup the feature-tour for the home page. (admins only)
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupHomeTour = function () {
|
||||
if ($scope.currentUser && $scope.currentUser.role === 'admin') {
|
||||
setupWelcomeTour();
|
||||
}
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// we retrieve tweets from here instead of ui-router's promise because, if adblock stop the API request,
|
||||
// this prevent the whole home page to be blocked
|
||||
$scope.lastTweets = Twitter.query({ limit: 1 });
|
||||
|
||||
// if we recieve a token to reset the password as GET parameter, trigger the
|
||||
// if we receive a token to reset the password as GET parameter, trigger the
|
||||
// changePassword modal from the parent controller
|
||||
if ($stateParams.reset_password_token) {
|
||||
return $scope.$parent.editPassword($stateParams.reset_password_token);
|
||||
}
|
||||
|
||||
// We set the home page content, with the directives replacing the placeholders
|
||||
$scope.homeContent = insertDirectives(settingsPromise.home_content);
|
||||
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
|
||||
// for admins, setup the tour on login
|
||||
$scope.$watch('currentUser', function (newValue, oldValue) {
|
||||
if (!oldValue && newValue && newValue.role === 'admin') {
|
||||
const uitour = uiTourService.getTourByName('welcome');
|
||||
if (!uitour.hasStep()) {
|
||||
setupWelcomeTour();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the provided html and replace the elements with special IDs (#news, #projects, #twitter, #members, #events)
|
||||
* by their respective angular directives
|
||||
* @param html {String} a valid html string, as defined by the summernote editor in admin/settings/home_page
|
||||
* @returns {string} a valid html string containing angular directives for the specified plugins
|
||||
*/
|
||||
const insertDirectives = function (html) {
|
||||
const node = document.createElement('div');
|
||||
node.innerHTML = html.trim();
|
||||
|
||||
node.querySelectorAll('div#news').forEach((newsNode) => {
|
||||
const news = document.createElement('news');
|
||||
newsNode.parentNode.replaceChild(news, newsNode);
|
||||
$scope.status.news = true;
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#projects').forEach((projectsNode) => {
|
||||
const projects = document.createElement('projects');
|
||||
projectsNode.parentNode.replaceChild(projects, projectsNode);
|
||||
$scope.status.projects = true;
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#twitter').forEach((twitterNode) => {
|
||||
const twitter = document.createElement('twitter');
|
||||
twitterNode.parentNode.replaceChild(twitter, twitterNode);
|
||||
$scope.status.twitter = true;
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#members').forEach((membersNode) => {
|
||||
const members = document.createElement('members');
|
||||
membersNode.parentNode.replaceChild(members, membersNode);
|
||||
$scope.status.members = true;
|
||||
});
|
||||
|
||||
node.querySelectorAll('div#events').forEach((eventsNode) => {
|
||||
const events = document.createElement('events');
|
||||
eventsNode.parentNode.replaceChild(events, eventsNode);
|
||||
$scope.status.events = true;
|
||||
});
|
||||
|
||||
return node.outerHTML;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the home page that will present an overview of the whole app.
|
||||
* This is intended as a contextual help.
|
||||
*/
|
||||
const setupWelcomeTour = function () {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('welcome');
|
||||
// add the steps
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.public.tour.welcome.welcome.title'),
|
||||
content: _t('app.public.tour.welcome.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.home-link',
|
||||
stepId: 'home',
|
||||
order: 1,
|
||||
title: _t('app.public.tour.welcome.home.title'),
|
||||
content: _t('app.public.tour.welcome.home.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.public-calendar-link',
|
||||
stepId: 'calendar',
|
||||
order: 2,
|
||||
title: _t('app.public.tour.welcome.calendar.title'),
|
||||
content: _t('app.public.tour.welcome.calendar.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.reserve-machine-link',
|
||||
stepId: 'machines',
|
||||
order: 3,
|
||||
title: _t('app.public.tour.welcome.machines.title'),
|
||||
content: _t('app.public.tour.welcome.machines.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
if (!Fablab.withoutSpaces) {
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.reserve-space-link',
|
||||
stepId: 'spaces',
|
||||
order: 4,
|
||||
title: _t('app.public.tour.welcome.spaces.title'),
|
||||
content: _t('app.public.tour.welcome.spaces.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
}
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.reserve-training-link',
|
||||
stepId: 'trainings',
|
||||
order: 5,
|
||||
title: _t('app.public.tour.welcome.trainings.title'),
|
||||
content: _t('app.public.tour.welcome.trainings.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.reserve-event-link',
|
||||
stepId: 'events',
|
||||
order: 6,
|
||||
title: _t('app.public.tour.welcome.events.title'),
|
||||
content: _t('app.public.tour.welcome.events.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.projects-gallery-link',
|
||||
stepId: 'projects',
|
||||
order: 7,
|
||||
title: _t('app.public.tour.welcome.projects.title'),
|
||||
content: _t('app.public.tour.welcome.projects.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary li.plans-link',
|
||||
stepId: 'plans',
|
||||
order: 8,
|
||||
title: _t('app.public.tour.welcome.plans.title'),
|
||||
content: _t('app.public.tour.welcome.plans.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.nav-primary .admin-section',
|
||||
stepId: 'admin',
|
||||
order: 9,
|
||||
title: _t('app.public.tour.welcome.admin.title'),
|
||||
content: _t('app.public.tour.welcome.admin.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.navbar.header li.about-page-link',
|
||||
stepId: 'about',
|
||||
order: 10,
|
||||
title: _t('app.public.tour.welcome.about.title'),
|
||||
content: _t('app.public.tour.welcome.about.content'),
|
||||
placement: 'bottom',
|
||||
popupClass: 'shift-right-40'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.navbar.header li.notification-center-link',
|
||||
stepId: 'notifications',
|
||||
order: 11,
|
||||
title: _t('app.public.tour.welcome.notifications.title'),
|
||||
content: _t('app.public.tour.welcome.notifications.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.navbar.header li.user-menu-dropdown',
|
||||
stepId: 'profile',
|
||||
order: 12,
|
||||
title: _t('app.public.tour.welcome.profile.title'),
|
||||
content: _t('app.public.tour.welcome.profile.content'),
|
||||
placement: 'bottom',
|
||||
popupClass: 'shift-left-80'
|
||||
});
|
||||
if ($scope.status.news && settingsPromise.home_blogpost) {
|
||||
uitour.createStep({
|
||||
selector: 'news',
|
||||
stepId: 'news',
|
||||
order: 13,
|
||||
title: _t('app.public.tour.welcome.news.title'),
|
||||
content: _t('app.public.tour.welcome.news.content'),
|
||||
placement: 'bottom'
|
||||
});
|
||||
}
|
||||
if ($scope.status.projects) {
|
||||
uitour.createStep({
|
||||
selector: 'projects',
|
||||
stepId: 'last_projects',
|
||||
order: 14,
|
||||
title: _t('app.public.tour.welcome.last_projects.title'),
|
||||
content: _t('app.public.tour.welcome.last_projects.content'),
|
||||
placement: 'right'
|
||||
});
|
||||
}
|
||||
if ($scope.status.twitter) {
|
||||
uitour.createStep({
|
||||
selector: 'twitter',
|
||||
stepId: 'last_tweet',
|
||||
order: 15,
|
||||
title: _t('app.public.tour.welcome.last_tweet.title'),
|
||||
content: _t('app.public.tour.welcome.last_tweet.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
}
|
||||
if ($scope.status.members) {
|
||||
uitour.createStep({
|
||||
selector: 'members',
|
||||
stepId: 'last_members',
|
||||
order: 16,
|
||||
title: _t('app.public.tour.welcome.last_members.title'),
|
||||
content: _t('app.public.tour.welcome.last_members.content'),
|
||||
placement: 'left'
|
||||
});
|
||||
}
|
||||
if ($scope.status.events) {
|
||||
uitour.createStep({
|
||||
selector: 'events',
|
||||
stepId: 'next_events',
|
||||
order: 17,
|
||||
title: _t('app.public.tour.welcome.next_events.title'),
|
||||
content: _t('app.public.tour.welcome.next_events.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'customize',
|
||||
order: 18,
|
||||
title: _t('app.public.tour.welcome.customize.title'),
|
||||
content: _t('app.public.tour.welcome.customize.content'),
|
||||
placement: 'bottom',
|
||||
orphan: 'true'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: '.app-generator .app-version',
|
||||
stepId: 'version',
|
||||
order: 19,
|
||||
title: _t('app.public.tour.welcome.version.title'),
|
||||
content: _t('app.public.tour.welcome.version.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 20,
|
||||
title: _t('app.public.tour.conclusion.title'),
|
||||
content: _t('app.public.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('welcome') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'welcome' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('welcome') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('welcome');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
|
@ -146,7 +146,7 @@ const _reserveMachine = function (machine, e) {
|
||||
let text = '';
|
||||
angular.forEach($scope.machine.trainings, function (training) {
|
||||
if (text.length > 0) {
|
||||
text += _this._t('machines_list._or_the_');
|
||||
text += _this._t('app.public.machines_list._or_the_');
|
||||
}
|
||||
return text += training.name.substr(0, 1).toLowerCase() + training.name.substr(1);
|
||||
});
|
||||
@ -180,8 +180,10 @@ const _reserveMachine = function (machine, e) {
|
||||
/**
|
||||
* Controller used in the public listing page, allowing everyone to see the list of machines
|
||||
*/
|
||||
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'Machine', '$uibModal', 'machinesPromise',
|
||||
function ($scope, $state, _t, Machine, $uibModal, machinesPromise) {
|
||||
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'Machine', '$uibModal', 'machinesPromise', 'Member', 'uiTourService',
|
||||
function ($scope, $state, _t, Machine, $uibModal, machinesPromise, Member, uiTourService) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// Retrieve the list of machines
|
||||
$scope.machines = machinesPromise;
|
||||
|
||||
@ -205,11 +207,92 @@ Application.Controllers.controller('MachinesController', ['$scope', '$state', '_
|
||||
$scope.machineFiltering = 'enabled';
|
||||
|
||||
// Available options for filtering machines by status
|
||||
return $scope.filterDisabled = [
|
||||
$scope.filterDisabled = [
|
||||
'enabled',
|
||||
'disabled',
|
||||
'all'
|
||||
];
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the machines page. (admins only)
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupMachinesTour = function () {
|
||||
// setup the tour for admins only
|
||||
if ($scope.currentUser && $scope.currentUser.role === 'admin') {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('machines');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.public.tour.machines.welcome.title'),
|
||||
content: _t('app.public.tour.machines.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
if ($scope.machines.length > 0) {
|
||||
uitour.createStep({
|
||||
selector: '.machines-list .show-button',
|
||||
stepId: 'view',
|
||||
order: 1,
|
||||
title: _t('app.public.tour.machines.view.title'),
|
||||
content: _t('app.public.tour.machines.view.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 2,
|
||||
title: _t('app.public.tour.conclusion.title'),
|
||||
content: _t('app.public.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('machines') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'machines' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('machines') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('machines');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
]);
|
||||
|
||||
@ -281,14 +364,14 @@ Application.Controllers.controller('ShowMachineController', ['$scope', '$state',
|
||||
$scope.delete = function (machine) {
|
||||
// check the permissions
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
console.error(_t('unauthorized_operation'));
|
||||
console.error(_t('app.public.machines_show.unauthorized_operation'));
|
||||
} else {
|
||||
dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_machine')
|
||||
title: _t('app.public.machines_show.confirmation_required'),
|
||||
msg: _t('app.public.machines_show.do_you_really_want_to_delete_this_machine')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -297,7 +380,7 @@ Application.Controllers.controller('ShowMachineController', ['$scope', '$state',
|
||||
// delete the machine then redirect to the machines listing
|
||||
machine.$delete(
|
||||
function () { $state.go('app.public.machines_list'); },
|
||||
function (error) { growl.warning(_t('the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users')); console.error(error); }
|
||||
function (error) { growl.warning(_t('app.public.machines_show.the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users')); console.error(error); }
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -367,6 +450,8 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
$scope.settings = settingsPromise;
|
||||
|
||||
// list of plans, classified by group
|
||||
$scope.groups = groupsPromise;
|
||||
$scope.plans = plansPromise;
|
||||
$scope.plansClassifiedByGroup = [];
|
||||
for (let group of Array.from(groupsPromise)) {
|
||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||
@ -402,16 +487,16 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
$scope.machineExplicationsAlert = settingsPromise.machine_explications_alert;
|
||||
|
||||
/**
|
||||
* Change the last selected slot's appearence to looks like 'added to cart'
|
||||
* Change the last selected slot's appearance to looks like 'added to cart'
|
||||
*/
|
||||
$scope.markSlotAsAdded = function () {
|
||||
$scope.selectedEvent.backgroundColor = FREE_SLOT_BORDER_COLOR;
|
||||
$scope.selectedEvent.title = _t('i_reserve');
|
||||
$scope.selectedEvent.title = _t('app.logged.machines_reserve.i_reserve');
|
||||
return updateCalendar();
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the last selected slot's appearence to looks like 'never added to cart'
|
||||
* Change the last selected slot's appearance to looks like 'never added to cart'
|
||||
*/
|
||||
$scope.markSlotAsRemoved = function (slot) {
|
||||
slot.backgroundColor = 'white';
|
||||
@ -431,16 +516,16 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
$scope.slotCancelled = function () { $scope.markSlotAsRemoved($scope.selectedEvent); };
|
||||
|
||||
/**
|
||||
* Change the last selected slot's appearence to looks like 'currently looking for a new destination to exchange'
|
||||
* Change the last selected slot's appearance to looks like 'currently looking for a new destination to exchange'
|
||||
*/
|
||||
$scope.markSlotAsModifying = function () {
|
||||
$scope.selectedEvent.backgroundColor = '#eee';
|
||||
$scope.selectedEvent.title = _t('i_change');
|
||||
$scope.selectedEvent.title = _t('app.logged.machines_reserve.i_change');
|
||||
return updateCalendar();
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the last selected slot's appearence to looks like 'the slot being exchanged will take this place'
|
||||
* Change the last selected slot's appearance to looks like 'the slot being exchanged will take this place'
|
||||
*/
|
||||
$scope.changeModifyMachineSlot = function () {
|
||||
if ($scope.events.placable) {
|
||||
@ -449,7 +534,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
}
|
||||
if (!$scope.events.placable || ($scope.events.placable._id !== $scope.selectedEvent._id)) {
|
||||
$scope.selectedEvent.backgroundColor = '#bbb';
|
||||
$scope.selectedEvent.title = _t('i_shift');
|
||||
$scope.selectedEvent.title = _t('app.logged.machines_reserve.i_shift');
|
||||
}
|
||||
return updateCalendar();
|
||||
};
|
||||
@ -458,7 +543,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
* When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
*/
|
||||
$scope.modifyMachineSlot = function () {
|
||||
$scope.events.placable.title = $scope.currentUser.role !== 'admin' ? _t('i_ve_reserved') : _t('not_available');
|
||||
$scope.events.placable.title = $scope.currentUser.role !== 'admin' ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available');
|
||||
$scope.events.placable.backgroundColor = 'white';
|
||||
$scope.events.placable.borderColor = $scope.events.modifiable.borderColor;
|
||||
$scope.events.placable.id = $scope.events.modifiable.id;
|
||||
@ -476,14 +561,14 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the current booking modification, reseting the whole process
|
||||
* Cancel the current booking modification, resetting the whole process
|
||||
*/
|
||||
$scope.cancelModifyMachineSlot = function () {
|
||||
if ($scope.events.placable) {
|
||||
$scope.events.placable.backgroundColor = 'white';
|
||||
$scope.events.placable.title = '';
|
||||
}
|
||||
$scope.events.modifiable.title = $scope.currentUser.role !== 'admin' ? _t('i_ve_reserved') : _t('not_available');
|
||||
$scope.events.modifiable.title = $scope.currentUser.role !== 'admin' ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available');
|
||||
$scope.events.modifiable.backgroundColor = 'white';
|
||||
|
||||
return updateCalendar();
|
||||
@ -500,7 +585,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the user current view from the plan subsription screen to the machine reservation agenda
|
||||
* Changes the user current view from the plan subscription screen to the machine reservation agenda
|
||||
* @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
*/
|
||||
$scope.doNotSubscribePlan = function (e) {
|
||||
@ -539,11 +624,11 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
machineSlot.is_reserved = true;
|
||||
machineSlot.can_modify = true;
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
machineSlot.title = _t('i_ve_reserved');
|
||||
machineSlot.title = _t('app.logged.machines_reserve.i_ve_reserved');
|
||||
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR;
|
||||
updateMachineSlot(machineSlot, reservation, $scope.currentUser);
|
||||
} else {
|
||||
machineSlot.title = _t('not_available');
|
||||
machineSlot.title = _t('app.logged.machines_reserve.not_available');
|
||||
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR;
|
||||
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member);
|
||||
}
|
||||
@ -579,7 +664,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
});
|
||||
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
return $scope.ctrl.member = $scope.currentUser;
|
||||
return Member.get({ id: $scope.currentUser.id }, function (member) { $scope.ctrl.member = member; });
|
||||
}
|
||||
};
|
||||
|
||||
@ -595,7 +680,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggered when fullCalendar tries to graphicaly render an event block.
|
||||
* Triggered when fullCalendar tries to graphically render an event block.
|
||||
* Append the event tag into the block, just after the event title.
|
||||
* @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
*/
|
||||
|
@ -18,109 +18,121 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
|
||||
$scope.navLinks = [
|
||||
{
|
||||
state: 'app.public.home',
|
||||
linkText: 'home',
|
||||
linkIcon: 'home'
|
||||
linkText: 'app.public.common.home',
|
||||
linkIcon: 'home',
|
||||
class: 'home-link'
|
||||
},
|
||||
{ class: 'menu-spacer' },
|
||||
{
|
||||
state: 'app.public.calendar',
|
||||
linkText: 'app.public.common.public_calendar',
|
||||
linkIcon: 'calendar',
|
||||
class: 'public-calendar-link'
|
||||
},
|
||||
|
||||
{
|
||||
state: 'app.public.machines_list',
|
||||
linkText: 'reserve_a_machine',
|
||||
linkIcon: 'cogs'
|
||||
linkText: 'app.public.common.reserve_a_machine',
|
||||
linkIcon: 'cogs',
|
||||
class: 'reserve-machine-link'
|
||||
},
|
||||
{
|
||||
state: 'app.public.trainings_list',
|
||||
linkText: 'trainings_registrations',
|
||||
linkIcon: 'graduation-cap'
|
||||
linkText: 'app.public.common.trainings_registrations',
|
||||
linkIcon: 'graduation-cap',
|
||||
class: 'reserve-training-link'
|
||||
},
|
||||
{
|
||||
state: 'app.public.events_list',
|
||||
linkText: 'events_registrations',
|
||||
linkIcon: 'tags'
|
||||
},
|
||||
{
|
||||
state: 'app.public.calendar',
|
||||
linkText: 'public_calendar',
|
||||
linkIcon: 'calendar'
|
||||
linkText: 'app.public.common.events_registrations',
|
||||
linkIcon: 'tags',
|
||||
class: 'reserve-event-link'
|
||||
},
|
||||
{ class: 'menu-spacer' },
|
||||
{
|
||||
state: 'app.public.projects_list',
|
||||
linkText: 'projects_gallery',
|
||||
linkIcon: 'th'
|
||||
}
|
||||
linkText: 'app.public.common.projects_gallery',
|
||||
linkIcon: 'th',
|
||||
class: 'projects-gallery-link'
|
||||
},
|
||||
{ class: 'menu-spacer' }
|
||||
|
||||
];
|
||||
|
||||
if (!Fablab.withoutPlans) {
|
||||
$scope.navLinks.push({
|
||||
state: 'app.public.plans',
|
||||
linkText: 'subscriptions',
|
||||
linkIcon: 'credit-card'
|
||||
linkText: 'app.public.common.subscriptions',
|
||||
linkIcon: 'credit-card',
|
||||
class: 'plans-link'
|
||||
});
|
||||
}
|
||||
|
||||
if (!Fablab.withoutSpaces) {
|
||||
$scope.navLinks.splice(3, 0, {
|
||||
$scope.navLinks.splice(4, 0, {
|
||||
state: 'app.public.spaces_list',
|
||||
linkText: 'reserve_a_space',
|
||||
linkIcon: 'rocket'
|
||||
linkText: 'app.public.common.reserve_a_space',
|
||||
linkIcon: 'rocket',
|
||||
class: 'reserve-space-link'
|
||||
});
|
||||
}
|
||||
|
||||
Fablab.adminNavLinks = Fablab.adminNavLinks || [];
|
||||
const adminNavLinks = [
|
||||
{
|
||||
state: 'app.admin.trainings',
|
||||
linkText: 'trainings_monitoring',
|
||||
linkIcon: 'graduation-cap'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.calendar',
|
||||
linkText: 'manage_the_calendar',
|
||||
linkText: 'app.public.common.manage_the_calendar',
|
||||
linkIcon: 'calendar'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.members',
|
||||
linkText: 'manage_the_users',
|
||||
linkIcon: 'users'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.invoices',
|
||||
linkText: 'manage_the_invoices',
|
||||
linkIcon: 'file-pdf-o'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.pricing',
|
||||
linkText: 'subscriptions_and_prices',
|
||||
linkIcon: 'money'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.events',
|
||||
linkText: 'manage_the_events',
|
||||
linkIcon: 'tags'
|
||||
},
|
||||
{
|
||||
state: 'app.public.machines_list',
|
||||
linkText: 'manage_the_machines',
|
||||
linkText: 'app.public.common.manage_the_machines',
|
||||
linkIcon: 'cogs'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.project_elements',
|
||||
linkText: 'manage_the_projects_elements',
|
||||
linkIcon: 'tasks'
|
||||
state: 'app.admin.trainings',
|
||||
linkText: 'app.public.common.trainings_monitoring',
|
||||
linkIcon: 'graduation-cap'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.events',
|
||||
linkText: 'app.public.common.manage_the_events',
|
||||
linkIcon: 'tags'
|
||||
},
|
||||
{ class: 'menu-spacer' },
|
||||
{
|
||||
state: 'app.admin.members',
|
||||
linkText: 'app.public.common.manage_the_users',
|
||||
linkIcon: 'users'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.pricing',
|
||||
linkText: 'app.public.common.subscriptions_and_prices',
|
||||
linkIcon: 'money'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.invoices',
|
||||
linkText: 'app.public.common.manage_the_invoices',
|
||||
linkIcon: 'file-pdf-o'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.statistics',
|
||||
linkText: 'statistics',
|
||||
linkText: 'app.public.common.statistics',
|
||||
linkIcon: 'bar-chart-o'
|
||||
},
|
||||
{ class: 'menu-spacer' },
|
||||
{
|
||||
state: 'app.admin.settings',
|
||||
linkText: 'customization',
|
||||
linkText: 'app.public.common.customization',
|
||||
linkIcon: 'gear'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.project_elements',
|
||||
linkText: 'app.public.common.manage_the_projects_elements',
|
||||
linkIcon: 'tasks'
|
||||
},
|
||||
{
|
||||
state: 'app.admin.open_api_clients',
|
||||
linkText: 'open_api_clients',
|
||||
linkText: 'app.public.common.open_api_clients',
|
||||
linkIcon: 'cloud'
|
||||
}
|
||||
].concat(Fablab.adminNavLinks);
|
||||
@ -128,9 +140,9 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc
|
||||
$scope.adminNavLinks = adminNavLinks;
|
||||
|
||||
if (!Fablab.withoutSpaces) {
|
||||
return $scope.adminNavLinks.splice(7, 0, {
|
||||
return $scope.adminNavLinks.splice(4, 0, {
|
||||
state: 'app.public.spaces_list',
|
||||
linkText: 'manage_the_spaces',
|
||||
linkText: 'app.public.common.manage_the_spaces',
|
||||
linkIcon: 'rocket'
|
||||
});
|
||||
}
|
||||
|
@ -142,10 +142,10 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
$rootScope.currentUser = user;
|
||||
Auth._currentUser.group_id = user.group_id;
|
||||
$scope.group.change = false;
|
||||
return growl.success(_t('edit_profile.your_group_has_been_successfully_changed'));
|
||||
return growl.success(_t('app.logged.dashboard.settings.your_group_has_been_successfully_changed'));
|
||||
}
|
||||
, function (err) {
|
||||
growl.error(_t('edit_profile.an_unexpected_error_prevented_your_group_from_being_changed'));
|
||||
growl.error(_t('app.logged.dashboard.settings.an_unexpected_error_prevented_your_group_from_being_changed'));
|
||||
return console.error(err);
|
||||
});
|
||||
|
||||
@ -198,13 +198,13 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
title: _t('app.logged.dashboard.settings.confirmation_required'),
|
||||
msg: $sce.trustAsHtml(
|
||||
_t('edit_profile.confirm_delete_your_account') + '<br/>' +
|
||||
'<strong>' + _t('edit_profile.all_data_will_be_lost') + '</strong><br/><br/>' +
|
||||
_t('edit_profile.invoicing_data_kept') + '<br/>' +
|
||||
_t('edit_profile.statistic_data_anonymized') + '<br/>' +
|
||||
_t('edit_profile.no_further_access_to_projects')
|
||||
_t('app.logged.dashboard.settings.confirm_delete_your_account') + '<br/>' +
|
||||
'<strong>' + _t('app.logged.dashboard.settings.all_data_will_be_lost') + '</strong><br/><br/>' +
|
||||
_t('app.logged.dashboard.settings.invoicing_data_kept') + '<br/>' +
|
||||
_t('app.logged.dashboard.settings.statistic_data_anonymized') + '<br/>' +
|
||||
_t('app.logged.dashboard.settings.no_further_access_to_projects')
|
||||
)
|
||||
};
|
||||
}
|
||||
@ -214,12 +214,12 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
|
||||
Member.remove({ id: user.id }, () =>
|
||||
Auth.logout().then(function () {
|
||||
$state.go('app.public.home');
|
||||
return growl.success(_t('edit_profile.your_user_account_has_been_successfully_deleted_goodbye'));
|
||||
return growl.success(_t('app.logged.dashboard.settings.your_user_account_has_been_successfully_deleted_goodbye'));
|
||||
})
|
||||
|
||||
, function (error) {
|
||||
console.log(error);
|
||||
return growl.error(_t('edit_profile.an_error_occured_preventing_your_account_from_being_deleted'));
|
||||
return growl.error(_t('app.logged.dashboard.settings.an_error_occured_preventing_your_account_from_being_deleted'));
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -99,7 +99,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
const amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount);
|
||||
if (($scope.currentUser.role !== 'admin') && (amountToPay > 0)) {
|
||||
if ($rootScope.fablabWithoutOnlinePayment) {
|
||||
growl.error(_t('online_payment_disabled'));
|
||||
growl.error(_t('app.public.plans.online_payment_disabled'));
|
||||
} else {
|
||||
return payByStripe();
|
||||
}
|
||||
@ -133,16 +133,16 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
$rootScope.currentUser = user;
|
||||
Auth._currentUser.group_id = user.group_id;
|
||||
growl.success(_t('your_group_was_successfully_changed'));
|
||||
growl.success(_t('app.public.plans.your_group_was_successfully_changed'));
|
||||
} else {
|
||||
growl.success(_t('the_user_s_group_was_successfully_changed'));
|
||||
growl.success(_t('app.public.plans.the_user_s_group_was_successfully_changed'));
|
||||
}
|
||||
}
|
||||
, function (err) {
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
growl.error(_t('an_error_prevented_your_group_from_being_changed'));
|
||||
growl.error(_t('app.public.plans.an_error_prevented_your_group_from_being_changed'));
|
||||
} else {
|
||||
growl.error(_t('an_error_prevented_to_change_the_user_s_group'));
|
||||
growl.error(_t('app.public.plans.an_error_prevented_to_change_the_user_s_group'));
|
||||
}
|
||||
console.error(err);
|
||||
});
|
||||
@ -318,12 +318,12 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
|
||||
// Button label
|
||||
if ($scope.amount > 0) {
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }, 'messageformat');
|
||||
$scope.validButtonName = _t('app.public.plans.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
|
||||
} else {
|
||||
if ((price.price > 0) && ($scope.walletAmount === 0)) {
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) }, 'messageformat');
|
||||
$scope.validButtonName = _t('app.public.plans.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) });
|
||||
} else {
|
||||
$scope.validButtonName = _t('confirm');
|
||||
$scope.validButtonName = _t('app.shared.buttons.confirm');
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
|
||||
}
|
||||
, function (data, status) { // failed
|
||||
$scope.alerts = [];
|
||||
$scope.alerts.push({ msg: _t('an_error_occured_during_the_payment_process_please_try_again_later'), type: 'danger' });
|
||||
$scope.alerts.push({ msg: _t('app.public.plans.an_error_occured_during_the_payment_process_please_try_again_later'), type: 'danger' });
|
||||
$scope.attempting = false;
|
||||
}
|
||||
);
|
||||
|
@ -123,7 +123,7 @@ Application.Controllers.controller('CompleteProfileController', ['$scope', '$roo
|
||||
if (err.data.error) {
|
||||
growl.error(err.data.error);
|
||||
} else {
|
||||
growl.error(_t('an_unexpected_error_occurred_check_your_authentication_code'));
|
||||
growl.error(_t('app.logged.profile_completion.an_unexpected_error_occurred_check_your_authentication_code'));
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
@ -174,7 +174,7 @@ Application.Controllers.controller('CompleteProfileController', ['$scope', '$roo
|
||||
function (email) {
|
||||
// Request the server to send an auth-migration email to the current user
|
||||
AuthProvider.send_code({ email },
|
||||
function (res) { growl.info(_t('code_successfully_sent_again')); },
|
||||
function (res) { growl.info(_t('app.logged.profile_completion.code_successfully_sent_again')); },
|
||||
function (err) { growl.error(err.data.error); }
|
||||
);
|
||||
}
|
||||
@ -206,7 +206,7 @@ Application.Controllers.controller('CompleteProfileController', ['$scope', '$roo
|
||||
CSRF.setMetaTags();
|
||||
|
||||
// init the birth date to JS object
|
||||
$scope.user.statistic_profile.birthday = moment($scope.user.statistic_profile.birthday).toDate();
|
||||
$scope.user.statistic_profile.birthday = $scope.user.statistic_profile.birthday ? moment($scope.user.statistic_profile.birthday).toDate() : undefined;
|
||||
|
||||
// bind fields protection with sso fields
|
||||
angular.forEach(activeProviderPromise.mapping, function (map) { $scope.preventField[map] = true; });
|
||||
|
@ -165,8 +165,8 @@ class ProjectsController {
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_step')
|
||||
title: _t('app.shared.project.confirmation_required'),
|
||||
msg: _t('app.shared.project.do_you_really_want_to_delete_this_step')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -215,6 +215,10 @@ class ProjectsController {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will query the API to autocomplete the typed user's name
|
||||
* @param nameLookup {string}
|
||||
*/
|
||||
$scope.autoCompleteName = function (nameLookup) {
|
||||
if (!nameLookup) {
|
||||
return;
|
||||
@ -246,6 +250,16 @@ class ProjectsController {
|
||||
return step.project_step_images_attributes.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the text to display on the save button, depending on the current state of the project
|
||||
*/
|
||||
$scope.saveButtonValue = function () {
|
||||
if (!$scope.project.state || $scope.project.state === 'draft') {
|
||||
return _t('app.shared.project.save_as_draft');
|
||||
}
|
||||
return _t('app.shared.buttons.save');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,7 +338,7 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
||||
$scope.projectsPagination = new paginationService.Instance(OpenlabProject, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreOpenlabCallback);
|
||||
return OpenlabProject.query({ q: $scope.search.q, page: currentPage, per_page: PROJECTS_PER_PAGE }, function (projectsPromise) {
|
||||
if (projectsPromise.errors != null) {
|
||||
growl.error(_t('projects_list.openlab_search_not_available_at_the_moment'));
|
||||
growl.error(_t('app.public.projects_list.openlab_search_not_available_at_the_moment'));
|
||||
$scope.openlab.searchOverWholeNetwork = false;
|
||||
return $scope.triggerSearch();
|
||||
} else {
|
||||
@ -533,8 +547,8 @@ Application.Controllers.controller('ShowProjectController', ['$scope', '$state',
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_project')
|
||||
title: _t('app.public.projects_show.confirmation_required'),
|
||||
msg: _t('app.public.projects_show.do_you_really_want_to_delete_this_project')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -543,7 +557,7 @@ Application.Controllers.controller('ShowProjectController', ['$scope', '$state',
|
||||
$scope.project.$delete(function () { $state.go('app.public.projects_list', {}, { reload: true }); });
|
||||
});
|
||||
} else {
|
||||
return console.error(_t('unauthorized_operation'));
|
||||
return console.error(_t('app.public.projects_show.unauthorized_operation'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -577,12 +591,12 @@ Application.Controllers.controller('ShowProjectController', ['$scope', '$state',
|
||||
{ abuse: $scope.signaler },
|
||||
function (res) {
|
||||
// creation successful
|
||||
growl.success(_t('your_report_was_successful_thanks'));
|
||||
growl.success(_t('app.public.projects_show.your_report_was_successful_thanks'));
|
||||
return $uibModalInstance.close(res);
|
||||
}
|
||||
, function (error) {
|
||||
// creation failed...
|
||||
growl.error(_t('an_error_occured_while_sending_your_report'));
|
||||
growl.error(_t('app.public.projects_show.an_error_occured_while_sending_your_report'));
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -98,30 +98,115 @@ class SpacesController {
|
||||
/**
|
||||
* Controller used in the public listing page, allowing everyone to see the list of spaces
|
||||
*/
|
||||
Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spacesPromise', function ($scope, $state, spacesPromise) {
|
||||
// Retrieve the list of spaces
|
||||
$scope.spaces = spacesPromise;
|
||||
Application.Controllers.controller('SpacesController', ['$scope', '$state', 'spacesPromise', '_t', 'Member', 'uiTourService',
|
||||
function ($scope, $state, spacesPromise, _t, Member, uiTourService) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
/**
|
||||
* Redirect the user to the space details page
|
||||
*/
|
||||
$scope.showSpace = function (space) { $state.go('app.public.space_show', { id: space.slug }); };
|
||||
// Retrieve the list of spaces
|
||||
$scope.spaces = spacesPromise;
|
||||
|
||||
/**
|
||||
* Callback to book a reservation for the current space
|
||||
*/
|
||||
$scope.reserveSpace = function (space) { $state.go('app.logged.space_reserve', { id: space.slug }); };
|
||||
/**
|
||||
* Redirect the user to the space details page
|
||||
*/
|
||||
$scope.showSpace = function (space) { $state.go('app.public.space_show', { id: space.slug }); };
|
||||
|
||||
// Default: we show only enabled spaces
|
||||
$scope.spaceFiltering = 'enabled';
|
||||
/**
|
||||
* Callback to book a reservation for the current space
|
||||
*/
|
||||
$scope.reserveSpace = function (space) { $state.go('app.logged.space_reserve', { id: space.slug }); };
|
||||
|
||||
// Available options for filtering spaces by status
|
||||
$scope.filterDisabled = [
|
||||
'enabled',
|
||||
'disabled',
|
||||
'all'
|
||||
];
|
||||
}]);
|
||||
// Default: we show only enabled spaces
|
||||
$scope.spaceFiltering = 'enabled';
|
||||
|
||||
// Available options for filtering spaces by status
|
||||
$scope.filterDisabled = [
|
||||
'enabled',
|
||||
'disabled',
|
||||
'all'
|
||||
];
|
||||
|
||||
/**
|
||||
* Setup the feature-tour for the spaces page. (admins only)
|
||||
* This is intended as a contextual help (when pressing F1)
|
||||
*/
|
||||
$scope.setupSpacesTour = function () {
|
||||
// setup the tour for admins only
|
||||
if ($scope.currentUser && $scope.currentUser.role === 'admin') {
|
||||
// get the tour defined by the ui-tour directive
|
||||
const uitour = uiTourService.getTourByName('spaces');
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'welcome',
|
||||
order: 0,
|
||||
title: _t('app.public.tour.spaces.welcome.title'),
|
||||
content: _t('app.public.tour.spaces.welcome.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
if ($scope.spaces.length > 0) {
|
||||
uitour.createStep({
|
||||
selector: '.spaces-list .show-button',
|
||||
stepId: 'view',
|
||||
order: 1,
|
||||
title: _t('app.public.tour.spaces.view.title'),
|
||||
content: _t('app.public.tour.spaces.view.content'),
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
uitour.createStep({
|
||||
selector: 'body',
|
||||
stepId: 'conclusion',
|
||||
order: 2,
|
||||
title: _t('app.public.tour.conclusion.title'),
|
||||
content: _t('app.public.tour.conclusion.content'),
|
||||
placement: 'bottom',
|
||||
orphan: true
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
uitour.on('ended', function () {
|
||||
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('spaces') < 0) {
|
||||
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'spaces' }, function (res) {
|
||||
$scope.currentUser.profile.tours = res.tours;
|
||||
});
|
||||
}
|
||||
});
|
||||
// if the user has never seen the tour, show him now
|
||||
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('spaces') < 0) {
|
||||
uitour.start();
|
||||
}
|
||||
// start this tour when an user press F1 - this is contextual help
|
||||
window.addEventListener('keydown', handleF1);
|
||||
}
|
||||
}
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// listen the $destroy event of the controller to remove the F1 key binding
|
||||
$scope.$on('$destroy', function () {
|
||||
window.removeEventListener('keydown', handleF1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to trigger the feature tour when the user press the F1 key.
|
||||
* @param e {KeyboardEvent}
|
||||
*/
|
||||
const handleF1 = function (e) {
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
const tour = uiTourService.getTourByName('spaces');
|
||||
if (tour) { tour.start(); }
|
||||
}
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller used in the space creation page (admin)
|
||||
@ -185,14 +270,14 @@ Application.Controllers.controller('ShowSpaceController', ['$scope', '$state', '
|
||||
event.preventDefault();
|
||||
// check the permissions
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
return console.error(_t('space_show.unauthorized_operation'));
|
||||
return console.error(_t('app.public.space_show.unauthorized_operation'));
|
||||
} else {
|
||||
return dialogs.confirm({
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('space_show.confirmation_required'),
|
||||
msg: _t('space_show.do_you_really_want_to_delete_this_space')
|
||||
title: _t('app.public.space_show.confirmation_required'),
|
||||
msg: _t('app.public.space_show.do_you_really_want_to_delete_this_space')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -204,7 +289,7 @@ Application.Controllers.controller('ShowSpaceController', ['$scope', '$state', '
|
||||
$state.go('app.public.spaces_list');
|
||||
},
|
||||
function (error) {
|
||||
growl.warning(_t('space_show.the_space_cant_be_deleted_because_it_is_already_reserved_by_some_users'));
|
||||
growl.warning(_t('app.public.space_show.the_space_cant_be_deleted_because_it_is_already_reserved_by_some_users'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
@ -243,6 +328,8 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$stateP
|
||||
|
||||
// list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = [];
|
||||
$scope.groups = groupsPromise;
|
||||
$scope.plans = plansPromise;
|
||||
for (let group of Array.from(groupsPromise)) {
|
||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||
for (let plan of Array.from(plansPromise)) {
|
||||
@ -333,7 +420,7 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$stateP
|
||||
*/
|
||||
$scope.markSlotAsModifying = function () {
|
||||
$scope.selectedEvent.backgroundColor = '#eee';
|
||||
$scope.selectedEvent.title = _t('space_reserve.i_change');
|
||||
$scope.selectedEvent.title = _t('app.logged.space_reserve.i_change');
|
||||
return updateCalendar();
|
||||
};
|
||||
|
||||
@ -347,7 +434,7 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$stateP
|
||||
}
|
||||
if (!$scope.events.placable || ($scope.events.placable._id !== $scope.selectedEvent._id)) {
|
||||
$scope.selectedEvent.backgroundColor = '#bbb';
|
||||
$scope.selectedEvent.title = _t('space_reserve.i_shift');
|
||||
$scope.selectedEvent.title = _t('app.logged.space_reserve.i_shift');
|
||||
}
|
||||
return updateCalendar();
|
||||
};
|
||||
@ -356,7 +443,7 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$stateP
|
||||
* When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
*/
|
||||
$scope.modifyTrainingSlot = function () {
|
||||
$scope.events.placable.title = _t('space_reserve.i_ve_reserved');
|
||||
$scope.events.placable.title = _t('app.logged.space_reserve.i_ve_reserved');
|
||||
$scope.events.placable.backgroundColor = 'white';
|
||||
$scope.events.placable.borderColor = $scope.events.modifiable.borderColor;
|
||||
$scope.events.placable.id = $scope.events.modifiable.id;
|
||||
@ -382,7 +469,7 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$stateP
|
||||
$scope.events.placable.backgroundColor = 'white';
|
||||
$scope.events.placable.title = '';
|
||||
}
|
||||
$scope.events.modifiable.title = _t('space_reserve.i_ve_reserved');
|
||||
$scope.events.modifiable.title = _t('app.logged.space_reserve.i_ve_reserved');
|
||||
$scope.events.modifiable.backgroundColor = 'white';
|
||||
|
||||
return updateCalendar();
|
||||
@ -451,7 +538,7 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$stateP
|
||||
angular.forEach($scope.events.paid, function (spaceSlot, key) {
|
||||
spaceSlot.is_reserved = true;
|
||||
spaceSlot.can_modify = true;
|
||||
spaceSlot.title = _t('space_reserve.i_ve_reserved');
|
||||
spaceSlot.title = _t('app.logged.space_reserve.i_ve_reserved');
|
||||
spaceSlot.backgroundColor = 'white';
|
||||
spaceSlot.borderColor = RESERVED_SLOT_BORDER_COLOR;
|
||||
return updateSpaceSlotId(spaceSlot, reservation);
|
||||
|
@ -47,15 +47,15 @@ Application.Controllers.controller('ShowTrainingController', ['$scope', '$state'
|
||||
$scope.delete = function (training) {
|
||||
// check the permissions
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
console.error(_t('unauthorized_operation'));
|
||||
growl.error(_t('app.public.training_show.unauthorized_operation'));
|
||||
} else {
|
||||
dialogs.confirm(
|
||||
{
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('confirmation_required'),
|
||||
msg: _t('do_you_really_want_to_delete_this_training')
|
||||
title: _t('app.public.training_show.confirmation_required'),
|
||||
msg: _t('app.public.training_show.do_you_really_want_to_delete_this_training')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -65,7 +65,7 @@ Application.Controllers.controller('ShowTrainingController', ['$scope', '$state'
|
||||
training.$delete(
|
||||
function () { $state.go('app.public.trainings_list'); },
|
||||
function (error) {
|
||||
growl.warning(_t('the_training_cant_be_deleted_because_it_is_already_reserved_by_some_users'));
|
||||
growl.warning(_t('app.public.training_show.the_training_cant_be_deleted_because_it_is_already_reserved_by_some_users'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
@ -111,6 +111,8 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$sta
|
||||
{ member: {} };
|
||||
|
||||
// list of plans, classified by group
|
||||
$scope.groups = groupsPromise;
|
||||
$scope.plans = plansPromise;
|
||||
$scope.plansClassifiedByGroup = [];
|
||||
for (let group of Array.from(groupsPromise)) {
|
||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||
@ -208,7 +210,7 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$sta
|
||||
*/
|
||||
$scope.markSlotAsModifying = function () {
|
||||
$scope.selectedEvent.backgroundColor = '#eee';
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('i_change');
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('app.logged.trainings_reserve.i_change');
|
||||
return updateCalendar();
|
||||
};
|
||||
|
||||
@ -222,7 +224,7 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$sta
|
||||
}
|
||||
if (!$scope.events.placable || ($scope.events.placable._id !== $scope.selectedEvent._id)) {
|
||||
$scope.selectedEvent.backgroundColor = '#bbb';
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('i_shift');
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('app.logged.trainings_reserve.i_shift');
|
||||
}
|
||||
return updateCalendar();
|
||||
};
|
||||
@ -231,7 +233,7 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$sta
|
||||
* When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
*/
|
||||
$scope.modifyTrainingSlot = function () {
|
||||
$scope.events.placable.title = $scope.currentUser.role !== 'admin' ? $scope.events.placable.training.name + ' - ' + _t('i_ve_reserved') : $scope.events.placable.training.name;
|
||||
$scope.events.placable.title = $scope.currentUser.role !== 'admin' ? $scope.events.placable.training.name + ' - ' + _t('app.logged.trainings_reserve.i_ve_reserved') : $scope.events.placable.training.name;
|
||||
$scope.events.placable.backgroundColor = 'white';
|
||||
$scope.events.placable.borderColor = $scope.events.modifiable.borderColor;
|
||||
$scope.events.placable.id = $scope.events.modifiable.id;
|
||||
@ -257,7 +259,7 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$sta
|
||||
$scope.events.placable.backgroundColor = 'white';
|
||||
$scope.events.placable.title = $scope.events.placable.training.name;
|
||||
}
|
||||
$scope.events.modifiable.title = $scope.currentUser.role !== 'admin' ? $scope.events.modifiable.training.name + ' - ' + _t('i_ve_reserved') : $scope.events.modifiable.training.name;
|
||||
$scope.events.modifiable.title = $scope.currentUser.role !== 'admin' ? $scope.events.modifiable.training.name + ' - ' + _t('app.logged.trainings_reserve.i_ve_reserved') : $scope.events.modifiable.training.name;
|
||||
$scope.events.modifiable.backgroundColor = 'white';
|
||||
|
||||
return updateCalendar();
|
||||
@ -329,7 +331,7 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$sta
|
||||
$scope.events.paid[0].can_modify = true;
|
||||
updateTrainingSlotId($scope.events.paid[0], reservation);
|
||||
$scope.events.paid[0].borderColor = '#b2e774';
|
||||
$scope.events.paid[0].title = $scope.events.paid[0].training.name + ' - ' + _t('i_ve_reserved');
|
||||
$scope.events.paid[0].title = $scope.events.paid[0].training.name + ' - ' + _t('app.logged.trainings_reserve.i_ve_reserved');
|
||||
|
||||
if ($scope.selectedPlan) {
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan);
|
||||
|
@ -10,8 +10,8 @@
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'helpers', '_t',
|
||||
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, helpers, _t) {
|
||||
Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'helpers', '_t', '$uibModal',
|
||||
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, helpers, _t, $uibModal) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
@ -23,6 +23,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
plan: '=',
|
||||
planSelectionTime: '=',
|
||||
settings: '=',
|
||||
plans: '=',
|
||||
groups: '=',
|
||||
onSlotAddedToCart: '=',
|
||||
onSlotRemovedFromCart: '=',
|
||||
onSlotStartToModify: '=',
|
||||
@ -72,8 +74,38 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
* @param slot {Object} fullCalendar event object
|
||||
*/
|
||||
$scope.validateSlot = function (slot) {
|
||||
slot.isValid = true;
|
||||
return updateCartPrice();
|
||||
let sameTimeReservations = [
|
||||
'training_reservations',
|
||||
'machine_reservations',
|
||||
'space_reservations',
|
||||
'events_reservations'
|
||||
].map(function (k) {
|
||||
return _.filter($scope.user[k], function(r) {
|
||||
return slot.start.isSame(r.start_at) ||
|
||||
(slot.end.isAfter(r.start_at) && slot.end.isBefore(r.end_at)) ||
|
||||
(slot.start.isAfter(r.start_at) && slot.start.isBefore(r.end_at)) ||
|
||||
(slot.start.isBefore(r.start_at) && slot.end.isAfter(r.end_at));
|
||||
})
|
||||
});
|
||||
sameTimeReservations = _.union.apply(null, sameTimeReservations);
|
||||
if (sameTimeReservations.length > 0) {
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "shared/_reserve_slot_same_time.html" %>',
|
||||
size: 'md',
|
||||
controller: 'ReserveSlotSameTimeController',
|
||||
resolve: {
|
||||
sameTimeReservations: function() { return sameTimeReservations; }
|
||||
}
|
||||
});
|
||||
modalInstance.result.then(function(res) {
|
||||
slot.isValid = true;
|
||||
return updateCartPrice();
|
||||
});
|
||||
} else {
|
||||
slot.isValid = true;
|
||||
return updateCartPrice();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -112,12 +144,19 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
* Switch the user's view from the reservation agenda to the plan subscription
|
||||
*/
|
||||
$scope.showPlans = function () {
|
||||
// first, we ensure that a user was selected (admin) or logged (member)
|
||||
if (Object.keys($scope.user).length > 0) {
|
||||
// first, we ensure that a user was selected (admin) or logged (member)
|
||||
const isSelectedUser = Object.keys($scope.user).length > 0;
|
||||
// all slots are in future
|
||||
const areFutureSlots = _.every($scope.events.reserved, function(s) {
|
||||
return s.start.isAfter();
|
||||
});
|
||||
if (isSelectedUser && areFutureSlots) {
|
||||
return $scope.modePlans = true;
|
||||
} else {
|
||||
} else if (!isSelectedUser){
|
||||
// otherwise we alert, this error musn't occur when the current user hasn't the admin role
|
||||
return growl.error(_t('cart.please_select_a_member_first'));
|
||||
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
|
||||
} else if (!areFutureSlots){
|
||||
return growl.error(_t('app.shared.cart.unable_to_select_plan_if_slots_in_the_past'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -127,25 +166,58 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
$scope.payCart = function () {
|
||||
// first, we check that a user was selected
|
||||
if (Object.keys($scope.user).length > 0) {
|
||||
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
|
||||
|
||||
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
|
||||
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
|
||||
if (!$scope.isAdmin() && (amountToPay > 0)) {
|
||||
if ($rootScope.fablabWithoutOnlinePayment) {
|
||||
growl.error(_t('cart.online_payment_disabled'));
|
||||
// check user was selected a plan if slot is restricted for subscriptions
|
||||
const slotValidations = [];
|
||||
let slotNotValid;
|
||||
let slotNotValidError;
|
||||
$scope.events.reserved.forEach(function (slot) {
|
||||
if (slot.plan_ids.length > 0) {
|
||||
if (
|
||||
($scope.selectedPlan && _.include(slot.plan_ids, $scope.selectedPlan.id)) ||
|
||||
($scope.user.subscribed_plan && _.include(slot.plan_ids, $scope.user.subscribed_plan.id))
|
||||
) {
|
||||
slotValidations.push(true);
|
||||
} else {
|
||||
return payByStripe(reservation);
|
||||
}
|
||||
} else {
|
||||
if ($scope.isAdmin() || (amountToPay === 0)) {
|
||||
return payOnSite(reservation);
|
||||
slotNotValid = slot;
|
||||
if ($scope.selectedPlan && !_.include(slot.plan_ids, $scope.selectedPlan.id)) {
|
||||
slotNotValidError = 'selectedPlanError';
|
||||
}
|
||||
if ($scope.user.subscribed_plan && !_.include(slot.plan_ids, $scope.user.subscribed_plan.id)) {
|
||||
slotNotValidError = 'userPlanError';
|
||||
}
|
||||
if (!$scope.selectedPlan || !$scope.user.subscribed_plan) {
|
||||
slotNotValidError = 'noPlanError';
|
||||
}
|
||||
slotValidations.push(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
const hasPlanForSlot = slotValidations.every(function (a) { return a; });
|
||||
if (!hasPlanForSlot) {
|
||||
if (!$scope.isAdmin()) {
|
||||
return growl.error(_t('app.shared.cart.slot_restrict_subscriptions_must_select_plan'));
|
||||
} else {
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "shared/_reserve_slot_without_plan.html" %>',
|
||||
size: 'md',
|
||||
controller: 'ReserveSlotWithoutPlanController',
|
||||
resolve: {
|
||||
slot: function() { return slotNotValid; },
|
||||
slotNotValidError: function() { return slotNotValidError; },
|
||||
}
|
||||
});
|
||||
modalInstance.result.then(function(res) {
|
||||
return paySlots();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return paySlots();
|
||||
}
|
||||
} else {
|
||||
// otherwise we alert, this error musn't occur when the current user is not admin
|
||||
return growl.error(_t('cart.please_select_a_member_first'));
|
||||
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -173,7 +245,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
return $scope.events.modifiable = null;
|
||||
}
|
||||
, function (err) { // failure
|
||||
growl.error(_t('cart.unable_to_change_the_reservation'));
|
||||
growl.error(_t('app.shared.cart.unable_to_change_the_reservation'));
|
||||
return console.error(err);
|
||||
});
|
||||
};
|
||||
@ -255,6 +327,27 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
*/
|
||||
var slotSelectionChanged = function () {
|
||||
if ($scope.slot) {
|
||||
// build a list of plans if this slot is restricted for subscriptions
|
||||
if ($scope.slot.plan_ids.length > 0) {
|
||||
const _plans = _.filter($scope.plans, function (p) { return _.include($scope.slot.plan_ids, p.id) });
|
||||
$scope.slot.plansGrouped = [];
|
||||
$scope.slot.group_ids = [];
|
||||
for (let group of Array.from($scope.groups)) {
|
||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||
for (let plan of Array.from(_plans)) {
|
||||
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
||||
}
|
||||
if (groupObj.plans.length > 0) {
|
||||
if ($scope.isAdmin()) {
|
||||
$scope.slot.plansGrouped.push(groupObj);
|
||||
} else if ($scope.user.group_id === groupObj.id) {
|
||||
$scope.slot.plansGrouped.push(groupObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
$scope.slot.group_ids = $scope.slot.plansGrouped.map(function(g) { return g.id; });
|
||||
}
|
||||
|
||||
if (!$scope.slot.is_reserved && !$scope.events.modifiable && !$scope.slot.is_completed) {
|
||||
// slot is not reserved and we are not currently modifying a slot
|
||||
// -> can be added to cart or removed if already present
|
||||
@ -315,19 +408,19 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('cart.confirmation_required'),
|
||||
msg: _t('cart.do_you_really_want_to_cancel_this_reservation')
|
||||
title: _t('app.shared.cart.confirmation_required'),
|
||||
msg: _t('app.shared.cart.do_you_really_want_to_cancel_this_reservation')
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () { // cancel confirmed
|
||||
Slot.cancel({ id: $scope.slot.id }, function () { // successfully canceled
|
||||
growl.success(_t('cart.reservation_was_cancelled_successfully'));
|
||||
growl.success(_t('app.shared.cart.reservation_was_cancelled_successfully'));
|
||||
if (typeof $scope.onSlotCancelSuccess === 'function') { return $scope.onSlotCancelSuccess(); }
|
||||
}
|
||||
, function () { // error while canceling
|
||||
growl.error(_t('cart.cancellation_failed'));
|
||||
growl.error(_t('app.shared.cart.cancellation_failed'));
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -403,7 +496,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
});
|
||||
} else {
|
||||
// otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.warning(_t('cart.please_select_a_member_first'));
|
||||
growl.warning(_t('app.shared.cart.please_select_a_member_first'));
|
||||
return $scope.amountTotal = null;
|
||||
}
|
||||
};
|
||||
@ -556,12 +649,12 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
|
||||
// Button label
|
||||
if ($scope.amount > 0) {
|
||||
$scope.validButtonName = _t('cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) }, 'messageformat');
|
||||
$scope.validButtonName = _t('app.shared.cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
|
||||
} else {
|
||||
if ((price.price > 0) && ($scope.walletAmount === 0)) {
|
||||
$scope.validButtonName = _t('cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')(price.price) }, 'messageformat');
|
||||
$scope.validButtonName = _t('app.shared.cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')(price.price) });
|
||||
} else {
|
||||
$scope.validButtonName = _t('confirm');
|
||||
$scope.validButtonName = _t('app.shared.buttons.confirm');
|
||||
}
|
||||
}
|
||||
|
||||
@ -576,7 +669,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
}
|
||||
, function (response) {
|
||||
$scope.alerts = [];
|
||||
$scope.alerts.push({ msg: _t('cart.a_problem_occurred_during_the_payment_process_please_try_again_later'), type: 'danger' });
|
||||
$scope.alerts.push({ msg: _t('app.shared.cart.a_problem_occurred_during_the_payment_process_please_try_again_later'), type: 'danger' });
|
||||
return $scope.attempting = false;
|
||||
});
|
||||
};
|
||||
@ -601,9 +694,75 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
return $scope.selectedPlan = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Actions to pay slots
|
||||
*/
|
||||
var paySlots = function() {
|
||||
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
|
||||
|
||||
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
|
||||
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
|
||||
if (!$scope.isAdmin() && (amountToPay > 0)) {
|
||||
if ($rootScope.fablabWithoutOnlinePayment) {
|
||||
growl.error(_t('app.shared.cart.online_payment_disabled'));
|
||||
} else {
|
||||
return payByStripe(reservation);
|
||||
}
|
||||
} else {
|
||||
if ($scope.isAdmin() || (amountToPay === 0)) {
|
||||
return payOnSite(reservation);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the directive
|
||||
return initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller of modal for show reservations the same date at the same time
|
||||
*/
|
||||
Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'sameTimeReservations', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, sameTimeReservations, growl, _t) {
|
||||
$scope.sameTimeReservations = sameTimeReservations;
|
||||
$scope.bookSlotAtSameTime = Fablab.bookSlotAtSameTime;
|
||||
/**
|
||||
* Confirmation callback
|
||||
*/
|
||||
$scope.ok = function () {
|
||||
$uibModalInstance.close({});
|
||||
}
|
||||
/**
|
||||
* Cancellation callback
|
||||
*/
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Controller used to alert admin reserve slot without plan
|
||||
*/
|
||||
Application.Controllers.controller('ReserveSlotWithoutPlanController', ['$scope', '$uibModalInstance', 'slot', 'slotNotValidError', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, slot, slotNotValidError, growl, _t) {
|
||||
$scope.slot = slot;
|
||||
$scope.slotNotValidError = slotNotValidError;
|
||||
/**
|
||||
* Confirmation callback
|
||||
*/
|
||||
$scope.ok = function () {
|
||||
$uibModalInstance.close({});
|
||||
}
|
||||
/**
|
||||
* Cancellation callback
|
||||
*/
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
21
app/assets/javascripts/directives/compile.js
Normal file
21
app/assets/javascripts/directives/compile.js
Normal file
@ -0,0 +1,21 @@
|
||||
Application.Directives.directive('compile', ['$compile', function ($compile) {
|
||||
return function (scope, element, attrs) {
|
||||
scope.$watch(
|
||||
function (scope) {
|
||||
// watch the 'compile' expression for changes
|
||||
return scope.$eval(attrs.compile);
|
||||
},
|
||||
function (value) {
|
||||
// when the 'compile' expression changes
|
||||
// assign it into the current DOM
|
||||
element.html(value);
|
||||
|
||||
// compile the new DOM and link it to the current
|
||||
// scope.
|
||||
// NOTE: we only compile .childNodes so that
|
||||
// we don't get into infinite loop compiling ourselves
|
||||
$compile(element.contents())(scope);
|
||||
}
|
||||
);
|
||||
};
|
||||
}]);
|
@ -53,15 +53,15 @@ Application.Directives.directive('coupon', [ '$rootScope', 'Coupon', '_t', funct
|
||||
$scope.status = 'valid';
|
||||
$scope.coupon = res;
|
||||
if (res.type === 'percent_off') {
|
||||
return $scope.messages.push({ type: 'success', message: _t('the_coupon_has_been_applied_you_get_PERCENT_discount', { PERCENT: res.percent_off }) });
|
||||
return $scope.messages.push({ type: 'success', message: _t('app.shared.coupon_input.the_coupon_has_been_applied_you_get_PERCENT_discount', { PERCENT: res.percent_off }) });
|
||||
} else {
|
||||
return $scope.messages.push({ type: 'success', message: _t('the_coupon_has_been_applied_you_get_AMOUNT_CURRENCY', { AMOUNT: res.amount_off, CURRENCY: $rootScope.currencySymbol }) });
|
||||
return $scope.messages.push({ type: 'success', message: _t('app.shared.coupon_input.the_coupon_has_been_applied_you_get_AMOUNT_CURRENCY', { AMOUNT: res.amount_off, CURRENCY: $rootScope.currencySymbol }) });
|
||||
}
|
||||
}
|
||||
, function (err) {
|
||||
$scope.status = 'invalid';
|
||||
$scope.coupon = null;
|
||||
return $scope.messages.push({ type: 'danger', message: _t(`unable_to_apply_the_coupon_because_${err.data.status}`) });
|
||||
return $scope.messages.push({ type: 'danger', message: _t(`app.shared.coupon_input.unable_to_apply_the_coupon_because_${err.data.status}`) });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
31
app/assets/javascripts/directives/events.js.erb
Normal file
31
app/assets/javascripts/directives/events.js.erb
Normal file
@ -0,0 +1,31 @@
|
||||
Application.Directives.directive('events', [ 'Event',
|
||||
function (Event) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
templateUrl: '<%= asset_path "home/events.html" %>',
|
||||
link ($scope, element, attributes) {
|
||||
// The closest upcoming events
|
||||
$scope.upcomingEvents = null;
|
||||
|
||||
/**
|
||||
* Test if the provided event run on a single day or not
|
||||
* @param event {Object} single event from the $scope.upcomingEvents array
|
||||
* @returns {boolean} false if the event runs on more that 1 day
|
||||
*/
|
||||
$scope.isOneDayEvent = function(event) {
|
||||
return moment(event.start_date).isSame(event.end_date, 'day');
|
||||
}
|
||||
|
||||
// constructor
|
||||
const initialize = function () {
|
||||
Event.upcoming({ limit: 3 }, function (data) {
|
||||
$scope.upcomingEvents = data;
|
||||
})
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the directive
|
||||
return initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
22
app/assets/javascripts/directives/members.js.erb
Normal file
22
app/assets/javascripts/directives/members.js.erb
Normal file
@ -0,0 +1,22 @@
|
||||
Application.Directives.directive('members', [ 'Member',
|
||||
function (Member) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
templateUrl: '<%= asset_path "home/members.html" %>',
|
||||
link ($scope, element, attributes) {
|
||||
// The last registered members who confirmed their addresses
|
||||
$scope.lastMembers = null;
|
||||
|
||||
// constructor
|
||||
const initialize = function () {
|
||||
Member.lastSubscribed({ limit: 4 }, function (data) {
|
||||
$scope.lastMembers = data;
|
||||
})
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the directive
|
||||
return initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
22
app/assets/javascripts/directives/news.js.erb
Normal file
22
app/assets/javascripts/directives/news.js.erb
Normal file
@ -0,0 +1,22 @@
|
||||
Application.Directives.directive('news', [ 'Setting',
|
||||
function (Setting) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
templateUrl: '<%= asset_path "home/news.html" %>',
|
||||
link ($scope, element, attributes) {
|
||||
// The admin blogpost
|
||||
$scope.homeBlogpost = null;
|
||||
|
||||
// constructor
|
||||
const initialize = function () {
|
||||
Setting.get({ name: 'home_blogpost' }, function (data) {
|
||||
$scope.homeBlogpost = data.setting.value;
|
||||
})
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the directive
|
||||
return initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
12
app/assets/javascripts/directives/post_render.js
Normal file
12
app/assets/javascripts/directives/post_render.js
Normal file
@ -0,0 +1,12 @@
|
||||
Application.Directives.directive('postRender', [ '$timeout',
|
||||
function ($timeout) {
|
||||
return ({
|
||||
restrict: 'A',
|
||||
terminal: false,
|
||||
transclude: false,
|
||||
link: function (scope, element, attrs) {
|
||||
$timeout(scope[attrs.postRender], 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
25
app/assets/javascripts/directives/projects.js.erb
Normal file
25
app/assets/javascripts/directives/projects.js.erb
Normal file
@ -0,0 +1,25 @@
|
||||
Application.Directives.directive('projects', [ 'Project',
|
||||
function (Project) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
templateUrl: '<%= asset_path "home/projects.html" %>',
|
||||
link ($scope, element, attributes) {
|
||||
// The last projects published/documented on the platform
|
||||
$scope.lastProjects = null;
|
||||
|
||||
// The default slide shown in the carousel
|
||||
$scope.activeSlide = 0;
|
||||
|
||||
// constructor
|
||||
const initialize = function () {
|
||||
Project.lastPublished(function (data) {
|
||||
$scope.lastProjects = data;
|
||||
})
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the directive
|
||||
return initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
@ -1,14 +1,3 @@
|
||||
/* eslint-disable
|
||||
no-return-assign,
|
||||
no-undef,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
@ -36,9 +25,9 @@ Application.Directives.directive('selectMember', [ 'Diacritics', 'Member', funct
|
||||
q['subscription'] = attributes.subscription;
|
||||
}
|
||||
|
||||
return Member.search(q, function (users) {
|
||||
Member.search(q, function (users) {
|
||||
scope.matchingMembers = users;
|
||||
return scope.isLoadingMembers = false;
|
||||
scope.isLoadingMembers = false;
|
||||
}
|
||||
, function (error) { console.error(error); });
|
||||
};
|
||||
|
@ -73,7 +73,7 @@ Application.Directives.directive('stripeForm', ['Payment', 'growl', '_t',
|
||||
if (response.error.statusText) {
|
||||
growl.error(response.error.statusText);
|
||||
} else {
|
||||
growl.error(`${_t('payment_card_error')} ${response.error}`);
|
||||
growl.error(`${_t('app.shared.messages.payment_card_error')} ${response.error}`);
|
||||
}
|
||||
confirmButton.prop('disabled', false);
|
||||
} else if (response.requires_action) {
|
||||
|
43
app/assets/javascripts/directives/twitter.js.erb
Normal file
43
app/assets/javascripts/directives/twitter.js.erb
Normal file
@ -0,0 +1,43 @@
|
||||
/* global twitterFetcher */
|
||||
|
||||
/**
|
||||
* This directive will show the last tweet.
|
||||
* Usage: <twitter />
|
||||
*/
|
||||
Application.Directives.directive('twitter', ['Setting',
|
||||
function (Setting) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
templateUrl: '<%= asset_path "home/twitter.html" %>',
|
||||
link ($scope, element, attributes) {
|
||||
// Twitter username
|
||||
$scope.twitterName = null;
|
||||
|
||||
// constructor
|
||||
const initialize = function () {
|
||||
Setting.get({ name: 'twitter_name' }, function (data) {
|
||||
$scope.twitterName = data.setting.value;
|
||||
if ($scope.twitterName) {
|
||||
const configProfile = {
|
||||
'profile': { 'screenName': $scope.twitterName },
|
||||
'domId': 'twitter',
|
||||
'maxTweets': 1,
|
||||
'enableLinks': true,
|
||||
'showUser': false,
|
||||
'showTime': true,
|
||||
'showImages': false,
|
||||
'showRetweet': true,
|
||||
'showInteraction': false,
|
||||
'lang': Fablab.locale
|
||||
};
|
||||
twitterFetcher.fetch(configProfile);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the directive
|
||||
return initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
@ -318,22 +318,12 @@ Application.Filters.filter('toIsoDate', [function () {
|
||||
};
|
||||
}]);
|
||||
|
||||
Application.Filters.filter('booleanFormat', [ '_t', function (_t) {
|
||||
return function (boolean) {
|
||||
if (boolean || (boolean === 'true')) {
|
||||
return _t('yes');
|
||||
} else {
|
||||
return _t('no');
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
Application.Filters.filter('booleanFormat', [ '_t', function (_t) {
|
||||
return function (boolean) {
|
||||
if (((typeof boolean === 'boolean') && boolean) || ((typeof boolean === 'string') && (boolean === 'true'))) {
|
||||
return _t('yes');
|
||||
return _t('app.shared.buttons.yes');
|
||||
} else {
|
||||
return _t('no');
|
||||
return _t('app.shared.buttons.no');
|
||||
}
|
||||
};
|
||||
}]);
|
||||
@ -341,7 +331,7 @@ Application.Filters.filter('booleanFormat', [ '_t', function (_t) {
|
||||
Application.Filters.filter('maxCount', [ '_t', function (_t) {
|
||||
return function (max) {
|
||||
if ((typeof max === 'undefined') || (max === null) || ((typeof max === 'number') && (max === 0))) {
|
||||
return _t('unlimited');
|
||||
return _t('app.admin.pricing.unlimited');
|
||||
} else {
|
||||
return max;
|
||||
}
|
||||
|
@ -36,18 +36,21 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
logoFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-file' }).$promise; }],
|
||||
logoBlackFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-black-file' }).$promise; }],
|
||||
commonTranslations: ['Translations', function (Translations) { return Translations.query(['app.public.common', 'app.shared.buttons', 'app.shared.elements']).$promise; }]
|
||||
sharedTranslations: ['Translations', function (Translations) { return Translations.query(['app.shared', 'app.public.common']).$promise; }]
|
||||
},
|
||||
onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'CSRF', function ($rootScope, logoFile, logoBlackFile, CSRF) {
|
||||
// Retrieve Anti-CSRF tokens from cookies
|
||||
CSRF.setMetaTags();
|
||||
// Application logo
|
||||
$rootScope.logo = logoFile.custom_asset;
|
||||
return $rootScope.logoBlack = logoBlackFile.custom_asset;
|
||||
$rootScope.logoBlack = logoBlackFile.custom_asset;
|
||||
}]
|
||||
})
|
||||
.state('app.public', {
|
||||
abstract: true
|
||||
abstract: true,
|
||||
resolve: {
|
||||
publicTranslations: ['Translations', function (Translations) { return Translations.query(['app.public']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged', {
|
||||
abstract: true,
|
||||
@ -55,7 +58,8 @@ angular.module('application.router', ['ui.router'])
|
||||
authorizedRoles: ['member', 'admin']
|
||||
},
|
||||
resolve: {
|
||||
currentUser: ['Auth', function (Auth) { return Auth.currentUser(); }]
|
||||
currentUser: ['Auth', function (Auth) { return Auth.currentUser(); }],
|
||||
loggedTranslations: ['Translations', function (Translations) { return Translations.query(['app.logged']).$promise; }]
|
||||
},
|
||||
onEnter: ['$state', '$timeout', 'currentUser', '$rootScope', function ($state, $timeout, currentUser, $rootScope) {
|
||||
$rootScope.currentUser = currentUser;
|
||||
@ -67,7 +71,8 @@ angular.module('application.router', ['ui.router'])
|
||||
authorizedRoles: ['admin']
|
||||
},
|
||||
resolve: {
|
||||
currentUser: ['Auth', function (Auth) { return Auth.currentUser(); }]
|
||||
currentUser: ['Auth', function (Auth) { return Auth.currentUser(); }],
|
||||
adminTranslations: ['Translations', function (Translations) { return Translations.query(['app.admin']).$promise; }]
|
||||
},
|
||||
onEnter: ['$state', '$timeout', 'currentUser', '$rootScope', function ($state, $timeout, currentUser, $rootScope) {
|
||||
$rootScope.currentUser = currentUser;
|
||||
@ -82,9 +87,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "shared/about.html" %>',
|
||||
controller: 'AboutController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.about').$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.home', {
|
||||
@ -96,12 +98,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
lastMembersPromise: ['Member', function (Member) { return Member.lastSubscribed({ limit: 4 }).$promise; }],
|
||||
lastProjectsPromise: ['Project', function (Project) { return Project.lastPublished().$promise; }],
|
||||
upcomingEventsPromise: ['Event', function (Event) { return Event.upcoming({ limit: 3 }).$promise; }],
|
||||
homeBlogpostPromise: ['Setting', function (Setting) { return Setting.get({ name: 'home_blogpost' }).$promise; }],
|
||||
twitterNamePromise: ['Setting', function (Setting) { return Setting.get({ name: 'twitter_name' }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.home').$promise; }]
|
||||
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['home_content', 'home_blogpost']" }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.privacy', {
|
||||
@ -111,9 +108,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "shared/privacy.html" %>',
|
||||
controller: 'PrivacyController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.privacy').$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -132,7 +126,6 @@ angular.module('application.router', ['ui.router'])
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
|
||||
cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }],
|
||||
memberPromise: ['Member', 'currentUser', function (Member, currentUser) { return Member.get({ id: currentUser.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.logged.profileCompletion', 'app.shared.user']).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -151,9 +144,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "dashboard/profile.html" %>',
|
||||
controller: 'DashboardController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.logged.dashboard.profile', 'app.shared.public_profile']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.dashboard.settings', {
|
||||
@ -167,7 +157,6 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
groups: ['Group', function (Group) { return Group.query().$promise; }],
|
||||
activeProviderPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.active().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.logged.dashboard.settings', 'app.shared.user']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.dashboard.projects', {
|
||||
@ -177,9 +166,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "dashboard/projects.html" %>',
|
||||
controller: 'DashboardController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.logged.dashboard.projects').$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.dashboard.trainings', {
|
||||
@ -189,9 +175,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "dashboard/trainings.html" %>',
|
||||
controller: 'DashboardController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.logged.dashboard.trainings').$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.dashboard.events', {
|
||||
@ -201,9 +184,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "dashboard/events.html" %>',
|
||||
controller: 'DashboardController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.logged.dashboard.events').$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.dashboard.invoices', {
|
||||
@ -213,9 +193,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "dashboard/invoices.html" %>',
|
||||
controller: 'DashboardController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.logged.dashboard.invoices').$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.dashboard.wallet', {
|
||||
@ -228,8 +205,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
walletPromise: ['Wallet', 'currentUser', function (Wallet, currentUser) { return Wallet.getWalletByUser({ user_id: currentUser.id }).$promise; }],
|
||||
transactionsPromise: ['Wallet', 'walletPromise', function (Wallet, walletPromise) { return Wallet.transactions({ id: walletPromise.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.shared.wallet']).$promise; }]
|
||||
transactionsPromise: ['Wallet', 'walletPromise', function (Wallet, walletPromise) { return Wallet.transactions({ id: walletPromise.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -243,8 +219,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
memberPromise: ['$stateParams', 'Member', function ($stateParams, Member) { return Member.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.logged.members_show', 'app.shared.public_profile']).$promise; }]
|
||||
memberPromise: ['$stateParams', 'Member', function ($stateParams, Member) { return Member.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.members', {
|
||||
@ -256,8 +231,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
membersPromise: ['Member', function (Member) { return Member.query({ requested_attributes: '[profile]', page: 1, size: 10 }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.logged.members').$promise; }]
|
||||
membersPromise: ['Member', function (Member) { return Member.query({ requested_attributes: '[profile]', page: 1, size: 10 }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -273,8 +247,7 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
|
||||
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.projects_list').$promise; }]
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.projects_new', {
|
||||
@ -286,8 +259,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.logged.projects_new', 'app.shared.project']).$promise; }]
|
||||
allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.projects_show', {
|
||||
@ -299,8 +271,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
projectPromise: ['$stateParams', 'Project', function ($stateParams, Project) { return Project.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.projects_show').$promise; }]
|
||||
projectPromise: ['$stateParams', 'Project', function ($stateParams, Project) { return Project.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.projects_edit', {
|
||||
@ -313,8 +284,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
projectPromise: ['$stateParams', 'Project', function ($stateParams, Project) { return Project.get({ id: $stateParams.id }).$promise; }],
|
||||
allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.logged.projects_edit', 'app.shared.project']).$promise; }]
|
||||
allowedExtensions: ['Project', function (Project) { return Project.allowedExtensions().$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -328,8 +298,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.public.machines_list', 'app.shared.training_reservation_modal', 'app.shared.request_training_modal']).$promise; }]
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.machines_new', {
|
||||
@ -339,9 +308,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "machines/new.html" %>',
|
||||
controller: 'NewMachineController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.machines_new', 'app.shared.machine']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.machines_show', {
|
||||
@ -353,8 +319,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
machinePromise: ['Machine', '$stateParams', function (Machine, $stateParams) { return Machine.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.public.machines_show', 'app.shared.training_reservation_modal', 'app.shared.request_training_modal']).$promise; }]
|
||||
machinePromise: ['Machine', '$stateParams', function (Machine, $stateParams) { return Machine.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.machines_reserve', {
|
||||
@ -380,11 +345,6 @@ angular.module('application.router', ['ui.router'])
|
||||
'booking_cancel_delay', \
|
||||
'subscription_explications_alert']`
|
||||
}).$promise;
|
||||
}],
|
||||
translations: ['Translations', function (Translations) {
|
||||
return Translations.query(['app.logged.machines_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select',
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise;
|
||||
}]
|
||||
}
|
||||
})
|
||||
@ -397,8 +357,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
machinePromise: ['Machine', '$stateParams', function (Machine, $stateParams) { return Machine.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.machines_edit', 'app.shared.machine']).$promise; }]
|
||||
machinePromise: ['Machine', '$stateParams', function (Machine, $stateParams) { return Machine.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -413,8 +372,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.public.spaces_list']).$promise; }]
|
||||
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.space_new', {
|
||||
@ -425,9 +383,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "spaces/new.html" %>',
|
||||
controller: 'NewSpaceController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.space_new', 'app.shared.space']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.space_show', {
|
||||
@ -440,8 +395,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
spacePromise: ['Space', '$stateParams', function (Space, $stateParams) { return Space.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.public.space_show']).$promise; }]
|
||||
spacePromise: ['Space', '$stateParams', function (Space, $stateParams) { return Space.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.space_edit', {
|
||||
@ -454,8 +408,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
spacePromise: ['Space', '$stateParams', function (Space, $stateParams) { return Space.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.space_edit', 'app.shared.space']).$promise; }]
|
||||
spacePromise: ['Space', '$stateParams', function (Space, $stateParams) { return Space.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.space_reserve', {
|
||||
@ -482,11 +435,6 @@ angular.module('application.router', ['ui.router'])
|
||||
'booking_cancel_delay', \
|
||||
'subscription_explications_alert', \
|
||||
'space_explications_alert']` }).$promise;
|
||||
}],
|
||||
translations: ['Translations', function (Translations) {
|
||||
return Translations.query(['app.logged.space_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select',
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise;
|
||||
}]
|
||||
}
|
||||
})
|
||||
@ -501,8 +449,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
trainingsPromise: ['Training', function (Training) { return Training.query({ public_page: true }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.public.trainings_list']).$promise; }]
|
||||
trainingsPromise: ['Training', function (Training) { return Training.query({ public_page: true }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.training_show', {
|
||||
@ -514,8 +461,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
trainingPromise: ['Training', '$stateParams', function (Training, $stateParams) { return Training.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.public.training_show']).$promise; }]
|
||||
trainingPromise: ['Training', '$stateParams', function (Training, $stateParams) { return Training.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.logged.trainings_reserve', {
|
||||
@ -545,11 +491,6 @@ angular.module('application.router', ['ui.router'])
|
||||
'subscription_explications_alert', \
|
||||
'training_explications_alert', \
|
||||
'training_information_message']` }).$promise;
|
||||
}],
|
||||
translations: ['Translations', function (Translations) {
|
||||
return Translations.query(['app.logged.trainings_reserve', 'app.shared.plan_subscribe', 'app.shared.member_select',
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise;
|
||||
}]
|
||||
}
|
||||
})
|
||||
@ -561,9 +502,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "notifications/index.html.erb" %>',
|
||||
controller: 'NotificationsController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.logged.notifications').$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -580,11 +518,7 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
subscriptionExplicationsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'subscription_explications_alert' }).$promise; }],
|
||||
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) {
|
||||
return Translations.query(['app.public.plans', 'app.shared.member_select', 'app.shared.stripe', 'app.shared.wallet',
|
||||
'app.shared.coupon_input']).$promise;
|
||||
}]
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -600,8 +534,7 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
categoriesPromise: ['Category', function (Category) { return Category.query().$promise; }],
|
||||
themesPromise: ['EventTheme', function (EventTheme) { return EventTheme.query().$promise; }],
|
||||
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.public.events_list').$promise; }]
|
||||
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.public.events_show', {
|
||||
@ -615,11 +548,7 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
eventPromise: ['Event', '$stateParams', function (Event, $stateParams) { return Event.get({ id: $stateParams.id }).$promise; }],
|
||||
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }],
|
||||
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['booking_move_enable', 'booking_move_delay', 'event_explications_alert']" }).$promise; }],
|
||||
translations: ['Translations', function (Translations) {
|
||||
return Translations.query(['app.public.events_show', 'app.shared.member_select', 'app.shared.stripe',
|
||||
'app.shared.valid_reservation_modal', 'app.shared.wallet', 'app.shared.coupon_input']).$promise;
|
||||
}]
|
||||
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', 'booking_cancel_delay', 'event_explications_alert']" }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -638,7 +567,7 @@ angular.module('application.router', ['ui.router'])
|
||||
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }]
|
||||
iCalendarPromise: ['ICalendar', function (ICalendar) { return ICalendar.query().$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -656,7 +585,20 @@ angular.module('application.router', ['ui.router'])
|
||||
bookingWindowStart: ['Setting', function (Setting) { return Setting.get({ name: 'booking_window_start' }).$promise; }],
|
||||
bookingWindowEnd: ['Setting', function (Setting) { return Setting.get({ name: 'booking_window_end' }).$promise; }],
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.calendar').$promise; }]
|
||||
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.calendar.icalendar', {
|
||||
url: '/admin/calendar/icalendar',
|
||||
views: {
|
||||
'main@': {
|
||||
templateUrl: '<%= asset_path "admin/calendar/icalendar.html" %>',
|
||||
controller: 'AdminICalendarController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
iCalendars: ['ICalendar', function (ICalendar) { return ICalendar.query().$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -672,8 +614,7 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
|
||||
licencesPromise: ['Licence', function (Licence) { return Licence.query().$promise; }],
|
||||
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.project_elements').$promise; }]
|
||||
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.manage_abuses', {
|
||||
@ -685,8 +626,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
abusesPromise: ['Abuse', function(Abuse) { return Abuse.query().$promise; }],
|
||||
translations: ['Translations', function(Translations) { return Translations.query('app.admin.manage_abuses').$promise; }]
|
||||
abusesPromise: ['Abuse', function(Abuse) { return Abuse.query().$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -701,8 +641,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.trainings', 'app.shared.trainings']).$promise; }]
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.trainings_new', {
|
||||
@ -714,8 +653,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.trainings_new', 'app.shared.trainings']).$promise; }]
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.trainings_edit', {
|
||||
@ -728,8 +666,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
trainingPromise: ['Training', '$stateParams', function (Training, $stateParams) { return Training.get({ id: $stateParams.id }).$promise; }],
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.shared.trainings').$promise; }]
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
|
||||
}
|
||||
})
|
||||
// events
|
||||
@ -746,8 +683,7 @@ angular.module('application.router', ['ui.router'])
|
||||
categoriesPromise: ['Category', function (Category) { return Category.query().$promise; }],
|
||||
themesPromise: ['EventTheme', function (EventTheme) { return EventTheme.query().$promise; }],
|
||||
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }],
|
||||
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.events').$promise; }]
|
||||
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.events_new', {
|
||||
@ -762,8 +698,7 @@ angular.module('application.router', ['ui.router'])
|
||||
categoriesPromise: ['Category', function (Category) { return Category.query().$promise; }],
|
||||
themesPromise: ['EventTheme', function (EventTheme) { return EventTheme.query().$promise; }],
|
||||
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }],
|
||||
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.events_new', 'app.shared.event']).$promise; }]
|
||||
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.events_edit', {
|
||||
@ -779,8 +714,7 @@ angular.module('application.router', ['ui.router'])
|
||||
categoriesPromise: ['Category', function (Category) { return Category.query().$promise; }],
|
||||
themesPromise: ['EventTheme', function (EventTheme) { return EventTheme.query().$promise; }],
|
||||
ageRangesPromise: ['AgeRange', function (AgeRange) { return AgeRange.query().$promise; }],
|
||||
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.events_edit', 'app.shared.event']).$promise; }]
|
||||
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.event_reservations', {
|
||||
@ -793,8 +727,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
eventPromise: ['Event', '$stateParams', function (Event, $stateParams) { return Event.get({ id: $stateParams.id }).$promise; }],
|
||||
reservationsPromise: ['Reservation', '$stateParams', function (Reservation, $stateParams) { return Reservation.query({ reservable_id: $stateParams.id, reservable_type: 'Event' }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.event_reservations').$promise; }]
|
||||
reservationsPromise: ['Reservation', '$stateParams', function (Reservation, $stateParams) { return Reservation.query({ reservable_id: $stateParams.id, reservable_type: 'Event' }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -812,7 +745,6 @@ angular.module('application.router', ['ui.router'])
|
||||
groups: ['Group', function (Group) { return Group.query().$promise; }],
|
||||
machinesPricesPromise: ['Price', function (Price) { return Price.query({ priceable_type: 'Machine', plan_id: 'null' }).$promise; }],
|
||||
trainingsPricingsPromise: ['TrainingsPricing', function (TrainingsPricing) { return TrainingsPricing.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.pricing', 'app.shared.member_select', 'app.shared.coupon']).$promise; }],
|
||||
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
|
||||
machineCreditsPromise: ['Credit', function (Credit) { return Credit.query({ creditable_type: 'Machine' }).$promise; }],
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
@ -840,9 +772,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "admin/plans/new.html" %>',
|
||||
controller: 'NewPlanController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.plans.new', 'app.shared.plan']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.plans.edit', {
|
||||
@ -857,8 +786,7 @@ angular.module('application.router', ['ui.router'])
|
||||
spaces: ['Space', function (Space) { return Space.query().$promise; }],
|
||||
machines: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||
plans: ['Plan', function (Plan) { return Plan.query().$promise; }],
|
||||
planPromise: ['Plan', '$stateParams', function (Plan, $stateParams) { return Plan.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.plans.edit', 'app.shared.plan']).$promise; }]
|
||||
planPromise: ['Plan', '$stateParams', function (Plan, $stateParams) { return Plan.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -870,9 +798,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "admin/coupons/new.html" %>',
|
||||
controller: 'NewCouponController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.coupons_new', 'app.shared.coupon']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.coupons_edit', {
|
||||
@ -884,8 +809,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
couponPromise: ['Coupon', '$stateParams', function (Coupon, $stateParams) { return Coupon.get({ id: $stateParams.id }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.coupons_edit', 'app.shared.coupon']).$promise; }]
|
||||
couponPromise: ['Coupon', '$stateParams', function (Coupon, $stateParams) { return Coupon.get({ id: $stateParams.id }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -914,8 +838,7 @@ angular.module('application.router', ['ui.router'])
|
||||
query: { number: '', customer: '', date: null, order_by: '-reference', page: 1, size: 20 }
|
||||
}).$promise;
|
||||
}],
|
||||
closedPeriods: [ 'AccountingPeriod', function(AccountingPeriod) { return AccountingPeriod.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.invoices').$promise; }]
|
||||
closedPeriods: [ 'AccountingPeriod', function(AccountingPeriod) { return AccountingPeriod.query().$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -945,8 +868,7 @@ angular.module('application.router', ['ui.router'])
|
||||
adminsPromise: ['Admin', function (Admin) { return Admin.query().$promise; }],
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
|
||||
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
|
||||
authProvidersPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.members').$promise; }]
|
||||
authProvidersPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.members_new', {
|
||||
@ -956,9 +878,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "admin/members/new.html" %>',
|
||||
controller: 'NewMemberController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.members_new', 'app.shared.user', 'app.shared.user_admin']).$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.members_import', {
|
||||
@ -970,7 +889,6 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.members_import', 'app.shared.user', 'app.shared.user_admin']).$promise; }],
|
||||
tags: ['Tag', function(Tag) { return Tag.query().$promise }]
|
||||
}
|
||||
})
|
||||
@ -983,7 +901,6 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.members_import_result', 'app.shared.user', 'app.shared.user_admin']).$promise; }],
|
||||
importItem: ['Import', '$stateParams', function(Import, $stateParams) { return Import.get({ id: $stateParams.id }).$promise }]
|
||||
}
|
||||
})
|
||||
@ -1000,8 +917,7 @@ angular.module('application.router', ['ui.router'])
|
||||
activeProviderPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.active().$promise; }],
|
||||
walletPromise: ['Wallet', '$stateParams', function (Wallet, $stateParams) { return Wallet.getWalletByUser({ user_id: $stateParams.id }).$promise; }],
|
||||
transactionsPromise: ['Wallet', 'walletPromise', function (Wallet, walletPromise) { return Wallet.transactions({ id: walletPromise.id }).$promise; }],
|
||||
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.members_edit', 'app.shared.user', 'app.shared.user_admin', 'app.shared.wallet']).$promise; }]
|
||||
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.admins_new', {
|
||||
@ -1011,9 +927,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "admin/admins/new.html" %>',
|
||||
controller: 'NewAdminController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.admins_new').$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -1028,8 +941,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
mappingFieldsPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.mapping_fields().$promise; }],
|
||||
authProvidersPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.authentication_new', 'app.shared.authentication', 'app.shared.oauth2']).$promise; }]
|
||||
authProvidersPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.authentication_edit', {
|
||||
@ -1042,8 +954,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
providerPromise: ['AuthProvider', '$stateParams', function (AuthProvider, $stateParams) { return AuthProvider.get({ id: $stateParams.id }).$promise; }],
|
||||
mappingFieldsPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.mapping_fields().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.authentication_edit', 'app.shared.authentication', 'app.shared.oauth2']).$promise; }]
|
||||
mappingFieldsPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.mapping_fields().$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -1058,8 +969,7 @@ angular.module('application.router', ['ui.router'])
|
||||
},
|
||||
resolve: {
|
||||
membersPromise: ['Member', function (Member) { return Member.mapping().$promise; }],
|
||||
statisticsPromise: ['Statistics', function (Statistics) { return Statistics.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.statistics').$promise; }]
|
||||
statisticsPromise: ['Statistics', function (Statistics) { return Statistics.query().$promise; }]
|
||||
}
|
||||
})
|
||||
.state('app.admin.stats_graphs', {
|
||||
@ -1069,9 +979,6 @@ angular.module('application.router', ['ui.router'])
|
||||
templateUrl: '<%= asset_path "admin/statistics/graphs.html" %>',
|
||||
controller: 'GraphsController'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.stats_graphs').$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -1087,42 +994,23 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
settingsPromise: ['Setting', function (Setting) {
|
||||
return Setting.query({
|
||||
names: `['twitter_name', \
|
||||
'about_title', \
|
||||
'about_body', \
|
||||
'privacy_body', \
|
||||
'privacy_dpo', \
|
||||
'about_contacts', \
|
||||
'home_blogpost', \
|
||||
'machine_explications_alert', \
|
||||
'training_explications_alert', \
|
||||
'training_information_message', \
|
||||
'subscription_explications_alert', \
|
||||
'event_explications_alert', \
|
||||
'space_explications_alert', \
|
||||
'booking_window_start', \
|
||||
'booking_window_end', \
|
||||
'booking_move_enable', \
|
||||
'booking_move_delay', \
|
||||
'booking_cancel_enable', \
|
||||
'booking_cancel_delay', \
|
||||
'main_color', \
|
||||
'secondary_color', \
|
||||
'fablab_name', \
|
||||
'name_genre', \
|
||||
'reminder_enable', \
|
||||
'reminder_delay', \
|
||||
'visibility_yearly', \
|
||||
'visibility_others', \
|
||||
'display_name_enable', \
|
||||
'machines_sort_by']` }).$promise;
|
||||
names: `['twitter_name', 'about_title', 'about_body', \
|
||||
'privacy_body', 'privacy_dpo', 'about_contacts', \
|
||||
'home_blogpost', 'machine_explications_alert', 'training_explications_alert', \
|
||||
'training_information_message', 'subscription_explications_alert', 'event_explications_alert', \
|
||||
'space_explications_alert', 'booking_window_start', 'booking_window_end', \
|
||||
'booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', \
|
||||
'booking_cancel_delay', 'main_color', 'secondary_color', \
|
||||
'fablab_name', 'name_genre', 'reminder_enable', \
|
||||
'reminder_delay', 'visibility_yearly', 'visibility_others', \
|
||||
'display_name_enable', 'machines_sort_by', 'fab_analytics', \
|
||||
'link_name', 'home_content', 'home_css']` }).$promise;
|
||||
}],
|
||||
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
|
||||
cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }],
|
||||
cgvFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgv-file' }).$promise; }],
|
||||
faviconFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'favicon-file' }).$promise; }],
|
||||
profileImageFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'profile-image-file' }).$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.settings').$promise; }]
|
||||
profileImageFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'profile-image-file' }).$promise; }]
|
||||
}
|
||||
})
|
||||
|
||||
@ -1136,8 +1024,7 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
clientsPromise: ['OpenAPIClient', function (OpenAPIClient) { return OpenAPIClient.query().$promise; }],
|
||||
translations: ['Translations', function (Translations) { return Translations.query('app.admin.open_api_clients').$promise; }]
|
||||
clientsPromise: ['OpenAPIClient', function (OpenAPIClient) { return OpenAPIClient.query().$promise; }]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,17 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
Application.Services.factory('AuthService', ['Session', 'CSRF', function (Session, CSRF) {
|
||||
return {
|
||||
isAuthenticated () {
|
||||
return (Session.currentUser != null) && (Session.currentUser.id != null);
|
||||
},
|
||||
let service = {};
|
||||
|
||||
isAuthorized (authorizedRoles) {
|
||||
if (!angular.isArray(authorizedRoles)) {
|
||||
authorizedRoles = [authorizedRoles];
|
||||
}
|
||||
|
||||
return this.isAuthenticated() && (authorizedRoles.indexOf(Session.currentUser.role) !== -1);
|
||||
}
|
||||
service.isAuthenticated = function() {
|
||||
return (Session.currentUser != null) && (Session.currentUser.id != null);
|
||||
};
|
||||
|
||||
service.isAuthorized = function(authorizedRoles) {
|
||||
if (!angular.isArray(authorizedRoles)) {
|
||||
authorizedRoles = [authorizedRoles];
|
||||
}
|
||||
return service.isAuthenticated() && (authorizedRoles.indexOf(Session.currentUser.role) !== -1);
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
||||
|
@ -17,7 +17,7 @@ Application.Services.factory('CalendarConfig', [() =>
|
||||
center: 'title',
|
||||
right: 'today prev,next'
|
||||
},
|
||||
firstDay: 1, // Week start on monday (France)
|
||||
firstDay: Fablab.weekStartingDay,
|
||||
scrollTime: DEFAULT_CALENDAR_POSITION,
|
||||
slotDuration: BASE_SLOT,
|
||||
allDayDefault: false,
|
||||
|
10
app/assets/javascripts/services/fab_analytics.js
Normal file
10
app/assets/javascripts/services/fab_analytics.js
Normal file
@ -0,0 +1,10 @@
|
||||
Application.Services.factory('FabAnalytics', ['$resource', function ($resource) {
|
||||
return $resource('/api/analytics',
|
||||
{}, {
|
||||
data: {
|
||||
method: 'GET',
|
||||
url: '/api/analytics/data'
|
||||
}
|
||||
}
|
||||
);
|
||||
}]);
|
5
app/assets/javascripts/services/ical.js
Normal file
5
app/assets/javascripts/services/ical.js
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
Application.Services.factory('Ical', ['$resource', function ($resource) {
|
||||
return $resource('/api/ical/externals');
|
||||
}]);
|
17
app/assets/javascripts/services/icalendar.js
Normal file
17
app/assets/javascripts/services/icalendar.js
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
Application.Services.factory('ICalendar', ['$resource', function ($resource) {
|
||||
return $resource('/api/i_calendar/:id',
|
||||
{ id: '@id' }, {
|
||||
events: {
|
||||
method: 'GET',
|
||||
url: '/api/i_calendar/:id/events'
|
||||
},
|
||||
sync: {
|
||||
method: 'POST',
|
||||
url: '/api/i_calendar/:id/sync',
|
||||
params: { id: '@id' }
|
||||
}
|
||||
}
|
||||
);
|
||||
}]);
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
Application.Services.factory('Member', ['$resource', function ($resource) {
|
||||
Application.Services.factory('Member', ['$resource', '$q', function ($resource, $q) {
|
||||
return $resource('/api/members/:id',
|
||||
{ id: '@id' }, {
|
||||
update: {
|
||||
@ -30,6 +30,20 @@ Application.Services.factory('Member', ['$resource', function ($resource) {
|
||||
mapping: {
|
||||
method: 'GET',
|
||||
url: '/api/members/mapping'
|
||||
},
|
||||
completeTour: {
|
||||
method: 'PATCH',
|
||||
url: '/api/members/:id/complete_tour',
|
||||
params: { id: '@id' },
|
||||
interceptor: {
|
||||
response: function (response) {
|
||||
if (Fablab.featureTourDisplay === 'session') {
|
||||
Fablab.sessionTours.push(response.data.tours[0]);
|
||||
return { tours: Fablab.sessionTours };
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -15,6 +15,11 @@ Application.Services.factory('Setting', ['$resource', function ($resource) {
|
||||
},
|
||||
query: {
|
||||
isArray: false
|
||||
},
|
||||
reset: {
|
||||
url: '/api/settings/reset/:name',
|
||||
params: { name: '@name' },
|
||||
method: 'PUT'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,5 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
Application.Services.factory('Twitter', ['$resource', function ($resource) {
|
||||
return $resource('/api/feeds/twitter_timelines');
|
||||
}]);
|
@ -12,6 +12,10 @@
|
||||
.bg-stage { background-color: $violet; }
|
||||
.bg-success { background-color: $brand-success; }
|
||||
.bg-info { background-color: $brand-info; }
|
||||
.border-machine { border-color: $beige !important; }
|
||||
.border-space { border-color: $cyan !important; }
|
||||
.border-formation { border-color: $violet !important; }
|
||||
.border-event { border-color: $japonica !important; }
|
||||
|
||||
.bg-black-light { background-color: #424242 !important; }
|
||||
|
||||
|
@ -290,6 +290,11 @@
|
||||
@include border-radius(3px);
|
||||
padding: 5px 10px;
|
||||
}
|
||||
&.well-disabled {
|
||||
border-color: $gray-lighter;
|
||||
background-color: $gray-lighter;
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.read {
|
||||
@ -629,3 +634,46 @@ padding: 10px;
|
||||
.help-block.error {
|
||||
color: #ff565d;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
background-color: $gray-lighter;
|
||||
color: $gray-light;
|
||||
|
||||
& a {
|
||||
color: $gray;
|
||||
}
|
||||
|
||||
.canceled-marker {
|
||||
float: right;
|
||||
top: -13px;
|
||||
position: relative;
|
||||
color: red;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
.calendar-legend-block {
|
||||
text-align: right;
|
||||
padding-right: 2em;
|
||||
|
||||
h4 {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
.legends {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.calendar-legend {
|
||||
border: 1px solid;
|
||||
border-left: 3px solid;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
padding: 2px;
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=date].form-control {
|
||||
line-height: 25px;
|
||||
}
|
||||
|
@ -101,7 +101,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
body.container{
|
||||
padding: 0;
|
||||
}
|
||||
@ -636,4 +635,4 @@ body.container{
|
||||
position: absolute;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
> li.menu-spacer {
|
||||
height: 1px;
|
||||
margin: 6px 80% 6px 5px;
|
||||
background: linear-gradient(45deg, black, transparent);
|
||||
}
|
||||
ul{
|
||||
display: none;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// medium editor placeholder
|
||||
// medium editor placeholder
|
||||
.medium-editor-placeholder {
|
||||
min-height: 30px; // fix for firefox
|
||||
}
|
||||
@ -126,6 +126,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fc-selected {
|
||||
box-shadow: 0 6px 10px 0 rgba(0,0,0,0.14),0 1px 18px 0 rgba(0,0,0,0.12),0 3px 5px -1px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -415,3 +419,19 @@
|
||||
.slider-nav__item--current {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
|
||||
// Twitter
|
||||
#twitter {
|
||||
ul {
|
||||
margin: 0px 15px;
|
||||
padding-left: 0;
|
||||
li {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.timePosted {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ p, .widget p {
|
||||
|
||||
.text-italic { font-style: italic; }
|
||||
|
||||
.text-left { text-align: left !important; }
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
@ -379,6 +380,10 @@ p, .widget p {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $screen-lg-min) {
|
||||
.b-r-lg {border-right: 1px solid $border-color; }
|
||||
.hide-b-r-lg { border: none !important; }
|
||||
|
@ -14,6 +14,7 @@
|
||||
*= require summernote/dist/summernote
|
||||
*= require jquery-minicolors/jquery.minicolors.css
|
||||
*= require angular-aside/dist/css/angular-aside
|
||||
*= require codemirror/lib/codemirror
|
||||
*/
|
||||
|
||||
@import "app.functions";
|
||||
@ -32,11 +33,7 @@
|
||||
@import "app.buttons";
|
||||
@import "app.components";
|
||||
@import "app.plugins";
|
||||
@import "modules/invoice";
|
||||
@import "modules/signup";
|
||||
@import "modules/abuses";
|
||||
@import "modules/cookies";
|
||||
@import "modules/stripe";
|
||||
@import "modules/*";
|
||||
|
||||
@import "app.responsive";
|
||||
|
||||
|
@ -28,4 +28,4 @@ li.abuse {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
app/assets/stylesheets/modules/icalendar.scss
Normal file
24
app/assets/stylesheets/modules/icalendar.scss
Normal file
@ -0,0 +1,24 @@
|
||||
.calendar-form {
|
||||
margin : 2em;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
padding: 1em;
|
||||
|
||||
& > .input-group, & > .minicolors {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-name {
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.calendar-url {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.external-calendar-legend {
|
||||
border-left: 3px solid;
|
||||
border-radius: 3px;
|
||||
}
|
63
app/assets/stylesheets/modules/settings.scss
Normal file
63
app/assets/stylesheets/modules/settings.scss
Normal file
@ -0,0 +1,63 @@
|
||||
.admin-settings {
|
||||
.home-page-settings {
|
||||
.home-page-content {
|
||||
.note-editor {
|
||||
.note-toolbar .note-btn-group .note-btn .nugget {
|
||||
font-family: "FontAwesome";
|
||||
}
|
||||
.note-editing-area .note-editable {
|
||||
#news {
|
||||
width: 100%;
|
||||
background-color: #b1b1b1;
|
||||
color: white;
|
||||
border: 1px dashed #8f9091;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 10rem;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
#projects {
|
||||
@extend #news;
|
||||
line-height: 523px;
|
||||
}
|
||||
|
||||
#twitter {
|
||||
@extend #news;
|
||||
line-height: 162px;
|
||||
}
|
||||
|
||||
#members {
|
||||
@extend #news;
|
||||
line-height: 320px;
|
||||
}
|
||||
|
||||
#events {
|
||||
@extend #news;
|
||||
line-height: 621px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.home-page-style {
|
||||
.panel {
|
||||
border: 0;
|
||||
.panel-heading {
|
||||
background: none;
|
||||
|
||||
.panel-title {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
border: 1px solid #ddd;
|
||||
font-size: 12px;
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
app/assets/stylesheets/modules/tour.scss
Normal file
31
app/assets/stylesheets/modules/tour.scss
Normal file
@ -0,0 +1,31 @@
|
||||
.ui-tour-backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
fill: rgba(0, 0, 0, 0.5);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
|
||||
.ui-tour-popup.popover {
|
||||
max-width: 414px;
|
||||
}
|
||||
|
||||
.tour-step-navigation.popover-navigation .btn-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0 14px;
|
||||
|
||||
.button-placekeeper {
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.shift-right-40 { margin-left: 40px; }
|
||||
|
||||
.shift-left-40 { margin-left: -40px; }
|
||||
.shift-left-50 { margin-left: -50px; }
|
||||
.shift-left-80 { margin-left: -80px; }
|
||||
|
||||
.width-350 { width: 350px; }
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'manage_abuses.abuses_list' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.manage_abuses.abuses_list' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@ -15,27 +15,27 @@
|
||||
|
||||
<section class="m-lg">
|
||||
<div class="row m-b-md">
|
||||
<span ng-show="abuses.length === 0" translate>{{ 'manage_abuses.no_reports' }}</span>
|
||||
<span ng-show="abuses.length === 0" translate>{{ 'app.admin.manage_abuses.no_reports' }}</span>
|
||||
<ul ng-show="abuses.length > 0">
|
||||
<li class="abuse" ng-repeat="abuse in abuses">
|
||||
<div class="signaled">
|
||||
<a ui-sref="app.public.projects_show({id:abuse.signaled.slug})">{{abuse.signaled.name}}</a>,
|
||||
<span translate>{{ 'manage_abuses.published_by' }}</span>
|
||||
<span translate>{{ 'app.admin.manage_abuses.published_by' }}</span>
|
||||
<a ui-sref="app.admin.members_edit({id:abuse.signaled.author.id})">{{abuse.signaled.author.full_name}}</a>,
|
||||
<span translate>{{ 'manage_abuses.at_date' }}</span>
|
||||
<span translate>{{ 'app.admin.manage_abuses.at_date' }}</span>
|
||||
<span>{{abuse.signaled.published_at | amDateFormat:'L' }}</span>
|
||||
<button class="btn btn-success" ng-click="confirmProcess(abuse.id)">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="report">
|
||||
<span translate>{{ 'manage_abuses.at_date' }}</span>
|
||||
<span translate>{{ 'app.admin.manage_abuses.at_date' }}</span>
|
||||
<span>{{abuse.created_at | amDateFormat:'L' }}</span>,
|
||||
<a href="mailto:{{abuse.email}}">{{abuse.first_name}} {{abuse.last_name}}</a>
|
||||
<span translate>{{ 'manage_abuses.has_reported' }}</span>
|
||||
<span translate>{{ 'app.admin.manage_abuses.has_reported' }}</span>
|
||||
<cite>{{ abuse.message }}</cite>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'add_an_administrator' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.admins_new.add_an_administrator' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -20,12 +20,12 @@
|
||||
<form role="form" name="adminForm" class="form-horizontal" novalidate>
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'shared/_admin_form.html' %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "shared/_admin_form.html" %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit" value="{{ 'save' | translate}}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-click="saveAdmin()" ng-disabled="adminForm.$invalid"/>
|
||||
<input type="submit" value="{{ 'app.shared.buttons.save' | translate}}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-click="saveAdmin()" ng-disabled="adminForm.$invalid"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
@ -1,16 +1,16 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title"><span translate>{{ 'data_mapping' }}</span> : {{field.local_field}}</h3>
|
||||
<h3 class="modal-title"><span translate>{{ 'app.shared.authentication.data_mapping' }}</span> : {{field.local_field}}</h3>
|
||||
</div>
|
||||
<div class="modal-body m-lg">
|
||||
<div>
|
||||
<span translate>{{ 'expected_data_type' }}</span> : {{datatype}}
|
||||
<span translate>{{ 'app.shared.authentication.expected_data_type' }}</span> : {{datatype}}
|
||||
</div>
|
||||
<form name="mappingForm" class="m-t-md">
|
||||
<ng-switch on="datatype">
|
||||
|
||||
<!-- BOOLEAN -->
|
||||
<div ng-switch-when="boolean">
|
||||
<label for="add_mapping" translate>{{ 'mappings' }}</label>
|
||||
<label for="add_mapping" translate>{{ 'app.shared.authentication.mappings' }}</label>
|
||||
<ul class="list-unstyled">
|
||||
<li class="m-t-sm m-l">
|
||||
<input type="text"
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
<!-- DATE -->
|
||||
<div ng-switch-when="date">
|
||||
<label for="date_format" translate>{{ 'input_format' }}</label>
|
||||
<label for="date_format" translate>{{ 'app.shared.authentication.input_format' }}</label>
|
||||
<select name="date_format"
|
||||
id="date_format"
|
||||
class="form-control"
|
||||
@ -46,7 +46,7 @@
|
||||
|
||||
<!-- INTEGER -->
|
||||
<div ng-switch-when="integer">
|
||||
<label for="add_mapping" translate>{{ 'mappings' }}</label>
|
||||
<label for="add_mapping" translate>{{ 'app.shared.authentication.mappings' }}</label>
|
||||
<button class="btn btn-default pull-right" ng-click="addIntegerMapping()"><i class="fa fa-plus"></i></button>
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="map in transformation.rules.mapping" class="m-t-sm m-l">
|
||||
@ -60,6 +60,6 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" ng-click="ok()" ng-disabled="!mappingForm.$valid" ng-if="datatype != 'string' && datatype != 'text'" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-warning" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
||||
<button class="btn btn-primary" ng-click="ok()" ng-disabled="!mappingForm.$valid" ng-if="datatype != 'string' && datatype != 'text'" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-warning" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[name]'].$dirty && providerForm['auth_provider[name]'].$invalid}">
|
||||
<label for="provider_name" class="col-sm-3 control-label" translate>{{ 'name' }}</label>
|
||||
<label for="provider_name" class="col-sm-3 control-label" translate>{{ 'app.shared.authentication.name' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
ng-model="provider.name"
|
||||
@ -8,23 +8,23 @@
|
||||
id="provider_name"
|
||||
ng-disabled="mode == 'edition'"
|
||||
required />
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[name]'].$dirty && providerForm['auth_provider[name]'].$error.required" translate>{{ 'provider_name_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[name]'].$dirty && providerForm['auth_provider[name]'].$error.required" translate>{{ 'app.shared.authentication.provider_name_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[providable_type]'].$dirty && providerForm['auth_provider[providable_type]'].$invalid}">
|
||||
<label for="provider_type" class="col-sm-3 control-label" translate>{{ 'authentication_type' }}</label>
|
||||
<label for="provider_type" class="col-sm-3 control-label" translate>{{ 'app.shared.authentication.authentication_type' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<select ng-model="provider.providable_type"
|
||||
ng-change="updateProvidable()"
|
||||
class="form-control"
|
||||
name="auth_provider[providable_type]"
|
||||
id="provider_type"
|
||||
ng-options="key as (value | translate) for (key, value) in authMethods"
|
||||
ng-options="key as methodName(key) for (key, value) in authMethods"
|
||||
ng-disabled="mode == 'edition'"
|
||||
required>
|
||||
</select>
|
||||
<input type="hidden" name="auth_provider[type]" ng-value="provider.type" />
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[providable_type]'].$dirty && providerForm['auth_provider[providable_type]'].$error.required" translate>{{ 'authentication_type_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[providable_type]'].$dirty && providerForm['auth_provider[providable_type]'].$error.required" translate>{{ 'app.shared.authentication.authentication_type_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<hr/>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[base_url]'].$dirty && providerForm['auth_provider[base_url]'].$invalid}">
|
||||
<label for="provider_base_url" class="col-sm-3 control-label" translate>{{ 'common_url' }}</label>
|
||||
<label for="provider_base_url" class="col-sm-3 control-label" translate>{{ 'app.shared.oauth2.common_url' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
ng-model="provider.providable_attributes.base_url"
|
||||
@ -11,13 +11,13 @@
|
||||
placeholder="https://sso.example.net..."
|
||||
required
|
||||
url>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[base_url]'].$dirty && providerForm['auth_provider[base_url]'].$error.required" translate>{{ 'common_url_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[base_url]'].$error.url" translate>{{ 'provided_url_is_not_a_valid_url' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[base_url]'].$dirty && providerForm['auth_provider[base_url]'].$error.required" translate>{{ 'app.shared.oauth2.common_url_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[base_url]'].$error.url" translate>{{ 'app.shared.oauth2.provided_url_is_not_a_valid_url' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[authorization_endpoint]'].$dirty && providerForm['auth_provider[authorization_endpoint]'].$invalid}">
|
||||
<label for="provider_authorization_endpoint" class="col-sm-3 control-label" translate>{{ 'authorization_endpoint' }}</label>
|
||||
<label for="provider_authorization_endpoint" class="col-sm-3 control-label" translate>{{ 'app.shared.oauth2.authorization_endpoint' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
ng-model="provider.providable_attributes.authorization_endpoint"
|
||||
@ -27,13 +27,13 @@
|
||||
placeholder="/oauth2/auth..."
|
||||
required
|
||||
endpoint>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[authorization_endpoint]'].$dirty && providerForm['auth_provider[authorization_url]'].$error.required" translate>{{ 'oauth2_authorization_endpoint_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[authorization_endpoint]'].$error.endpoint" translate>{{ 'provided_endpoint_is_not_valid' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[authorization_endpoint]'].$dirty && providerForm['auth_provider[authorization_url]'].$error.required" translate>{{ 'app.shared.oauth2.oauth2_authorization_endpoint_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[authorization_endpoint]'].$error.endpoint" translate>{{ 'app.shared.oauth2.provided_endpoint_is_not_valid' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[token_endpoint]'].$dirty && providerForm['auth_provider[token_endpoint]'].$invalid}">
|
||||
<label for="provider_token_endpoint" class="col-sm-3 control-label" translate>{{ 'token_acquisition_endpoint' }}</label>
|
||||
<label for="provider_token_endpoint" class="col-sm-3 control-label" translate>{{ 'app.shared.oauth2.token_acquisition_endpoint' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
ng-model="provider.providable_attributes.token_endpoint"
|
||||
@ -43,13 +43,13 @@
|
||||
placeholder="/oauth2/token..."
|
||||
required
|
||||
endpoint>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[token_endpoint]'].$dirty && providerForm['auth_provider[token_endpoint]'].$error.required" translate>{{ 'oauth2_token_acquisition_endpoint_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[token_endpoint]'].$error.endpoint" translate>{{ 'provided_endpoint_is_not_valid' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[token_endpoint]'].$dirty && providerForm['auth_provider[token_endpoint]'].$error.required" translate>{{ 'app.shared.oauth2.oauth2_token_acquisition_endpoint_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[token_endpoint]'].$error.endpoint" translate>{{ 'app.shared.oauth2.provided_endpoint_is_not_valid' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[profile_url]'].$dirty && providerForm['auth_provider[profile_url]'].$invalid}">
|
||||
<label for="provider_profile_url" class="col-sm-3 control-label" translate>{{ 'profil_edition_url' }}</label>
|
||||
<label for="provider_profile_url" class="col-sm-3 control-label" translate>{{ 'app.shared.oauth2.profil_edition_url' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
ng-model="provider.providable_attributes.profile_url"
|
||||
@ -59,13 +59,13 @@
|
||||
placeholder="https://exemple.net/user..."
|
||||
required
|
||||
url>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[profile_url]'].$dirty && providerForm['auth_provider[profile_url]'].$error.required" translate>{{ 'profile_edition_url_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[profile_url]'].$error.url" translate>{{ 'provided_url_is_not_a_valid_url' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[profile_url]'].$dirty && providerForm['auth_provider[profile_url]'].$error.required" translate>{{ 'app.shared.oauth2.profile_edition_url_is_required' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[profile_url]'].$error.url" translate>{{ 'app.shared.oauth2.provided_url_is_not_a_valid_url' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[client_id]'].$dirty && providerForm['auth_provider[client_id]'].$invalid}">
|
||||
<label for="provider_client_id" class="col-sm-3 control-label" translate>{{ 'client_identifier' }}</label>
|
||||
<label for="provider_client_id" class="col-sm-3 control-label" translate>{{ 'app.shared.oauth2.client_identifier' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
ng-model="provider.providable_attributes.client_id"
|
||||
@ -73,12 +73,12 @@
|
||||
name="auth_provider[client_id]"
|
||||
id="provider_client_id"
|
||||
required>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[client_id]'].$dirty && providerForm['auth_provider[client_id]'].$error.required" translate>{{ 'oauth2_client_identifier_is_required' }} {{ 'obtain_it_when_registering_with_your_provider' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[client_id]'].$dirty && providerForm['auth_provider[client_id]'].$error.required" translate>{{ 'app.shared.oauth2.oauth2_client_identifier_is_required' }} {{ 'obtain_it_when_registering_with_your_provider' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': providerForm['auth_provider[client_secret]'].$dirty && providerForm['auth_provider[client_secret]'].$invalid}">
|
||||
<label for="provider_client_secret" class="col-sm-3 control-label" translate>{{ 'client_secret' }}</label>
|
||||
<label for="provider_client_secret" class="col-sm-3 control-label" translate>{{ 'app.shared.oauth2.client_secret' }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
ng-model="provider.providable_attributes.client_secret"
|
||||
@ -86,8 +86,8 @@
|
||||
name="auth_provider[client_secret]"
|
||||
id="provider_client_secret"
|
||||
required>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[client_secret]'].$dirty && providerForm['auth_provider[client_secret]'].$error.required" translate>{{ 'oauth2_client_secret_is_required' }} {{ 'obtain_it_when_registering_with_your_provider' }}</span>
|
||||
<span class="help-block" ng-show="providerForm['auth_provider[client_secret]'].$dirty && providerForm['auth_provider[client_secret]'].$error.required" translate>{{ 'app.shared.oauth2.oauth2_client_secret_is_required' }} {{ 'obtain_it_when_registering_with_your_provider' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/authentications/_oauth2_mapping.html' %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "admin/authentications/_oauth2_mapping.html" %>'"></ng-include>
|
||||
|
@ -1,15 +1,15 @@
|
||||
<h4 class="m-l m-t-xl" translate>{{ 'define_the_fields_mapping' }}</h4>
|
||||
<h4 class="m-l m-t-xl" translate>{{ 'app.shared.oauth2.define_the_fields_mapping' }}</h4>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-success m-l m-b" ng-click="newMapping = {}"><i class="fa fa-plus"></i> {{ 'add_a_match' | translate }}</button>
|
||||
<button type="button" class="btn btn-success m-l m-b" ng-click="newMapping = {}"><i class="fa fa-plus"></i> {{ 'app.shared.oauth2.add_a_match' | translate }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'model' }}</th>
|
||||
<th translate>{{ 'field' }}</th>
|
||||
<th translate>{{ 'api_endpoint_url' }}</th>
|
||||
<th translate>{{ 'api_type' }}</th>
|
||||
<th translate>{{ 'api_fields' }}</th>
|
||||
<th translate>{{ 'app.shared.oauth2.model' }}</th>
|
||||
<th translate>{{ 'app.shared.oauth2.field' }}</th>
|
||||
<th translate>{{ 'app.shared.oauth2.api_endpoint_url' }}</th>
|
||||
<th translate>{{ 'app.shared.oauth2.api_type' }}</th>
|
||||
<th translate>{{ 'app.shared.oauth2.api_fields' }}</th>
|
||||
<th style="width: 6.4em;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -76,4 +76,4 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
|
@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1>{{ 'provider' | translate }} {{provider.name}}</h1>
|
||||
<h1>{{ 'app.admin.authentication_edit.provider' | translate }} {{provider.name}}</h1>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -17,7 +17,7 @@
|
||||
<div class="col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
<div class="btn btn-lg btn-block btn-default m-t-xs" ng-click="cancel()" translate>
|
||||
{{ 'cancel' }}
|
||||
{{ 'app.shared.buttons.cancel' }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -35,13 +35,13 @@
|
||||
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'admin/authentications/_form.html' %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path 'admin/authentications/_oauth2.html'%>'" ng-if="provider.providable_type == 'OAuth2Provider'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "admin/authentications/_form.html" %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "admin/authentications/_oauth2.html"%>'" ng-if="provider.providable_type == 'OAuth2Provider'"></ng-include>
|
||||
</div> <!-- ./panel-body -->
|
||||
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="button" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="providerForm.$invalid" ng-click="updateProvider()"/>
|
||||
<input type="button" value="{{ 'app.shared.buttons.confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="providerForm.$invalid" ng-click="updateProvider()"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
@ -2,23 +2,23 @@
|
||||
<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="searchFilter" class="form-control" placeholder="{{ 'search_for_an_authentication_provider' | translate }}">
|
||||
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'app.admin.members.authentication_form.search_for_an_authentication_provider' | translate }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.authentication_new" translate>{{ 'add_a_new_authentication_provider' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.authentication_new" translate>{{ 'app.admin.members.authentication_form.add_a_new_authentication_provider' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%" translate>{{ 'name' }}</th>
|
||||
<th style="width:15%" translate>{{ 'app.admin.members.authentication_form.name' }}</th>
|
||||
|
||||
<th style="width:15%" translate>{{ 'strategy_name' }}</th>
|
||||
<th style="width:15%" translate>{{ 'app.admin.members.authentication_form.strategy_name' }}</th>
|
||||
|
||||
<th style="width:15%" translate>{{ 'type' }}</th>
|
||||
<th style="width:15%" translate>{{ 'app.admin.members.authentication_form.type' }}</th>
|
||||
|
||||
<th style="width:15%" translate>{{ 'state' }}</th>
|
||||
<th style="width:15%" translate>{{ 'app.admin.members.authentication_form.state' }}</th>
|
||||
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
@ -31,7 +31,7 @@
|
||||
<td>{{ getState(provider.status) }}</td>
|
||||
<td>
|
||||
<button class="btn btn-default" ui-sref="app.admin.authentication_edit({id:provider.id})">
|
||||
<i class="fa fa-pencil-square-o"></i> {{ 'edit' | translate }}
|
||||
<i class="fa fa-pencil-square-o"></i> {{ 'app.shared.buttons.edit' | translate }}
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-if="provider.status != 'active'" ng-click="destroyProvider(providers, provider)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
@ -40,4 +40,4 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'add_a_new_authentication_provider' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.authentication_new.add_a_new_authentication_provider' }}</h1>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -17,7 +17,7 @@
|
||||
<div class="col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
<div class="btn btn-lg btn-block btn-default m-t-xs" ng-click="cancel()" translate>
|
||||
{{ 'cancel' }}
|
||||
{{ 'app.shared.buttons.cancel' }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -36,13 +36,13 @@
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/authentications/_form.html' %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path 'admin/authentications/_oauth2.html'%>'" ng-if="provider.providable_type == 'OAuth2Provider'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "admin/authentications/_form.html" %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "admin/authentications/_oauth2.html" %>'" ng-if="provider.providable_type == 'OAuth2Provider'"></ng-include>
|
||||
</div> <!-- ./panel-body -->
|
||||
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="button" value="{{ 'save' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="providerForm.$invalid" ng-click="registerProvider()"/>
|
||||
<input type="button" value="{{ 'app.shared.buttons.save' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="providerForm.$invalid" ng-click="registerProvider()"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
@ -5,17 +5,17 @@
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<div class="col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'admin_calendar.calendar_management' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.calendar.calendar_management' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper" ng-class="{'p-s': !fablabWithoutSpaces}">
|
||||
<span class="badge text-sm bg-formation" ng-class="{'m-t-sm': fablabWithoutSpaces}" translate>{{ 'admin_calendar.trainings' }}</span><br>
|
||||
<span class="badge text-sm bg-machine" translate>{{ 'admin_calendar.machines' }}</span><br>
|
||||
<span class="badge text-sm bg-space" ng-hide="fablabWithoutSpaces" translate>{{ 'admin_calendar.spaces' }}</span>
|
||||
<div class="col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
<a role="button" ui-sref="app.admin.calendar.icalendar" class="btn btn-lg btn-default rounded m-t-sm text-sm import-ics-button">
|
||||
<i class="fa fa-exchange m-r" aria-hidden="true"></i><span translate>{{ 'app.admin.calendar.external_sync' }}</span>
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -23,29 +23,44 @@
|
||||
</section>
|
||||
|
||||
|
||||
<section class="row no-gutter">
|
||||
<section class="row no-gutter admin-calendar"
|
||||
ui-tour="calendar"
|
||||
ui-tour-backdrop="true"
|
||||
ui-tour-template-url="'<%= asset_path "shared/tour-step-template.html" %>'"
|
||||
ui-tour-use-hotkeys="true"
|
||||
ui-tour-scroll-parent-id="content-main"
|
||||
post-render="setupCalendarTour">
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="calendar" class="wrapper-lg"></div>
|
||||
<div class="calendar-legend-block">
|
||||
<h4 translate>{{ 'app.admin.calendar.legend' }}</h4>
|
||||
<div class="legends">
|
||||
<span class="calendar-legend text-sm border-formation" translate>{{ 'app.admin.calendar.trainings' }}</span><br>
|
||||
<span class="calendar-legend text-sm border-machine" translate>{{ 'app.admin.calendar.machines' }}</span><br>
|
||||
<span class="calendar-legend text-sm border-space" ng-hide="fablabWithoutSpaces" translate>{{ 'app.admin.calendar.spaces' }}</span>
|
||||
<span class="calendar-legend text-sm border-event" ng-show="eventsInCalendar" translate>{{ 'app.admin.calendar.events' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="m text-center">
|
||||
<a class="btn btn-default"
|
||||
<a class="btn btn-default export-xls-button"
|
||||
ng-href="api/availabilities/export_index.xlsx"
|
||||
target="export-frame"
|
||||
ng-click="alertExport('index')"
|
||||
uib-popover="{{ 'admin_calendar.availabilities_notice' | translate}}"
|
||||
uib-popover="{{ 'app.admin.calendar.availabilities_notice' | translate}}"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="bottom">
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'admin_calendar.availabilities' | translate }}
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'app.admin.calendar.availabilities' | translate }}
|
||||
</a>
|
||||
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability">
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability" ng-hide="availability.available_type == 'event'">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3>
|
||||
<h3 translate>{{ 'app.admin.calendar.ongoing_reservations' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-class="{'reservations-locked': availability.lock}">
|
||||
<ul class="list-unstyled" ng-if="reservations.length > 0">
|
||||
@ -56,14 +71,14 @@
|
||||
<span class="btn btn-warning btn-xs" ng-click="cancelBooking(r)" ng-if="!r.canceled_at"><i class="fa fa-times red"></i></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-show="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div>
|
||||
<div class="m-t" ng-show="availability.lock"><i class="fa fa-ban"/> <span class="m-l-xs" translate>{{ 'admin_calendar.reservations_locked' }}</span></div>
|
||||
<div ng-show="reservations.length == 0" translate>{{ 'app.admin.calendar.no_reservations' }}</div>
|
||||
<div class="m-t" ng-show="availability.lock"><i class="fa fa-ban"></i> <span class="m-l-xs" translate>{{ 'app.admin.calendar.reservations_locked' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability.machine_ids.length > 0">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
|
||||
<h3 translate>{{ 'app.admin.calendar.machines' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled">
|
||||
@ -75,29 +90,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability">
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability.plan_ids.length > 0">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'admin_calendar.actions' }}</h3>
|
||||
<h3 translate>{{ 'app.admin.calendar.plans' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="g in availability.plans" class="m-b-xs">
|
||||
<div class="font-sbold">{{::g.name}}</div>
|
||||
<ul class="m-n" ng-repeat="plan in g.plans">
|
||||
<li>
|
||||
{{::plan.name}}
|
||||
<span class="btn btn-warning btn-xs" ng-click="removePlan(plan)" ><i class="fa fa-times red"></i></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="availability" >
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'app.admin.calendar.actions' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-hide="availability.available_type == 'event'">
|
||||
<button class="btn btn-default" ng-click="toggleLockReservations()">
|
||||
<span ng-hide="availability.lock">
|
||||
<i class="fa fa-stop" />
|
||||
<span class="m-l-xs" translate>{{ 'admin_calendar.block_reservations' }}</span>
|
||||
<i class="fa fa-stop"></i>
|
||||
<span class="m-l-xs" translate>{{ 'app.admin.calendar.block_reservations' }}</span>
|
||||
</span>
|
||||
<span ng-show="availability.lock">
|
||||
<i class="fa fa-play" />
|
||||
<span class="m-l-xs" translate>{{ 'admin_calendar.allow_reservations' }}</span>
|
||||
<i class="fa fa-play"></i>
|
||||
<span class="m-l-xs" translate>{{ 'app.admin.calendar.allow_reservations' }}</span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn btn-default m-t" ng-click="removeSlot()">
|
||||
<span>
|
||||
<i class="fa fa-trash" />
|
||||
<span class="m-l-xs" translate>{{ 'admin_calendar.delete_slot' }}</span>
|
||||
<i class="fa fa-trash"></i>
|
||||
<span class="m-l-xs" translate>{{ 'app.admin.calendar.delete_slot' }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="availability.available_type == 'event'">
|
||||
<a class="btn btn-default pointer" ui-sref="app.admin.events_edit({id: availability.event_id})">
|
||||
<span>
|
||||
<i class="fa fa-edit"></i>
|
||||
<span class="m-l-xs" translate>{{ 'app.admin.calendar.edit_event' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a class="btn btn-default m-t pointer" ui-sref="app.admin.event_reservations({id: availability.event_id})">
|
||||
<span>
|
||||
<i class="fa fa-bookmark"></i>
|
||||
<span class="m-l-xs" translate>{{ 'app.admin.calendar.view_reservations' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
26
app/assets/templates/admin/calendar/deleteRecurrent.html
Normal file
26
app/assets/templates/admin/calendar/deleteRecurrent.html
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="modal-header">
|
||||
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
|
||||
<h1 translate>{{ 'app.admin.calendar.confirmation_required' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-hide="isRecurrent" translate>{{ 'app.admin.calendar.do_you_really_want_to_delete_this_slot' }}</p>
|
||||
<p ng-show="isRecurrent" translate>{{ 'app.admin.calendar.delete_recurring_slot' }}</p>
|
||||
<div ng-show="isRecurrent" class="form-group">
|
||||
<label class="checkbox">
|
||||
<input type="radio" name="delete_mode" ng-model="deleteMode" value="single" required/>
|
||||
<span translate>{{ 'app.admin.calendar.delete_this_slot' }}</span>
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="radio" name="delete_mode" ng-model="deleteMode" value="next" required/>
|
||||
<span translate>{{ 'app.admin.calendar.delete_this_and_next' }}</span>
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="radio" name="delete_mode" ng-model="deleteMode" value="all" required/>
|
||||
<span translate>{{ 'app.admin.calendar.delete_all' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok()" translate>{{ 'app.shared.buttons.delete' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
@ -1,27 +1,27 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red">
|
||||
{{ 'admin_calendar.DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
|
||||
{{ 'app.admin.calendar.DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="step === 1">
|
||||
<label class="m-t-sm" translate>{{ 'admin_calendar.what_kind_of_slot_do_you_want_to_create' }}</label>
|
||||
<label class="m-t-sm" translate>{{ 'app.admin.calendar.what_kind_of_slot_do_you_want_to_create' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" id="training" name="available_type" value="training" ng-model="availability.available_type">
|
||||
<span translate>{{ 'admin_calendar.training' }}</span>
|
||||
<span translate>{{ 'app.admin.calendar.training' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" id="machine" name="available_type" value="machines" ng-model="availability.available_type">
|
||||
<span translate>{{ 'admin_calendar.machine' }}</span>
|
||||
<span translate>{{ 'app.admin.calendar.machine' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio" ng-hide="fablabWithoutSpaces">
|
||||
<label>
|
||||
<input type="radio" id="space" name="available_type" value="space" ng-model="availability.available_type" ng-disabled="spaces.length === 0">
|
||||
<span translate>{{ 'admin_calendar.space' }}</span>
|
||||
<span translate>{{ 'app.admin.calendar.space' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -29,13 +29,17 @@
|
||||
<div class="modal-body" ng-show="step === 2">
|
||||
|
||||
<div ng-show="availability.available_type == 'machines'">
|
||||
<p class="text-center font-sbold m-t-sm">{{ 'admin_calendar.select_some_machines' | translate }}</p>
|
||||
<p class="text-center font-sbold m-t-sm">{{ 'app.admin.calendar.select_some_machines' | translate }}</p>
|
||||
|
||||
<div class="form-group m-l-xl">
|
||||
<button class="btn btn-default pull-right m-t-n-xl" ng-click="toggleAll()" translate>{{ selectedMachines.length == 0 ? 'app.admin.calendar.select_all' : 'app.admin.calendar.select_none' }}</button>
|
||||
<label class="checkbox" ng-repeat="machine in machines">
|
||||
<input type="checkbox" ng-click="toggleSelection(machine)"> {{machine.name}} <span class="text-xs">(id {{machine.id}})</span>
|
||||
<input type="checkbox" ng-click="toggleSelection(machine)" ng-model="selectedMachinesBinding[machine.id]"> {{machine.name}} <span class="text-xs">(id {{machine.id}})</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="alert alert-info m-b-none text-xs wrapper-sm">
|
||||
<i class="fa fa-lightbulb-o m-r" aria-hidden="true"></i> <a ui-sref="app.public.machines_list" ng-click="cancel()" translate> {{ 'app.admin.calendar.manage_machines' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="availability.available_type == 'training'">
|
||||
@ -43,12 +47,15 @@
|
||||
</select>
|
||||
<div class="row m-t">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-6 control-label" for="nb_places_training" translate>{{ 'admin_calendar.number_of_tickets' }}</label>
|
||||
<label class="col-sm-6 control-label" for="nb_places_training" translate>{{ 'app.admin.calendar.number_of_tickets' }}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" id="nb_places_training" class="form-control" ng-model="availability.nb_total_places">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info m-b-none text-xs wrapper-sm m-t">
|
||||
<i class="fa fa-lightbulb-o m-r" aria-hidden="true"></i> <a ui-sref="app.public.trainings_list" ng-click="cancel()" translate> {{ 'app.admin.calendar.manage_trainings' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="availability.available_type == 'space'">
|
||||
@ -56,29 +63,37 @@
|
||||
</select>
|
||||
<div class="row m-t">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-6 control-label" for="nb_places_space" translate>{{ 'admin_calendar.number_of_tickets' }}</label>
|
||||
<label class="col-sm-6 control-label" for="nb_places_space" translate>{{ 'app.admin.calendar.number_of_tickets' }}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" id="nb_places_space" class="form-control" ng-model="availability.nb_total_places">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info m-b-none text-xs wrapper-sm m-t">
|
||||
<i class="fa fa-lightbulb-o m-r" aria-hidden="true"></i> <a ui-sref="app.public.spaces_list" ng-click="cancel()" translate> {{ 'app.admin.calendar.manage_spaces' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="step === 3">
|
||||
<div id="timeAdjust" class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.adjust_the_opening_hours' }}</p>
|
||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.adjust_the_opening_hours' }}</p>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-2">
|
||||
<uib-timepicker ng-model="start" hour-step="timepickers.start.hstep" readonly-input="true" minute-step="timepickers.start.mstep" show-meridian="false"></uib-timepicker>
|
||||
</div>
|
||||
<span class="col-md-1 m-t-xl m-l" translate>{{ 'admin_calendar.to_time' }}</span>
|
||||
<span class="col-md-1 m-t-xl m-l" translate>{{ 'app.admin.calendar.to_time' }}</span>
|
||||
<fieldset ng-disabled="endDateReadOnly" class="col-md-5">
|
||||
<uib-timepicker ng-model="end" hour-step="timepickers.end.hstep" readonly-input="true" minute-step="timepickers.end.mstep" show-meridian="false"></uib-timepicker>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="step === 4">
|
||||
<div class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.restrict_options' }}</p>
|
||||
</div>
|
||||
<div id="tagAssociate" class="m-t-lg">
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.restrict_this_slot_with_labels_optional' }}</p>
|
||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.restrict_with_labels' }}</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<ui-select multiple ng-model="availability.tag_ids" class="form-control">
|
||||
@ -92,14 +107,124 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.restrict_for_subscriptions' }}</p>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="is_only_subscriptions" translate>{{ 'app.admin.calendar.enabled' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="isOnlySubscriptions"
|
||||
id="is_only_subscriptions"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
||||
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
||||
switch-animate="true"/>
|
||||
</div>
|
||||
<div class="col-md-12" ng-show="isOnlySubscriptions">
|
||||
<p class="font-sbold m-t-sm">{{ 'app.admin.calendar.select_some_plans' | translate }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-default pull-right m-t-n-xl" ng-click="toggleAllPlans()" translate>{{ selectedPlans.length == 0 ? 'app.admin.calendar.select_all' : 'app.admin.calendar.select_none' }}</button>
|
||||
<div ng-repeat="group in plansClassifiedByGroup">
|
||||
<div class="text-center font-sbold">{{::group.name}}</div>
|
||||
<label class="checkbox m-l-md" ng-repeat="plan in group.plans">
|
||||
<input type="checkbox" ng-click="toggleSelectPlan(plan)" ng-model="selectedPlansBinding[plan.id]"> {{::plan.name}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step < 3">
|
||||
<button class="btn btn-info" ng-click="previous()" ng-disabled="step === 1" translate>{{ 'admin_calendar.previous' }}</button>
|
||||
<button class="btn btn-info" ng-click="next()" translate>{{ 'admin_calendar.next' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<div class="modal-body m-h" ng-show="step === 5">
|
||||
<div class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.recurrence' }}</p>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="is_recurrent" translate>{{ 'app.admin.calendar.enabled' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="availability.is_recurrent"
|
||||
id="is_recurrent"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
||||
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<input type="hidden" name="availability[is_recurrent]" value="{{availability.is_recurrent}}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="period">{{ 'app.admin.calendar.period' | translate }}</label>
|
||||
<select id="period"
|
||||
name="period"
|
||||
class="form-control"
|
||||
ng-model="availability.period"
|
||||
ng-required="availability.is_recurrent"
|
||||
ng-disabled="!availability.is_recurrent">
|
||||
<option value="week" ng-selected="availability.period == 'week'" translate>{{ 'app.admin.calendar.week' }}</option>
|
||||
<option value="month" ng-selected="availability.period == 'month'" translate>{{ 'app.admin.calendar.month' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="nb_periods">{{ 'app.admin.calendar.number_of_periods' | translate }}</label>
|
||||
<input id="nb_periods"
|
||||
name="nb_periods"
|
||||
class="form-control"
|
||||
ng-model="availability.nb_periods"
|
||||
type="number"
|
||||
ng-required="availability.is_recurrent"
|
||||
ng-disabled="!availability.is_recurrent" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="end_date">{{ 'app.admin.calendar.end_date' | translate }}</label>
|
||||
<input id="end_date"
|
||||
name="end_date"
|
||||
class="form-control"
|
||||
ng-model="availability.end_date"
|
||||
type="date"
|
||||
ng-required="availability.is_recurrent"
|
||||
ng-disabled="!availability.is_recurrent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step === 3">
|
||||
<button class="btn btn-info" ng-click="previous()" translate>{{ 'admin_calendar.previous' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<div class="modal-body m-h" ng-show="step === 6">
|
||||
<div class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.summary' }}</p>
|
||||
<div class="row">
|
||||
<span>{{ 'app.admin.calendar.about_to_create' | translate:{NUMBER:occurrences.length,TYPE:availability.available_type} }}</span>
|
||||
<ul>
|
||||
<li ng-repeat="slot in occurrences">{{slot.start_at | amDateFormat:'L LT'}} - {{slot.end_at | amDateFormat:'LT'}}</li>
|
||||
</ul>
|
||||
<div class="alert alert-warning" translate translate-values="{DURATION: slotDuration}"> {{ 'app.admin.calendar.divided_in_slots' }} </div>
|
||||
<div>
|
||||
<span class="underline" translate>{{ 'app.admin.calendar.reservable' }}</span>
|
||||
<span ng-bind-html="reservableName"></span>
|
||||
</div>
|
||||
<div class="m-t">
|
||||
<span class="underline" translate>{{ 'app.admin.calendar.labels' }}</span>
|
||||
<span ng-bind-html="tagsName"></span>
|
||||
</div>
|
||||
<div class="m-t" ng-show="isOnlySubscriptions">
|
||||
<span class="underline" translate>{{ 'app.admin.calendar.plans' }}</span>
|
||||
<span ng-bind-html="plansName"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step < 6">
|
||||
<button class="btn btn-info" ng-click="previous()" ng-disabled="step === 1" translate>{{ 'app.admin.calendar.previous' }}</button>
|
||||
<button class="btn btn-info" ng-click="next()" translate>{{ 'app.admin.calendar.next' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step === 6">
|
||||
<button class="btn btn-info" ng-click="previous()" translate>{{ 'app.admin.calendar.previous' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
|
96
app/assets/templates/admin/calendar/icalendar.html
Normal file
96
app/assets/templates/admin/calendar/icalendar.html
Normal file
@ -0,0 +1,96 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a role="button" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'app.admin.icalendar.icalendar_import' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="row no-gutter">
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div class="alert alert-info m-lg" translate>
|
||||
{{ 'app.admin.icalendar.intro' }}
|
||||
</div>
|
||||
<div class="wrapper-lg">
|
||||
<table class="table" ng-show="calendars.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 35%;" translate>{{ 'app.admin.icalendar.name' }}</th>
|
||||
<th style="width: 35%;" translate>{{ 'app.admin.icalendar.url' }}</th>
|
||||
<th translate>{{ 'app.admin.icalendar.display' }}</th>
|
||||
<th style="width: 20%;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="calendar in calendars">
|
||||
<td class="calendar-name">{{calendar.name}}</td>
|
||||
<td class="calendar-url"><a href="{{calendar.url}}" target="_blank">{{calendar.url}}</a></td>
|
||||
<td class="calendar-legend-block text-left"><span class="calendar-legend" ng-style="calendarStyle(calendar)" translate> {{ calendar.text_hidden ? '' : 'app.admin.icalendar.example' }}</span>
|
||||
<td class="calendar-actions">
|
||||
<button class="btn btn-info" ng-click="sync(calendar)"><i class="fa fa-refresh"></i></button>
|
||||
<button class="btn btn-danger" ng-click="delete(calendar)"><i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<form class="calendar-form" name="newImportForm">
|
||||
<h4 translate>{{ 'app.admin.icalendar.new_import' }}</h4>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-font"></i>
|
||||
</div>
|
||||
<input type="text" ng-model="newCalendar.name" class="form-control" placeholder="{{ 'app.admin.icalendar.name' | translate }}" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-link"></i>
|
||||
</div>
|
||||
<input type="url" ng-model="newCalendar.url" class="form-control" placeholder="{{ 'app.admin.icalendar.url' | translate }}" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</div>
|
||||
<input type="text" minicolors ng-model="newCalendar.color" class="form-control" placeholder="{{ 'app.admin.icalendar.color' | translate}}" required/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</div>
|
||||
<input type="text" minicolors ng-model="newCalendar.text_color" class="form-control" placeholder="{{ 'app.admin.icalendar.text_color' | translate}}" ng-required="!newCalendar.text_hidden"/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="hideText" class="control-label m-r" translate>{{ 'app.admin.icalendar.hide_text' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="newCalendar.text_hidden"
|
||||
id="hideText"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'app.admin.icalendar.hidden' | translate }}"
|
||||
switch-off-text="{{ 'app.admin.icalendar.shown' | translate }}"
|
||||
switch-animate="true"/>
|
||||
</div>
|
||||
<div class="m-t text-right">
|
||||
<button role="button" class="btn btn-warning" ng-click="save()" ng-disabled="newImportForm.$invalid" translate>
|
||||
{{ 'app.shared.buttons.confirm' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -1,15 +1,15 @@
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[name]'].$dirty && couponForm['coupon[name]'].$invalid}">
|
||||
<label for="coupon[name]">{{ 'name' | translate }} *</label>
|
||||
<label for="coupon[name]">{{ 'app.shared.coupon.name' | translate }} *</label>
|
||||
<input type="text" id="coupon[name]"
|
||||
name="coupon[name]"
|
||||
class="form-control"
|
||||
ng-model="coupon.name"
|
||||
required="required"/>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[name]'].$dirty && couponForm['coupon[name]'].$error.required" translate>{{ 'name_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[name]'].$dirty && couponForm['coupon[name]'].$error.required" translate>{{ 'app.shared.coupon.name_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$invalid}">
|
||||
<label for="coupon[code]">{{ 'code' | translate }} *</label>
|
||||
<label for="coupon[code]">{{ 'app.shared.coupon.code' | translate }} *</label>
|
||||
<input type="text" id="coupon[code]"
|
||||
name="coupon[code]"
|
||||
class="form-control"
|
||||
@ -17,25 +17,25 @@
|
||||
ng-pattern="/^[A-Z0-9\-]+$/"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
required="required"/>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$error.required" translate>{{ 'code_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$error.pattern" translate>{{ 'code_must_be_composed_of_capital_letters_digits_and_or_dashes' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$error.required" translate>{{ 'app.shared.coupon.code_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$error.pattern" translate>{{ 'app.shared.coupon.code_must_be_composed_of_capital_letters_digits_and_or_dashes' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="coupon[type]">{{ 'kind_of_coupon' | translate }} *</label>
|
||||
<label for="coupon[type]">{{ 'app.shared.coupon.kind_of_coupon' | translate }} *</label>
|
||||
<select id="coupon[type]"
|
||||
name="coupon[type]"
|
||||
class="form-control"
|
||||
ng-model="coupon.type"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
required="required">
|
||||
<option value="percent_off" translate>{{ 'percentage' }}</option>
|
||||
<option value="amount_off" translate>{{ 'amount' }}</option>
|
||||
<option value="percent_off" translate>{{ 'app.shared.coupon.percentage' }}</option>
|
||||
<option value="amount_off" translate>{{ 'app.shared.coupon.amount' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$invalid}" ng-show="coupon.type == 'percent_off'">
|
||||
<label for="coupon[percent_off]">{{ 'percent_off' | translate }} *</label>
|
||||
<label for="coupon[percent_off]">{{ 'app.shared.coupon.percent_off' | translate }} *</label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="coupon[percent_off]"
|
||||
name="coupon[percent_off]"
|
||||
@ -47,13 +47,13 @@
|
||||
ng-required="coupon.type == 'percent_off'"/>
|
||||
<span class="input-group-addon"><i class="fa fa-percent"></i></span>
|
||||
</div>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$error.required" translate>{{ 'percent_off_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && (couponForm['coupon[percent_off]'].$error.min || couponForm['coupon[percent_off]'].$error.max)" translate>{{ 'percentage_must_be_between_0_and_100' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$error.required" translate>{{ 'app.shared.coupon.percent_off_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && (couponForm['coupon[percent_off]'].$error.min || couponForm['coupon[percent_off]'].$error.max)" translate>{{ 'app.shared.coupon.percentage_must_be_between_0_and_100' }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[amount_off]'].$dirty && couponForm['coupon[amount_off]'].$invalid}" ng-show="coupon.type == 'amount_off'">
|
||||
<label for="coupon[amount_off]">{{ 'amount_off' | translate }} *</label>
|
||||
<label for="coupon[amount_off]">{{ 'app.shared.coupon.amount_off' | translate }} *</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{currencySymbol}}</span>
|
||||
<input type="number" id="coupon[amount_off]"
|
||||
@ -64,25 +64,25 @@
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
ng-required="coupon.type == 'amount_off'"/>
|
||||
</div>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$error.required" translate>{{ 'percent_off_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && (couponForm['coupon[percent_off]'].$error.min || couponForm['coupon[percent_off]'].$error.max)" translate>{{ 'percentage_must_be_between_0_and_100' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$error.required" translate>{{ 'app.shared.coupon.percent_off_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[percent_off]'].$dirty && (couponForm['coupon[percent_off]'].$error.min || couponForm['coupon[percent_off]'].$error.max)" translate>{{ 'app.shared.coupon.percentage_must_be_between_0_and_100' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[validity_per_user]'].$dirty && couponForm['coupon[validity_per_user]'].$invalid}">
|
||||
<label for="coupon[validity_per_user]">{{ 'validity_per_user' | translate }} *</label>
|
||||
<label for="coupon[validity_per_user]">{{ 'app.shared.coupon.validity_per_user' | translate }} *</label>
|
||||
<select id="coupon[validity_per_user]"
|
||||
name="coupon[validity_per_user]"
|
||||
class="form-control"
|
||||
ng-model="coupon.validity_per_user"
|
||||
required="required"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
ng-options="( validity | translate ) for validity in validities">
|
||||
ng-options="validityName(validity) for validity in validities">
|
||||
</select>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[validity_per_user]'].$dirty && couponForm['coupon[validity_per_user]'].$error.required" translate>{{ 'validity_per_user_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[validity_per_user]'].$dirty && couponForm['coupon[validity_per_user]'].$error.required" translate>{{ 'app.shared.coupon.validity_per_user_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': errors['valid_until']}">
|
||||
<label for="coupon[valid_until]" translate>{{ 'valid_until' }}</label>
|
||||
<label for="coupon[valid_until]" translate>{{ 'app.shared.coupon.valid_until' }}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="coupon[valid_until]"
|
||||
name="coupon[valid_until]"
|
||||
@ -100,34 +100,34 @@
|
||||
<span class="help-block error" ng-show="errors['valid_until']">{{ errors['valid_until'].join(' ; ') }}</span>
|
||||
|
||||
<span class="text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'leave_empty_for_no_limit' | translate }}
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'app.shared.coupon.leave_empty_for_no_limit' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[max_usages]'].$dirty && couponForm['coupon[max_usages]'].$invalid}">
|
||||
<label for="coupon[max_usages]">{{ 'max_usages' | translate }}</label>
|
||||
<label for="coupon[max_usages]">{{ 'app.shared.coupon.max_usages' | translate }}</label>
|
||||
<input type="number" id="coupon[max_usages]"
|
||||
name="coupon[max_usages]"
|
||||
class="form-control"
|
||||
ng-model="coupon.max_usages"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
min="0"/>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[max_usages]'].$dirty && couponForm['coupon[max_usages]'].$error.min" translate>{{ 'max_usages_must_be_equal_or_greater_than_0' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[max_usages]'].$dirty && couponForm['coupon[max_usages]'].$error.min" translate>{{ 'app.shared.coupon.max_usages_must_be_equal_or_greater_than_0' }}</span>
|
||||
|
||||
<span class="text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'leave_empty_for_no_limit' | translate }}
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'app.shared.coupon.leave_empty_for_no_limit' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="coupon[active]" translate>{{ 'enabled' }}</label>
|
||||
<label for="coupon[active]" translate>{{ 'app.shared.coupon.enabled' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="coupon.active"
|
||||
id="coupon[active]"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
||||
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
||||
switch-animate="true" />
|
||||
<input type="hidden" name="coupon[active]" value="{{coupon.active}}"/>
|
||||
</div>
|
||||
|
@ -2,18 +2,18 @@
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
<a ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left pointer"></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-7 col-sm-7 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1>{{ 'coupon' | translate }} : {{ coupon.name }}</h1>
|
||||
<h1>{{ 'app.admin.coupons_edit.coupon' | translate }} {{ coupon.name }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-3 col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-lg btn-block btn-default m-t-xs" ui-sref="app.admin.pricing" translate>{{ 'cancel' }}</a>
|
||||
<a class="btn btn-lg btn-block btn-default m-t-xs" ui-sref="app.admin.pricing" translate>{{ 'app.shared.buttons.cancel' }}</a>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -28,10 +28,10 @@
|
||||
<div id="couponForm">
|
||||
<form name="couponForm" novalidate="novalidate" class="col-lg-7 col-lg-offset-2 m-t-lg form-group">
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/coupons/_form.html' %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "admin/coupons/_form.html" %>'"></ng-include>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="button" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="couponForm.$invalid" ng-click="updateCoupon()"/>
|
||||
<input type="button" value="{{ 'app.shared.buttons.confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="couponForm.$invalid" ng-click="updateCoupon()"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -2,12 +2,12 @@
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
<a ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left pointer"></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'add_a_coupon' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.coupons_new.add_a_coupon' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -20,10 +20,10 @@
|
||||
<div id="couponForm">
|
||||
<form name="couponForm" novalidate="novalidate" class="col-lg-10 col-lg-offset-2 m-t-lg form-group">
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/coupons/_form.html' %>'"></ng-include>
|
||||
<ng-include src="'<%= asset_path "admin/coupons/_form.html" %>'"></ng-include>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="button" value="{{ 'save' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="couponForm.$invalid" ng-click="saveCoupon()"/>
|
||||
<input type="button" value="{{ 'app.shared.buttons.save' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="couponForm.$invalid" ng-click="saveCoupon()"/>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<div class="m-t">
|
||||
<h3 translate>{{ 'categories' }}</h3>
|
||||
<p translate>{{ 'at_least_one_category_is_required' }}</p>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('category')" translate>{{ 'add_a_category' }}</button>
|
||||
<h3 class="events-categories" translate>{{ 'app.admin.events.categories' }}</h3>
|
||||
<p translate>{{ 'app.admin.events.at_least_one_category_is_required' }}</p>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('category')" translate>{{ 'app.admin.events.add_a_category' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:80%" translate>{{ 'app.admin.events.name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -28,7 +28,7 @@
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeElement('category', $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
@ -39,12 +39,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 translate>{{ 'themes' }}</h3>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('theme')" translate>{{ 'add_a_theme' }}</button>
|
||||
<h3 class="events-themes" translate>{{ 'app.admin.events.themes' }}</h3>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('theme')" translate>{{ 'app.admin.events.add_a_theme' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:80%" translate>{{ 'app.admin.events.name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -67,7 +67,7 @@
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeElement('theme', $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
@ -78,12 +78,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 translate>{{ 'age_ranges' }}</h3>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('age_range')" translate>{{ 'add_a_range' }}</button>
|
||||
<h3 class="events-age-ranges" translate>{{ 'app.admin.events.age_ranges' }}</h3>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('age_range')" translate>{{ 'app.admin.events.add_a_range' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:80%" translate>{{ 'app.admin.events.name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -106,7 +106,7 @@
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeElement('age_range', $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
@ -117,4 +117,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,31 +7,37 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'fablab_events' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.events.fablab_events' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md" ng-if="isAuthorized(['admin'])">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.admin.events_new" role="button" translate>{{ 'add_an_event' }}</a>
|
||||
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.admin.events_new" role="button" translate>{{ 'app.admin.events.add_an_event' }}</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="m-lg">
|
||||
<section class="m-lg events-management"
|
||||
ui-tour="events"
|
||||
ui-tour-backdrop="true"
|
||||
ui-tour-template-url="'<%= asset_path "shared/tour-step-template.html" %>'"
|
||||
ui-tour-use-hotkeys="true"
|
||||
ui-tour-scroll-parent-id="content-main"
|
||||
post-render="setupEventsTour">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
<uib-tab heading="{{ 'events_monitoring' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/events/monitoring.html' %>'"></ng-include>
|
||||
<uib-tabset justified="true" active="tabs.active">
|
||||
<uib-tab heading="{{ 'app.admin.events.events_monitoring' | translate }}" index="0">
|
||||
<ng-include src="'<%= asset_path "admin/events/monitoring.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'manage_filters' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/events/filters.html' %>'"></ng-include>
|
||||
<uib-tab heading="{{ 'app.admin.events.manage_filters' | translate }}" index="1">
|
||||
<ng-include src="'<%= asset_path "admin/events/filters.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'manage_prices_categories' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/events/prices.html' %>'"></ng-include>
|
||||
<uib-tab heading="{{ 'app.admin.events.manage_prices_categories' | translate }}" index="2" class="prices-tab">
|
||||
<ng-include src="'<%= asset_path "admin/events/prices.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
|
@ -1,18 +1,18 @@
|
||||
<div class="col-md-6 m-b m-t">
|
||||
<select ng-model="eventsScope.selected" class="form-control" ng-change="changeScope()">
|
||||
<option value="" translate>{{ 'all_events' }}</option>
|
||||
<option value="passed" translate>{{ 'passed_events' }}</option>
|
||||
<option value="future" translate>{{ 'events_to_come' }}</option>
|
||||
<option value="future_asc" translate>{{ 'events_to_come_asc' }}</option>
|
||||
<select ng-model="eventsScope.selected" class="form-control events-list-filter" ng-change="changeScope()">
|
||||
<option value="" translate>{{ 'app.admin.events.all_events' }}</option>
|
||||
<option value="passed" translate>{{ 'app.admin.events.passed_events' }}</option>
|
||||
<option value="future" translate>{{ 'app.admin.events.events_to_come' }}</option>
|
||||
<option value="future_asc" translate>{{ 'app.admin.events.events_to_come_asc' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<table class="table events-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30%" translate>{{ 'title' }}</th>
|
||||
<th style="width:30%" translate>{{ 'dates' }}</th>
|
||||
<th style="width:10%" translate>{{ 'booking' }}</th>
|
||||
<th style="width:30%" translate>{{ 'app.admin.events.title' }}</th>
|
||||
<th style="width:30%" translate>{{ 'app.admin.events.dates' }}</th>
|
||||
<th style="width:10%" translate>{{ 'app.admin.events.booking' }}</th>
|
||||
<th style="width:30%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -27,22 +27,22 @@
|
||||
|
||||
<!--One day event-->
|
||||
<span ng-if="(event.start_date | amDateFormat:'LL')==(event.end_date | amDateFormat:'LL')">
|
||||
{{ 'on_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }}
|
||||
{{ 'app.admin.events.on_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }}
|
||||
<span ng-if="event.all_day == 'false'">
|
||||
{{ 'from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
|
||||
<span class="text-sm font-thin" translate>{{ 'to_time' }}</span>
|
||||
{{ 'app.admin.events.from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
|
||||
<span class="text-sm font-thin" translate>{{ 'app.admin.events.to_time' }}</span>
|
||||
{{event.end_date | amDateFormat:'LT'}}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!--Multiple days event-->
|
||||
<span ng-if="(event.start_date | amDateFormat:'LL')!=(event.end_date | amDateFormat:'LL')">
|
||||
{{'from_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }}
|
||||
{{'to_date' | translate}} {{event.end_date | amDateFormat:'LL'}}
|
||||
{{'app.admin.events.from_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }}
|
||||
{{'app.admin.events.to_date' | translate}} {{event.end_date | amDateFormat:'LL'}}
|
||||
<br ng-if="event.all_day == 'false'"/>
|
||||
<span ng-if="event.all_day == 'false'">
|
||||
{{ 'from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
|
||||
<span class="text-sm font-thin" translate>{{ 'to_time' }}</span>
|
||||
{{ 'app.admin.events.from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
|
||||
<span class="text-sm font-thin" translate>{{ 'app.admin.events.to_time' }}</span>
|
||||
{{event.end_date | amDateFormat:'LT'}}
|
||||
</span>
|
||||
</span>
|
||||
@ -50,17 +50,17 @@
|
||||
|
||||
<td style="vertical-align:middle">
|
||||
<span class="ng-binding" ng-if="event.nb_total_places > 0">{{ event.nb_total_places - event.nb_free_places }} / {{ event.nb_total_places }}</span>
|
||||
<span class="badge font-sbold cancelled" ng-if="event.nb_total_places == -1" translate>{{ 'cancelled' }}</span>
|
||||
<span class="badge font-sbold" ng-if="!event.nb_total_places" translate>{{ 'free_entry' }}</span>
|
||||
<span class="badge font-sbold cancelled" ng-if="event.nb_total_places == -1" translate>{{ 'app.admin.events.cancelled' }}</span>
|
||||
<span class="badge font-sbold" ng-if="!event.nb_total_places" translate>{{ 'app.admin.events.free_entry' }}</span>
|
||||
</td>
|
||||
|
||||
<td style="vertical-align:middle">
|
||||
<div class="buttons">
|
||||
<a class="btn btn-default" ui-sref="app.admin.event_reservations({id: event.id})">
|
||||
<i class="fa fa-bookmark"></i> {{ 'view_reservations' | translate }}
|
||||
<i class="fa fa-bookmark"></i> {{ 'app.admin.events.view_reservations' | translate }}
|
||||
</a>
|
||||
<a class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
@ -70,6 +70,6 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive" translate>{{ 'load_the_next_events' }}</a>
|
||||
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive" translate>{{ 'app.admin.events.load_the_next_events' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="modal-header">
|
||||
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
|
||||
<h1 translate>{{ 'price_category' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.events.price_category' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form" name="priceCategoryForm" class="form-horizontal" novalidate autocomplete="off" ng-keydown="priceCategoryForm.$valid && $event.which == 13 && ok()">
|
||||
@ -12,10 +12,10 @@
|
||||
name="name"
|
||||
ng-model="category.name"
|
||||
class="form-control"
|
||||
placeholder="{{ 'category_name' | translate }}"
|
||||
placeholder="{{ 'app.admin.events.category_name' | translate }}"
|
||||
required />
|
||||
</div>
|
||||
<span class="help-block" ng-show="priceCategoryForm.name.$dirty && priceCategoryForm.name.$error.required" translate>{{ 'category_name_is_required' }}</span>
|
||||
<span class="help-block" ng-show="priceCategoryForm.name.$dirty && priceCategoryForm.name.$error.required" translate>{{ 'app.admin.events.category_name_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -25,16 +25,16 @@
|
||||
rows="10"
|
||||
class="form-control"
|
||||
id="conditions"
|
||||
placeholder="{{ 'enter_here_the_conditions_under_which_this_price_is_applicable' | translate }}"
|
||||
placeholder="{{ 'app.admin.events.enter_here_the_conditions_under_which_this_price_is_applicable' | translate }}"
|
||||
name="conditions"
|
||||
required>
|
||||
</textarea>
|
||||
<span class="help-block" ng-show="priceCategoryForm.conditions.$dirty && priceCategoryForm.conditions.$error.required" translate>{{ 'conditions_are_required' }}</span>
|
||||
<span class="help-block" ng-show="priceCategoryForm.conditions.$dirty && priceCategoryForm.conditions.$error.required" translate>{{ 'app.admin.events.conditions_are_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok()" ng-disabled="priceCategoryForm.$invalid" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-info" ng-click="ok()" ng-disabled="priceCategoryForm.$invalid" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
<div class="m-t">
|
||||
<h3 translate>{{ 'prices_categories' }}</h3>
|
||||
<h3 translate>{{ 'app.admin.events.prices_categories' }}</h3>
|
||||
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="newPriceCategory()" translate>{{ 'add_a_price_category' }}</button>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="newPriceCategory()" translate>{{ 'app.admin.events.add_a_price_category' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40%" translate>{{ 'name' }}</th>
|
||||
<th style="width:40%" translate>{{ 'usages_count' }}</th>
|
||||
<th style="width:40%" translate>{{ 'app.admin.events.name' }}</th>
|
||||
<th style="width:40%" translate>{{ 'app.admin.events.usages_count' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -17,7 +17,7 @@
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ng-click="editPriceCategory(category.id, $index)">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removePriceCategory(category.id, $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
@ -28,4 +28,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1>{{ 'the_reservations' | translate }} {{event.title}}</h1>
|
||||
<h1>{{ 'app.admin.event_reservations.the_reservations' | translate }} {{event.title}}</h1>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@ -20,35 +20,36 @@
|
||||
<table class="table" ng-if="reservations.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:25%" translate>{{ 'user' }}</th>
|
||||
<th style="width:25%" translate>{{ 'payment_date' }}</th>
|
||||
<th style="width:25%" translate>{{ 'reserved_tickets' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.user' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.payment_date' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.reserved_tickets' }}</th>
|
||||
<th style="width:25%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="reservation in reservations">
|
||||
<tr ng-repeat="reservation in reservations" ng-class="{'disabled': isCancelled(reservation)}">
|
||||
<td class="text-c">
|
||||
<a ui-sref="app.logged.members_show({id: reservation.user_id})">{{ reservation.user_full_name }} </a>
|
||||
</td>
|
||||
<td>{{ reservation.created_at | amDateFormat:'LL LTS' }}</td>
|
||||
<td>
|
||||
<span ng-if="reservation.nb_reserve_places > 0">{{ 'full_price_' | translate }} {{reservation.nb_reserve_places}}<br/></span>
|
||||
<span ng-if="reservation.nb_reserve_places > 0">{{ 'app.admin.event_reservations.full_price_' | translate }} {{reservation.nb_reserve_places}}<br/></span>
|
||||
<span ng-repeat="ticket in reservation.tickets">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
|
||||
<div ng-show="isCancelled(reservation)" class="canceled-marker" translate>{{ 'app.admin.event_reservations.canceled' }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.public.events_show({id: event.id})">
|
||||
<i class="fa fa-tag"></i> {{ 'show_the_event' | translate }}
|
||||
<i class="fa fa-tag"></i> {{ 'app.admin.event_reservations.show_the_event' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p ng-if="reservations.length == 0" translate>{{ 'no_reservations_for_now' }}</p>
|
||||
<p ng-if="reservations.length == 0" translate>{{ 'app.admin.event_reservations.no_reservations_for_now' }}</p>
|
||||
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.events" translate>{{ 'back_to_monitoring' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.events" translate>{{ 'app.admin.event_reservations.back_to_monitoring' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,13 +1,13 @@
|
||||
<div class="m-t-lg m-b">
|
||||
<button type="button" class="btn btn-warning" ng-click="addGroup()">
|
||||
<i class="fa fa-plus m-r"></i>
|
||||
<span translate>{{ 'group_form.add_a_group' }}</span>
|
||||
<span translate>{{ 'app.admin.members.group_form.add_a_group' }}</span>
|
||||
</button>
|
||||
<div class="form-group pull-right">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<select ng-model="groupFiltering" class="form-control">
|
||||
<option ng-repeat="status in filterDisabled" value="{{status}}" translate>{{ 'group_form.status_'+status }}</option>
|
||||
<option ng-repeat="status in filterDisabled" value="{{status}}" translate>{{ 'app.admin.members.group_form.status_'+status }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -16,7 +16,7 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 75%;" translate>{{ 'group_form.group_name' }}</th>
|
||||
<th style="width: 75%;" translate>{{ 'app.admin.members.group_form.group_name' }}</th>
|
||||
<th style="width: 25%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -39,11 +39,11 @@
|
||||
</form>
|
||||
<div class="buttons" ng-hide="rowform.$visible || group.slug === 'admins'">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="toggleDisableGroup($index)">
|
||||
<span ng-hide="group.disabled"><i class="fa fa-eye-slash"></i> <span translate>{{ 'group_form.disable' }}</span></span>
|
||||
<span ng-show="group.disabled"><i class="fa fa-eye"></i> <span translate>{{ 'group_form.enable' }}</span></span>
|
||||
<span ng-hide="group.disabled"><i class="fa fa-eye-slash"></i> <span translate>{{ 'app.admin.members.group_form.disable' }}</span></span>
|
||||
<span ng-show="group.disabled"><i class="fa fa-eye"></i> <span translate>{{ 'app.admin.members.group_form.enable' }}</span></span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeGroup($index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<ul>
|
||||
<li><span class="period-info-title" translate>{{ 'invoices.closed_at' }}</span> : <span>{{period.closed_at | amDateFormat:'L'}}</span></li>
|
||||
<li><span class="period-info-title" translate>{{ 'invoices.closed_by' }}</span> : <span>{{period.user_name}}</span></li>
|
||||
<li><span class="period-info-title" translate>{{ 'invoices.period_total' }}</span> : <span>{{period.period_total | currency}}</span></li>
|
||||
<li><span class="period-info-title" translate>{{ 'invoices.perpetual_total' }}</span> : <span>{{period.perpetual_total | currency}}</span></li>
|
||||
<li><span class="period-info-title" translate>{{ 'app.admin.invoices.closed_at' }}</span> : <span>{{period.closed_at | amDateFormat:'L'}}</span></li>
|
||||
<li><span class="period-info-title" translate>{{ 'app.admin.invoices.closed_by' }}</span> : <span>{{period.user_name}}</span></li>
|
||||
<li><span class="period-info-title" translate>{{ 'app.admin.invoices.period_total' }}</span> : <span>{{period.period_total | currency}}</span></li>
|
||||
<li><span class="period-info-title" translate>{{ 'app.admin.invoices.perpetual_total' }}</span> : <span>{{period.perpetual_total | currency}}</span></li>
|
||||
<li>
|
||||
<span class="period-info-title" translate>{{ 'invoices.integrity' }}</span> :
|
||||
<span class="period-info-title" translate>{{ 'app.admin.invoices.integrity' }}</span> :
|
||||
<i class="fa fa-link chained" ng-show="period.chained_footprint"></i>
|
||||
<i class="fa fa-chain-broken broken" ng-hide="period.chained_footprint"></i>
|
||||
</li>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red" translate>{{ 'invoices.export_accounting_data' }}</h3>
|
||||
<h3 class="text-center red" translate>{{ 'app.admin.invoices.export_accounting_data' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form" name="exportForm">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="start_date" translate>{{ 'invoices.export_form_date' }}</label>
|
||||
<label for="start_date" translate>{{ 'app.admin.invoices.export_form_date' }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text"
|
||||
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="end_date" translate>{{ 'invoices.export_to_date' }}</label>
|
||||
<label for="end_date" translate>{{ 'app.admin.invoices.export_to_date' }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text"
|
||||
@ -42,36 +42,36 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h4 class="control-label m-l" translate>{{ 'invoices.export_to' }}</h4>
|
||||
<h4 class="control-label m-l" translate>{{ 'app.admin.invoices.export_to' }}</h4>
|
||||
<div class="form-group m-l-lg">
|
||||
<label for="acd">
|
||||
<input type="radio" name="acd" id="acd" ng-model="exportTarget.software" ng-value="'acd'" ng-click="fillSettings()" required/>
|
||||
{{ 'invoices.acd' | translate }}
|
||||
{{ 'app.admin.invoices.acd' | translate }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="exportTarget.settings">
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.format' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.format' }}</div>
|
||||
<div class="col-md-8">{{ exportTarget.settings.format }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.encoding' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.encoding' }}</div>
|
||||
<div class="col-md-8">{{ exportTarget.settings.encoding }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.separator' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.separator' }}</div>
|
||||
<div class="col-md-8">{{ exportTarget.settings.separator }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.dateFormat' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.dateFormat' }}</div>
|
||||
<div class="col-md-8">
|
||||
<a href="https://apidock.com/ruby/DateTime/strftime" class="help-cursor" target="_blank">{{ exportTarget.settings.dateFormat }}</a>
|
||||
</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.labelMaxLength' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.labelMaxLength' }}</div>
|
||||
<div class="col-md-8">{{ exportTarget.settings.labelMaxLength }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.decimalSeparator' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.decimalSeparator' }}</div>
|
||||
<div class="col-md-8">{{ exportTarget.settings.decimalSeparator }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.exportInvoicesAtZero' }}</div>
|
||||
<div class="col-md-8" translate>{{ exportTarget.settings.exportInvoicesAtZero ? 'yes' : 'no' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'invoices.columns' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.exportInvoicesAtZero' }}</div>
|
||||
<div class="col-md-8" translate>{{ exportTarget.settings.exportInvoicesAtZero ? 'app.shared.buttons.yes' : 'app.shared.buttons.no' }}</div>
|
||||
<div class="col-md-4 font-bold" translate>{{ 'app.admin.invoices.columns' }}</div>
|
||||
<table class="col-md-12 export-table-template">
|
||||
<thead>
|
||||
<tr>
|
||||
<td ng-repeat="column in exportTarget.settings.columns" translate>{{ 'invoices.exportColumns.' + column }}</td>
|
||||
<td ng-repeat="column in exportTarget.settings.columns" translate>{{ 'app.admin.invoices.exportColumns.' + column }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -91,7 +91,7 @@
|
||||
<input name="type" type="hidden" ng-value="exportTarget.software"/>
|
||||
<input name="key" type="hidden" ng-value="query.key"/>
|
||||
<input name="query" type="hidden" ng-value="query.query"/>
|
||||
<input type="submit" class="btn btn-warning" value="{{ 'confirm' | translate }}" formtarget="export-frame"/>
|
||||
<input type="submit" class="btn btn-warning" value="{{ 'app.shared.buttons.confirm' | translate }}" formtarget="export-frame"/>
|
||||
</form>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red" translate>{{ 'invoices.create_a_refund_on_this_invoice' }}</h3>
|
||||
<h3 class="text-center red" translate>{{ 'app.admin.invoices.create_a_refund_on_this_invoice' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="avoirForm" novalidate="novalidate">
|
||||
<div class="form-group" ng-class="{'has-error': avoirForm.avoir_date.$dirty && avoirForm.avoir_date.$invalid }">
|
||||
<label translate>{{ 'invoices.creation_date_for_the_refund' }}</label>
|
||||
<label translate>{{ 'app.admin.invoices.creation_date_for_the_refund' }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text"
|
||||
@ -19,24 +19,24 @@
|
||||
ng-click="openDatePicker($event)"
|
||||
required/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="avoirForm.avoir_date.$dirty && avoirForm.avoir_date.$error.required" translate>{{ 'invoices.creation_date_is_required' }}</span>
|
||||
<span class="help-block" ng-show="avoirForm.avoir_date.$dirty && avoirForm.avoir_date.$error.required" translate>{{ 'app.admin.invoices.creation_date_is_required' }}</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label translate>{{ 'invoices.refund_mode' }}</label>
|
||||
<label translate>{{ 'app.admin.invoices.refund_mode' }}</label>
|
||||
<select class="form-control m-t-sm" name="payment_method" ng-model="avoir.payment_method" ng-options="mode.value as mode.name for mode in avoirModes" required></select>
|
||||
</div>
|
||||
<div class="form-group" ng-if="invoice.is_subscription_invoice">
|
||||
<label translate>{{ 'invoices.do_you_want_to_disable_the_user_s_subscription' }}</label>
|
||||
<label translate>{{ 'app.admin.invoices.do_you_want_to_disable_the_user_s_subscription' }}</label>
|
||||
<select class="form-control m-t-sm" name="subscription_to_expire" ng-model="avoir.subscription_to_expire" ng-options="value as key for (key, value) in subscriptionExpireOptions" required></select>
|
||||
</div>
|
||||
<div ng-show="!invoice.is_subscription_invoice && invoice.items.length > 1" class="form-group">
|
||||
<label translate>{{ 'invoices.elements_to_refund' }}</label>
|
||||
<label translate>{{ 'app.admin.invoices.elements_to_refund' }}</label>
|
||||
<table class="table partial-avoir-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="input-col"></th>
|
||||
<th class="label-col" translate>{{ 'invoices.description' }}</th>
|
||||
<th class="amount-col" translate>{{ 'invoices.price' }}</th>
|
||||
<th class="label-col" translate>{{ 'app.admin.invoices.description' }}</th>
|
||||
<th class="amount-col" translate>{{ 'app.admin.invoices.price' }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -49,13 +49,13 @@
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description" translate>{{ 'invoices.description_optional' }}</label>
|
||||
<p translate>{{ 'invoices.will_appear_on_the_refund_invoice' }}</p>
|
||||
<label for="description" translate>{{ 'app.admin.invoices.description_optional' }}</label>
|
||||
<p translate>{{ 'app.admin.invoices.will_appear_on_the_refund_invoice' }}</p>
|
||||
<textarea class="form-control m-t-sm" name="description" ng-model="avoir.description"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" ng-disabled="avoirForm.$invalid" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" ng-disabled="avoirForm.$invalid" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red" translate>{{ 'invoices.close_accounting_period' }}</h3>
|
||||
<h3 class="text-center red" translate>{{ 'app.admin.invoices.close_accounting_period' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="closePeriodForm" novalidate="novalidate" class="row">
|
||||
<div class="form-group col-md-6" ng-class="{'has-error': closePeriodForm.start_at.$dirty && closePeriodForm.start_at.$invalid }">
|
||||
<label translate>{{ 'invoices.close_from_date' }}</label>
|
||||
<label translate>{{ 'app.admin.invoices.close_from_date' }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text"
|
||||
@ -21,11 +21,11 @@
|
||||
readonly
|
||||
required/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="closePeriodForm.start_at.$dirty && closePeriodForm.start_at.$error.required" translate>{{ 'invoices.start_date_is_required' }}</span>
|
||||
<span class="help-block" ng-show="closePeriodForm.start_at.$dirty && closePeriodForm.start_at.$error.required" translate>{{ 'app.admin.invoices.start_date_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="errors.start_at">{{ errors.start_at[0] }}</span>
|
||||
</div>
|
||||
<div class="form-group col-md-6" ng-class="{'has-error': closePeriodForm.end_at.$dirty && closePeriodForm.end_at.$invalid }">
|
||||
<label translate>{{ 'invoices.close_until_date' }}</label>
|
||||
<label translate>{{ 'app.admin.invoices.close_until_date' }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text"
|
||||
@ -43,7 +43,7 @@
|
||||
required
|
||||
readonly/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="closePeriodForm.end_at.$dirty && closePeriodForm.end_at.$error.required" translate>{{ 'invoices.end_date_is_required' }}</span>
|
||||
<span class="help-block" ng-show="closePeriodForm.end_at.$dirty && closePeriodForm.end_at.$error.required" translate>{{ 'app.admin.invoices.end_date_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="errors.end_at">{{ errors.end_at[0] }}</span>
|
||||
</div>
|
||||
</form>
|
||||
@ -51,12 +51,12 @@
|
||||
<span class="help-block error">{{ $parent.invoiceErrorRE.exec(key)[1] }} : {{ value[0] }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 translate>{{ 'invoices.previous_closings' }}</h4>
|
||||
<h4 translate>{{ 'app.admin.invoices.previous_closings' }}</h4>
|
||||
<table class="table closings-table" ng-show="accountingPeriods.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'invoices.start_date' }}</th>
|
||||
<th translate>{{ 'invoices.end_date' }}</th>
|
||||
<th translate>{{ 'app.admin.invoices.start_date' }}</th>
|
||||
<th translate>{{ 'app.admin.invoices.end_date' }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -72,10 +72,10 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-show="accountingPeriods.length === 0" translate>{{ 'invoices.no_periods'}}</div>
|
||||
<div ng-show="accountingPeriods.length === 0" translate>{{ 'app.admin.invoices.no_periods'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" ng-disabled="closePeriodForm.$invalid || pendingCreation" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" ng-disabled="pendingCreation" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" ng-disabled="closePeriodForm.$invalid || pendingCreation" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" ng-disabled="pendingCreation" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
|
@ -5,33 +5,39 @@
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'invoices.invoices' }}</h1>
|
||||
<h1 translate>{{ 'app.admin.invoices.invoices' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
||||
<div class="col-xs-12 col-sm-12 col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-default rounded m-t-sm" ng-click="toggleExportModal()"><i class="fa fa-book"></i></a>
|
||||
<a class="btn btn-default rounded m-t-sm export-accounting-button" ng-click="toggleExportModal()"><i class="fa fa-book"></i></a>
|
||||
<iframe name="export-frame" height="0" width="0" class="none" id="accounting-export-frame"></iframe>
|
||||
<a class="btn btn-lg btn-default rounded m-t-sm text-sm" ng-click="closeAnAccountingPeriod()"><i class="fa fa-calendar-check-o"></i> {{ 'invoices.accounting_periods' | translate }}</a>
|
||||
<a class="btn btn-lg btn-default rounded m-t-sm text-sm close-accounting-periods-button" ng-click="closeAnAccountingPeriod()"><i class="fa fa-calendar-check-o"></i> {{ 'app.admin.invoices.accounting_periods' | translate }}</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="m-lg">
|
||||
<section class="m-lg invoices-management"
|
||||
ui-tour="invoices"
|
||||
ui-tour-backdrop="true"
|
||||
ui-tour-template-url="'<%= asset_path "shared/tour-step-template.html" %>'"
|
||||
ui-tour-use-hotkeys="true"
|
||||
ui-tour-scroll-parent-id="content-main"
|
||||
post-render="setupInvoicesTour">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
<uib-tab heading="{{ 'invoices.invoices_list' | translate }}" ng-hide="fablabWithoutInvoices" active="tabs.listing.active">
|
||||
<h3 class="m-t-xs"><i class="fa fa-filter"></i> {{ 'invoices.filter_invoices' | translate }}</h3>
|
||||
<uib-tabset justified="true" active="tabs.active">
|
||||
<uib-tab heading="{{ 'app.admin.invoices.invoices_list' | translate }}" ng-hide="fablabWithoutInvoices" index="0">
|
||||
<h3 class="m-t-xs"><i class="fa fa-filter"></i> {{ 'app.admin.invoices.filter_invoices' | translate }}</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" translate>{{ 'invoices.invoice_num_' }}</span>
|
||||
<span class="input-group-addon" translate>{{ 'app.admin.invoices.invoice_num_' }}</span>
|
||||
<input type="text" ng-model="searchInvoice.reference" class="form-control" placeholder="" ng-change="handleFilterChange()">
|
||||
</div>
|
||||
</div>
|
||||
@ -40,7 +46,7 @@
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" translate>{{ 'invoices.customer_' }}</span>
|
||||
<span class="input-group-addon" translate>{{ 'app.admin.invoices.customer_' }}</span>
|
||||
<input type="text" ng-model="searchInvoice.name" class="form-control" placeholder="" ng-change="handleFilterChange()">
|
||||
</div>
|
||||
</div>
|
||||
@ -49,7 +55,7 @@
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{ "invoices.date_" | translate }}</span>
|
||||
<span class="input-group-addon">{{ "app.admin.invoices.date_" | translate }}</span>
|
||||
<input type="date" ng-model="searchInvoice.date" class="form-control" ng-change="handleFilterChange()">
|
||||
</div>
|
||||
</div>
|
||||
@ -60,24 +66,24 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<table class="table" ng-if="invoices.length > 0">
|
||||
<table class="table invoices-list" ng-if="invoices.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:5%"></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderInvoice('reference')">{{ 'invoices.invoice_num' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='reference', 'fa fa-sort-numeric-desc': orderInvoice=='-reference', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderInvoice('reference')">{{ 'app.admin.invoices.invoice_num' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='reference', 'fa fa-sort-numeric-desc': orderInvoice=='-reference', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
|
||||
<th style="width:20%"><a href="" ng-click="setOrderInvoice('date')">{{ 'invoices.date' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='date', 'fa fa-sort-numeric-desc': orderInvoice=='-date', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
<th style="width:20%"><a href="" ng-click="setOrderInvoice('date')">{{ 'app.admin.invoices.date' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='date', 'fa fa-sort-numeric-desc': orderInvoice=='-date', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderInvoice('total')"> {{ 'invoices.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
<th style="width:10%"><a href="" ng-click="setOrderInvoice('total')"> {{ 'app.admin.invoices.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderInvoice=='total', 'fa fa-sort-numeric-desc': orderInvoice=='-total', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
|
||||
<th style="width:20%"><a href="" ng-click="setOrderInvoice('name')">{{ 'invoices.customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
<th style="width:20%"><a href="" ng-click="setOrderInvoice('name')">{{ 'app.admin.invoices.customer' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderInvoice=='name', 'fa fa-sort-alpha-desc': orderInvoice=='-name', 'fa fa-arrows-v': orderInvoice }"></i></a></th>
|
||||
|
||||
<th style="width:30%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="invoice in invoices">
|
||||
<td>
|
||||
<td class="chained-indicator">
|
||||
<i class="fa fa-link chained" ng-show="invoice.chained_footprint"/>
|
||||
<i class="fa fa-chain-broken broken" ng-hide="invoice.chained_footprint"/>
|
||||
</td>
|
||||
@ -90,14 +96,14 @@
|
||||
<span ng-hide="invoice.user_id">{{ invoice.name }}</span>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-default" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="!invoice.is_avoir">
|
||||
<i class="fa fa-file-pdf-o"></i> {{ 'invoices.download_the_invoice' | translate }}
|
||||
<a class="btn btn-default download-button" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="!invoice.is_avoir">
|
||||
<i class="fa fa-file-pdf-o"></i> {{ 'app.admin.invoices.download_the_invoice' | translate }}
|
||||
</a>
|
||||
<a class="btn btn-default" ng-href="api/invoices/{{invoice.id}}/download" target="_blank" ng-if="invoice.is_avoir">
|
||||
<i class="fa fa-file-pdf-o"></i> {{ 'invoices.download_the_credit_note' | translate }}
|
||||
<i class="fa fa-file-pdf-o"></i> {{ 'app.admin.invoices.download_the_credit_note' | translate }}
|
||||
</a>
|
||||
<a class="btn btn-default" ng-click="generateAvoirForInvoice(invoice)" ng-if="(!invoice.has_avoir || invoice.has_avoir == 'partial') && !invoice.is_avoir && !invoice.prevent_refund && !isDateClosed(invoice.created_at)">
|
||||
<i class="fa fa-reply"></i> {{ 'invoices.credit_note' | translate }}
|
||||
<a class="btn btn-default refund-button" ng-click="generateAvoirForInvoice(invoice)" ng-if="(!invoice.has_avoir || invoice.has_avoir == 'partial') && !invoice.is_avoir && !invoice.prevent_refund && !isDateClosed(invoice.created_at)">
|
||||
<i class="fa fa-reply"></i> {{ 'app.admin.invoices.credit_note' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
@ -105,9 +111,9 @@
|
||||
</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> {{ 'invoices.display_more_invoices' | translate }}</button>
|
||||
<button class="btn btn-warning" ng-click="showNextInvoices()" ng-hide="noMoreResults"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'app.admin.invoices.display_more_invoices' | translate }}</button>
|
||||
</div>
|
||||
<p ng-if="invoices.length == 0" translate>{{ 'invoices.no_invoices_for_now' }}</p>
|
||||
<p ng-if="invoices.length == 0" translate>{{ 'app.admin.invoices.no_invoices_for_now' }}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -116,211 +122,211 @@
|
||||
|
||||
|
||||
|
||||
<uib-tab heading="{{ 'invoices.invoicing_settings' | translate }}" active="tabs.settings.active">
|
||||
<div class="alert alert-warning p-md m-t" role="alert" ng-show="fablabWithoutInvoices">
|
||||
<i class="fa fa-warning m-r"></i>
|
||||
<span translate>{{ 'invoices.warning_invoices_disabled' }}</span>
|
||||
</div>
|
||||
<form class="invoice-placeholder">
|
||||
<div class="invoice-logo">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!invoice.logo" class="img-responsive">
|
||||
<img base-sixty-four-image="invoice.logo" ng-if="invoice.logo && invoice.logo.base64">
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'invoices.change_logo' | translate }}
|
||||
<input type="file" accept="image/png,image/jpeg,image/x-png,image/pjpeg" name="invoice[logo][attachment]" ng-model="invoice.logo" base-sixty-four-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invoice-buyer-infos">
|
||||
<strong translate>{{ 'invoices.john_smith' }}</strong>
|
||||
<div translate>{{ 'invoices.john_smith_at_example_com' }}</div>
|
||||
<uib-tab heading="{{ 'app.admin.invoices.invoicing_settings' | translate }}" index="1" class="invoices-settings">
|
||||
<div class="alert alert-warning p-md m-t" role="alert" ng-show="fablabWithoutInvoices">
|
||||
<i class="fa fa-warning m-r"></i>
|
||||
<span translate>{{ 'app.admin.invoices.warning_invoices_disabled' }}</span>
|
||||
</div>
|
||||
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'invoices.invoice_reference_' | translate }} {{mkReference()}}</div>
|
||||
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'invoices.code_' | translate }} {{invoice.code.model}}</div>
|
||||
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'invoices.code_disabled' }}</div>
|
||||
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'invoices.order_num' | translate }} {{mkNumber()}}</div>
|
||||
<div class="invoice-date">{{ 'invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
|
||||
<div class="invoice-object">
|
||||
{{ 'invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
|
||||
</div>
|
||||
<div class="invoice-data">
|
||||
{{ 'invoices.order_summary' | translate }}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'invoices.details' }}</th>
|
||||
<th class="right" translate>{{ 'invoices.amount' }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
<form class="invoice-placeholder">
|
||||
<div class="invoice-logo">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!invoice.logo" class="img-responsive">
|
||||
<img base-sixty-four-image="invoice.logo" ng-if="invoice.logo && invoice.logo.base64">
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'app.admin.invoices.change_logo' | translate }}
|
||||
<input type="file" accept="image/png,image/jpeg,image/x-png,image/pjpeg" name="invoice[logo][attachment]" ng-model="invoice.logo" base-sixty-four-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invoice-buyer-infos">
|
||||
<strong translate>{{ 'app.admin.invoices.john_smith' }}</strong>
|
||||
<div translate>{{ 'app.admin.invoices.john_smith_at_example_com' }}</div>
|
||||
</div>
|
||||
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'app.admin.invoices.invoice_reference_' | translate }} {{mkReference()}}</div>
|
||||
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'app.admin.invoices.code_' | translate }} {{invoice.code.model}}</div>
|
||||
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'app.admin.invoices.code_disabled' }}</div>
|
||||
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'app.admin.invoices.order_num' | translate }} {{mkNumber()}}</div>
|
||||
<div class="invoice-date">{{ 'app.admin.invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
|
||||
<div class="invoice-object">
|
||||
{{ 'app.admin.invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
|
||||
</div>
|
||||
<div class="invoice-data">
|
||||
{{ 'app.admin.invoices.order_summary' | translate }}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'app.admin.invoices.details' }}</th>
|
||||
<th class="right" translate>{{ 'app.admin.invoices.amount' }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'app.admin.invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
|
||||
<tr class="invoice-total" ng-class="{'bold vat-line':invoice.VAT.active}">
|
||||
<td ng-show="!invoice.VAT.active" translate>{{ 'invoices.total_amount' }}</td>
|
||||
<td ng-show="invoice.VAT.active" translate>{{ 'invoices.total_including_all_taxes' }}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-total" ng-class="{'bold vat-line':invoice.VAT.active}">
|
||||
<td ng-show="!invoice.VAT.active" translate>{{ 'app.admin.invoices.total_amount' }}</td>
|
||||
<td ng-show="invoice.VAT.active" translate>{{ 'app.admin.invoices.total_including_all_taxes' }}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
|
||||
<tr class="invoice-vat invoice-activable" ng-click="openEditVAT()" ng-show="!invoice.VAT.active">
|
||||
<td translate>{{ 'invoices.VAT_disabled' }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="invoice-vat invoice-activable" ng-click="openEditVAT()" ng-show="!invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.VAT_disabled' }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
|
||||
<td>{{ 'invoices.including_VAT' | translate }} {{invoice.VAT.rate}} %</td>
|
||||
<td>{{30-(30/(invoice.VAT.rate/100+1)) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-ht vat-line italic" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'invoices.including_total_excluding_taxes' }}</td>
|
||||
<td>{{30/(invoice.VAT.rate/100+1) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-payed vat-line bold" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'invoices.including_amount_payed_on_ordering' }}</td>
|
||||
<td>{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
|
||||
<td>{{ 'app.admin.invoices.including_VAT' | translate }} {{invoice.VAT.rate}} %</td>
|
||||
<td>{{30-(30/(invoice.VAT.rate/100+1)) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-ht vat-line italic" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.including_total_excluding_taxes' }}</td>
|
||||
<td>{{30/(invoice.VAT.rate/100+1) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-payed vat-line bold" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.including_amount_payed_on_ordering' }}</td>
|
||||
<td>{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="invoice-payment" translate translate-values="{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT'), AMOUNT:(30.0 | currency)}">
|
||||
{{ 'invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
|
||||
</p>
|
||||
</div>
|
||||
<div medium-editor class="invoice-text invoice-editable" ng-model="invoice.text.content"
|
||||
options='{
|
||||
"placeholder": "{{ "invoices.important_notes" | translate }}",
|
||||
"buttons": ["underline"]
|
||||
}'
|
||||
ng-blur="textEditEnd($event)">
|
||||
</div>
|
||||
<div medium-editor class="invoice-legals invoice-editable" ng-model="invoice.legals.content"
|
||||
options='{
|
||||
"placeholder": "{{ "invoices.address_and_legal_information" | translate }}",
|
||||
"buttons": ["bold", "underline"]
|
||||
}'
|
||||
ng-blur="legalsEditEnd($event)">
|
||||
</div>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="invoice-payment" translate translate-values="{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT'), AMOUNT:(30.0 | currency)}">
|
||||
{{ 'app.admin.invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
|
||||
</p>
|
||||
</div>
|
||||
<div medium-editor class="invoice-text invoice-editable" ng-model="invoice.text.content"
|
||||
options='{
|
||||
"placeholder": "{{ "app.admin.invoices.important_notes" | translate }}",
|
||||
"buttons": ["underline"]
|
||||
}'
|
||||
ng-blur="textEditEnd($event)">
|
||||
</div>
|
||||
<div medium-editor class="invoice-legals invoice-editable" ng-model="invoice.legals.content"
|
||||
options='{
|
||||
"placeholder": "{{ "app.admin.invoices.address_and_legal_information" | translate }}",
|
||||
"buttons": ["bold", "underline"]
|
||||
}'
|
||||
ng-blur="legalsEditEnd($event)">
|
||||
</div>
|
||||
</form>
|
||||
</uib-tab>
|
||||
|
||||
|
||||
|
||||
|
||||
<uib-tab heading="{{ 'invoices.accounting_codes' | translate }}">
|
||||
<uib-tab heading="{{ 'app.admin.invoices.accounting_codes' | translate }}" index="2" class="accounting-codes-tab">
|
||||
<div class="panel panel-default m-t-md accounting-codes">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="journalCode" translate>{{ 'invoices.accounting_journal_code' }}</label>
|
||||
<input type="text" id="journalCode" ng-model="settings.journalCode.value" class="form-control" placeholder="{{ 'invoices.general_journal_code' | translate }}"/>
|
||||
<label for="journalCode" translate>{{ 'app.admin.invoices.accounting_journal_code' }}</label>
|
||||
<input type="text" id="journalCode" ng-model="settings.journalCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_journal_code' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="cardClientCode" translate>{{ 'invoices.accounting_card_client_code' }}</label>
|
||||
<input type="text" id="cardClientCode" ng-model="settings.cardClientCode.value" class="form-control" placeholder="{{ 'invoices.card_client_code' | translate }}" />
|
||||
<label for="cardClientCode" translate>{{ 'app.admin.invoices.accounting_card_client_code' }}</label>
|
||||
<input type="text" id="cardClientCode" ng-model="settings.cardClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.card_client_code' | translate }}" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="cardClientLabel" translate>{{ 'invoices.accounting_card_client_label' }}</label>
|
||||
<input type="text" id="cardClientLabel" ng-model="settings.cardClientLabel.value" class="form-control" placeholder="{{ 'invoices.card_client_label' | translate }}"/>
|
||||
<label for="cardClientLabel" translate>{{ 'app.admin.invoices.accounting_card_client_label' }}</label>
|
||||
<input type="text" id="cardClientLabel" ng-model="settings.cardClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.card_client_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="walletClientCode" translate>{{ 'invoices.accounting_wallet_client_code' }}</label>
|
||||
<input type="text" id="walletClientCode" ng-model="settings.walletClientCode.value" class="form-control" placeholder="{{ 'invoices.wallet_client_code' | translate }}" />
|
||||
<label for="walletClientCode" translate>{{ 'app.admin.invoices.accounting_wallet_client_code' }}</label>
|
||||
<input type="text" id="walletClientCode" ng-model="settings.walletClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.wallet_client_code' | translate }}" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="walletClientLabel" translate>{{ 'invoices.accounting_wallet_client_label' }}</label>
|
||||
<input type="text" id="walletClientLabel" ng-model="settings.walletClientLabel.value" class="form-control" placeholder="{{ 'invoices.wallet_client_label' | translate }}"/>
|
||||
<label for="walletClientLabel" translate>{{ 'app.admin.invoices.accounting_wallet_client_label' }}</label>
|
||||
<input type="text" id="walletClientLabel" ng-model="settings.walletClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.wallet_client_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="otherClientCode" translate>{{ 'invoices.accounting_other_client_code' }}</label>
|
||||
<input type="text" id="otherClientCode" ng-model="settings.otherClientCode.value" class="form-control" placeholder="{{ 'invoices.other_client_code' | translate }}" />
|
||||
<label for="otherClientCode" translate>{{ 'app.admin.invoices.accounting_other_client_code' }}</label>
|
||||
<input type="text" id="otherClientCode" ng-model="settings.otherClientCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.other_client_code' | translate }}" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="otherClientLabel" translate>{{ 'invoices.accounting_other_client_label' }}</label>
|
||||
<input type="text" id="otherClientLabel" ng-model="settings.otherClientLabel.value" class="form-control" placeholder="{{ 'invoices.other_client_label' | translate }}"/>
|
||||
<label for="otherClientLabel" translate>{{ 'app.admin.invoices.accounting_other_client_label' }}</label>
|
||||
<input type="text" id="otherClientLabel" ng-model="settings.otherClientLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.other_client_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="walletCode" translate>{{ 'invoices.accounting_wallet_code' }}</label>
|
||||
<input type="text" id="walletCode" ng-model="settings.walletCode.value" class="form-control" placeholder="{{ 'invoices.general_wallet_code' | translate }}" />
|
||||
<label for="walletCode" translate>{{ 'app.admin.invoices.accounting_wallet_code' }}</label>
|
||||
<input type="text" id="walletCode" ng-model="settings.walletCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_wallet_code' | translate }}" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="walletLabel" translate>{{ 'invoices.accounting_wallet_label' }}</label>
|
||||
<input type="text" id="walletLabel" ng-model="settings.walletLabel.value" class="form-control" placeholder="{{ 'invoices.general_wallet_label' | translate }}"/>
|
||||
<label for="walletLabel" translate>{{ 'app.admin.invoices.accounting_wallet_label' }}</label>
|
||||
<input type="text" id="walletLabel" ng-model="settings.walletLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_wallet_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="vatCode" translate>{{ 'invoices.accounting_vat_code' }}</label>
|
||||
<input type="text" id="vatCode" ng-model="settings.vatCode.value" class="form-control" placeholder="{{ 'invoices.general_vat_code' | translate }}"/>
|
||||
<label for="vatCode" translate>{{ 'app.admin.invoices.accounting_vat_code' }}</label>
|
||||
<input type="text" id="vatCode" ng-model="settings.vatCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_vat_code' | translate }}"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="vatLabel" translate>{{ 'invoices.accounting_vat_label' }}</label>
|
||||
<input type="text" id="vatLabel" ng-model="settings.vatLabel.value" class="form-control" placeholder="{{ 'invoices.general_vat_label' | translate }}"/>
|
||||
<label for="vatLabel" translate>{{ 'app.admin.invoices.accounting_vat_label' }}</label>
|
||||
<input type="text" id="vatLabel" ng-model="settings.vatLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_vat_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="subscriptionCode" translate>{{ 'invoices.accounting_subscription_code' }}</label>
|
||||
<input type="text" id="subscriptionCode" ng-model="settings.subscriptionCode.value" class="form-control" placeholder="{{ 'invoices.general_subscription_code' | translate }}" />
|
||||
<label for="subscriptionCode" translate>{{ 'app.admin.invoices.accounting_subscription_code' }}</label>
|
||||
<input type="text" id="subscriptionCode" ng-model="settings.subscriptionCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_subscription_code' | translate }}" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="subscriptionLabel" translate>{{ 'invoices.accounting_subscription_label' }}</label>
|
||||
<input type="text" id="subscriptionLabel" ng-model="settings.subscriptionLabel.value" class="form-control" placeholder="{{ 'invoices.general_subscription_label' | translate }}"/>
|
||||
<label for="subscriptionLabel" translate>{{ 'app.admin.invoices.accounting_subscription_label' }}</label>
|
||||
<input type="text" id="subscriptionLabel" ng-model="settings.subscriptionLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_subscription_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="machineCode" translate>{{ 'invoices.accounting_Machine_code' }}</label>
|
||||
<input type="text" id="machineCode" ng-model="settings.machineCode.value" class="form-control" placeholder="{{ 'invoices.general_machine_code' | translate }}"/>
|
||||
<label for="machineCode" translate>{{ 'app.admin.invoices.accounting_Machine_code' }}</label>
|
||||
<input type="text" id="machineCode" ng-model="settings.machineCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_machine_code' | translate }}"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="machineLabel" translate>{{ 'invoices.accounting_Machine_label' }}</label>
|
||||
<input type="text" id="machineLabel" ng-model="settings.machineLabel.value" class="form-control" placeholder="{{ 'invoices.general_machine_label' | translate }}"/>
|
||||
<label for="machineLabel" translate>{{ 'app.admin.invoices.accounting_Machine_label' }}</label>
|
||||
<input type="text" id="machineLabel" ng-model="settings.machineLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_machine_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="trainingCode" translate>{{ 'invoices.accounting_Training_code' }}</label>
|
||||
<input type="text" id="trainingCode" ng-model="settings.trainingCode.value" class="form-control" placeholder="{{ 'invoices.general_training_code' | translate }}" />
|
||||
<label for="trainingCode" translate>{{ 'app.admin.invoices.accounting_Training_code' }}</label>
|
||||
<input type="text" id="trainingCode" ng-model="settings.trainingCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_training_code' | translate }}" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="trainingLabel" translate>{{ 'invoices.accounting_Training_label' }}</label>
|
||||
<input type="text" id="trainingLabel" ng-model="settings.trainingLabel.value" class="form-control" placeholder="{{ 'invoices.general_training_label' | translate }}"/>
|
||||
<label for="trainingLabel" translate>{{ 'app.admin.invoices.accounting_Training_label' }}</label>
|
||||
<input type="text" id="trainingLabel" ng-model="settings.trainingLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_training_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="eventCode" translate>{{ 'invoices.accounting_Event_code' }}</label>
|
||||
<input type="text" id="eventCode" ng-model="settings.eventCode.value" class="form-control" placeholder="{{ 'invoices.general_event_code' | translate }}"/>
|
||||
<label for="eventCode" translate>{{ 'app.admin.invoices.accounting_Event_code' }}</label>
|
||||
<input type="text" id="eventCode" ng-model="settings.eventCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_event_code' | translate }}"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="eventLabel" translate>{{ 'invoices.accounting_Event_label' }}</label>
|
||||
<input type="text" id="eventLabel" ng-model="settings.eventLabel.value" class="form-control" placeholder="{{ 'invoices.general_event_label' | translate }}"/>
|
||||
<label for="eventLabel" translate>{{ 'app.admin.invoices.accounting_Event_label' }}</label>
|
||||
<input type="text" id="eventLabel" ng-model="settings.eventLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_event_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="spaceCode" translate>{{ 'invoices.accounting_Space_code' }}</label>
|
||||
<input type="text" id="spaceCode" ng-model="settings.spaceCode.value" class="form-control" placeholder="{{ 'invoices.general_space_code' | translate }}" />
|
||||
<label for="spaceCode" translate>{{ 'app.admin.invoices.accounting_Space_code' }}</label>
|
||||
<input type="text" id="spaceCode" ng-model="settings.spaceCode.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_space_code' | translate }}" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="spaceLabel" translate>{{ 'invoices.accounting_Space_label' }}</label>
|
||||
<input type="text" id="spaceLabel" ng-model="settings.spaceLabel.value" class="form-control" placeholder="{{ 'invoices.general_space_label' | translate }}"/>
|
||||
<label for="spaceLabel" translate>{{ 'app.admin.invoices.accounting_Space_label' }}</label>
|
||||
<input type="text" id="spaceLabel" ng-model="settings.spaceLabel.value" class="form-control" placeholder="{{ 'app.admin.invoices.general_space_label' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning m-t-lg" ng-click="save()" translate>{{ 'save' }}</button>
|
||||
<button name="button" class="btn btn-warning m-t-lg" ng-click="save()" translate>{{ 'app.shared.buttons.save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
@ -333,125 +339,125 @@
|
||||
<script type="text/ng-template" id="editReference.html">
|
||||
<div class="custom-invoice">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" translate>{{ 'invoices.invoice_reference' }}</h3>
|
||||
<h3 class="modal-title" translate>{{ 'app.admin.invoices.invoice_reference' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body row">
|
||||
<div class="elements col-md-4">
|
||||
<h4>Éléments</h4>
|
||||
<ul>
|
||||
<li ng-click="invoice.reference.help = 'addYear.html'">{{ 'invoices.year' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addMonth.html'">{{ 'invoices.month' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addDay.html'">{{ 'invoices.day' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ 'invoices.num_of_invoice' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addOnlineInfo.html'">{{ 'invoices.online_sales' | translate }}</li>
|
||||
<%# <li ng-click="invoice.reference.help = 'addWalletInfo.html'">{{ 'invoices.wallet' | translate }}</li> %>
|
||||
<li ng-click="invoice.reference.help = 'addRefundInfo.html'">{{ 'invoices.refund' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addYear.html'">{{ 'app.admin.invoices.year' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addMonth.html'">{{ 'app.admin.invoices.month' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addDay.html'">{{ 'app.admin.invoices.day' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ 'app.admin.invoices.num_of_invoice' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addOnlineInfo.html'">{{ 'app.admin.invoices.online_sales' | translate }}</li>
|
||||
<%# <li ng-click="invoice.reference.help = 'addWalletInfo.html'">{{ 'app.admin.invoices.wallet' | translate }}</li> %>
|
||||
<li ng-click="invoice.reference.help = 'addRefundInfo.html'">{{ 'app.admin.invoices.refund' | translate }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="model">
|
||||
<h4 translate>{{ 'invoices.model' }}</h4>
|
||||
<h4 translate>{{ 'app.admin.invoices.model' }}</h4>
|
||||
<input type="text" class="form-control" ng-model="model">
|
||||
</div>
|
||||
<div class="help">
|
||||
<h4 translate>{{ 'invoices.documentation' }}</h4>
|
||||
<h4 translate>{{ 'app.admin.invoices.documentation' }}</h4>
|
||||
<ng-include src="invoice.reference.help" autoscroll="true">
|
||||
</ng-include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addYear.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>YY</strong></td><td translate>{{ 'invoices.2_digits_year' }}</td></tr>
|
||||
<tr><td><strong>YYYY</strong></td><td translate>{{ 'invoices.4_digits_year' }}</td></tr>
|
||||
<tr><td><strong>YY</strong></td><td translate>{{ 'app.admin.invoices.2_digits_year' }}</td></tr>
|
||||
<tr><td><strong>YYYY</strong></td><td translate>{{ 'app.admin.invoices.4_digits_year' }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addMonth.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>M</strong></td><td translate>{{ 'invoices.month_number' }}</td></tr>
|
||||
<tr><td><strong>MM</strong></td><td translate>{{ 'invoices.2_digits_month_number' }}</td></tr>
|
||||
<tr><td><strong>MMM</strong></td><td translate>{{ 'invoices.3_characters_month_name' }}</td></tr>
|
||||
<tr><td><strong>M</strong></td><td translate>{{ 'app.admin.invoices.month_number' }}</td></tr>
|
||||
<tr><td><strong>MM</strong></td><td translate>{{ 'app.admin.invoices.2_digits_month_number' }}</td></tr>
|
||||
<tr><td><strong>MMM</strong></td><td translate>{{ 'app.admin.invoices.3_characters_month_name' }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addDay.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>D</strong></td><td translate>{{ 'invoices.day_in_the_month' }}</td></tr>
|
||||
<tr><td><strong>DD</strong></td><td translate>{{ 'invoices.2_digits_day_in_the_month' }}</td></tr>
|
||||
<tr><td><strong>D</strong></td><td translate>{{ 'app.admin.invoices.day_in_the_month' }}</td></tr>
|
||||
<tr><td><strong>DD</strong></td><td translate>{{ 'app.admin.invoices.2_digits_day_in_the_month' }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addInvoiceNumber.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'invoices.n_digits_daily_count_of_invoices' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'invoices.n_digits_monthly_count_of_invoices' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'invoices.n_digits_annual_amount_of_invoices' }}</td></tr>
|
||||
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'app.admin.invoices.n_digits_daily_count_of_invoices' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'app.admin.invoices.n_digits_monthly_count_of_invoices' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'app.admin.invoices.n_digits_annual_amount_of_invoices' }}</td></tr>
|
||||
</table>
|
||||
<span class="bottom-notes" translate>{{ 'invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
|
||||
<span class="bottom-notes" translate>{{ 'app.admin.invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addOrderNumber.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>nn...nn</strong></td><td translate>{{ 'invoices.n_digits_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'invoices.n_digits_daily_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'invoices.n_digits_monthly_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'invoices.n_digits_annual_amount_of_orders' }}</td></tr>
|
||||
<tr><td><strong>nn...nn</strong></td><td translate>{{ 'app.admin.invoices.n_digits_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>dd...dd</strong></td><td translate>{{ 'app.admin.invoices.n_digits_daily_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>mm...mm</strong></td><td translate>{{ 'app.admin.invoices.n_digits_monthly_count_of_orders' }}</td></tr>
|
||||
<tr><td><strong>yy...yy</strong></td><td translate>{{ 'app.admin.invoices.n_digits_annual_amount_of_orders' }}</td></tr>
|
||||
</table>
|
||||
<span class="bottom-notes" translate>{{ 'invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
|
||||
<span class="bottom-notes" translate>{{ 'app.admin.invoices.beware_if_the_number_exceed_the_specified_length_it_will_be_truncated_by_the_left' }}</span>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addOnlineInfo.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>X[texte]</strong></td><td>{{ 'invoices.add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'invoices.eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe' | translate }}</td></tr>
|
||||
<tr><td><strong>X[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_the_online_sales_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_XVL_will_add_VL_to_the_invoices_settled_with_stripe' | translate }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addWalletInfo.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>W[texte]</strong></td><td>{{ 'invoices.add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'invoices.eg_WPM_will_add_PM_to_the_invoices_settled_with_wallet' | translate }}</td></tr>
|
||||
<tr><td><strong>W[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_WPM_will_add_PM_to_the_invoices_settled_with_wallet' | translate }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addRefundInfo.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>R[texte]</strong></td><td>{{ 'invoices.add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'invoices.this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ 'invoices.eg_RA_will_add_A_to_the_refund_invoices' | translate }}</td></tr>
|
||||
<tr><td><strong>R[texte]</strong></td><td>{{ 'app.admin.invoices.add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'app.admin.invoices.this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ 'app.admin.invoices.eg_RA_will_add_A_to_the_refund_invoices' | translate }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="editCode.html">
|
||||
<div class="custom-invoice">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" translate>{{ 'invoices.code' }}</h3>
|
||||
<h3 class="modal-title" translate>{{ 'app.admin.invoices.code' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="enableCode" class="control-label" translate>{{ 'invoices.enable_the_code' }}</label>
|
||||
<label for="enableCode" class="control-label" translate>{{ 'app.admin.invoices.enable_the_code' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="isSelected"
|
||||
id="enableCode"
|
||||
type="checkbox"
|
||||
class="form-control m-l-sm"
|
||||
switch-on-text="{{ 'invoices.enabled' | translate }}"
|
||||
switch-off-text="{{ 'invoices.disabled' | translate }}"
|
||||
switch-on-text="{{ 'app.admin.invoices.enabled' | translate }}"
|
||||
switch-off-text="{{ 'app.admin.invoices.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="isSelected">
|
||||
<label for="codeModel" class="control-label" translate>{{ 'invoices.code' }}</label>
|
||||
<label for="codeModel" class="control-label" translate>{{ 'app.admin.invoices.code' }}</label>
|
||||
<input id="codeModel" type="text" ng-model="codeModel" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
@ -461,33 +467,33 @@
|
||||
<script type="text/ng-template" id="editNumber.html">
|
||||
<div class="custom-invoice">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" translate>{{ 'invoices.order_number' }}</h3>
|
||||
<h3 class="modal-title" translate>{{ 'app.admin.invoices.order_number' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body row">
|
||||
<div class="elements col-md-4">
|
||||
<h4 translate>{{ 'invoices.elements' }}</h4>
|
||||
<h4 translate>{{ 'app.admin.invoices.elements' }}</h4>
|
||||
<ul>
|
||||
<li ng-click="invoice.number.help = 'addYear.html'">{{ 'invoices.year' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addMonth.html'">{{ 'invoices.month' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addDay.html'">{{ 'invoices.day' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addOrderNumber.html'">{{ 'invoices.order_num' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addYear.html'">{{ 'app.admin.invoices.year' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addMonth.html'">{{ 'app.admin.invoices.month' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addDay.html'">{{ 'app.admin.invoices.day' | translate }}</li>
|
||||
<li ng-click="invoice.number.help = 'addOrderNumber.html'">{{ 'app.admin.invoices.order_num' | translate }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="model">
|
||||
<h4 translate>{{ 'invoices.model' }}</h4>
|
||||
<h4 translate>{{ 'app.admin.invoices.model' }}</h4>
|
||||
<input type="text" class="form-control" ng-model="model">
|
||||
</div>
|
||||
<div class="help">
|
||||
<h4 translate>{{ 'invoices.documentation' }}</h4>
|
||||
<h4 translate>{{ 'app.admin.invoices.documentation' }}</h4>
|
||||
<ng-include src="invoice.number.help" autoscroll="true">
|
||||
</ng-include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
@ -496,23 +502,23 @@
|
||||
<script type="text/ng-template" id="editVAT.html">
|
||||
<div class="custom-invoice">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" translate>{{ 'invoices.VAT' }}</h3>
|
||||
<h3 class="modal-title" translate>{{ 'app.admin.invoices.VAT' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="enableVAT" class="control-label" translate>{{ 'invoices.enable_VAT' }}</label>
|
||||
<label for="enableVAT" class="control-label" translate>{{ 'app.admin.invoices.enable_VAT' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="isSelected"
|
||||
id="enableVAT"
|
||||
type="checkbox"
|
||||
class="form-control m-l-sm"
|
||||
switch-on-text="{{ 'invoices.enabled' | translate }}"
|
||||
switch-off-text="{{ 'invoices.disabled' | translate }}"
|
||||
switch-on-text="{{ 'app.admin.invoices.enabled' | translate }}"
|
||||
switch-off-text="{{ 'app.admin.invoices.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="isSelected">
|
||||
<label for="vatRate" class="control-label" translate>{{ 'invoices.VAT_rate' }}</label>
|
||||
<label for="vatRate" class="control-label" translate>{{ 'app.admin.invoices.VAT_rate' }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">% </span>
|
||||
<input id="vatRate" type="number" ng-model="rate" class="form-control" min="0" max="100"/>
|
||||
@ -520,32 +526,32 @@
|
||||
</div>
|
||||
|
||||
<div class="m-t-lg">
|
||||
<h4 translate>{{ 'invoices.VAT_history' }}</h4>
|
||||
<h4 translate>{{ 'app.admin.invoices.VAT_history' }}</h4>
|
||||
<table class="table scrollable-3-cols">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'invoices.VAT_rate' }}</th>
|
||||
<th translate>{{ 'invoices.changed_at' }}</th>
|
||||
<th translate>{{ 'invoices.changed_by' }}</th>
|
||||
<th translate>{{ 'app.admin.invoices.VAT_rate' }}</th>
|
||||
<th translate>{{ 'app.admin.invoices.changed_at' }}</th>
|
||||
<th translate>{{ 'app.admin.invoices.changed_by' }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="value in history | orderBy:'-date'">
|
||||
<td>
|
||||
<span class="no-user-label" ng-show="value.enabled === false" translate>{{'invoices.VAT_disabled'}}</span>
|
||||
<span class="no-user-label" ng-show="value.enabled === true" translate>{{'invoices.VAT_enabled'}}</span>
|
||||
<span class="no-user-label" ng-show="value.enabled === false" translate>{{'app.admin.invoices.VAT_disabled'}}</span>
|
||||
<span class="no-user-label" ng-show="value.enabled === true" translate>{{'app.admin.invoices.VAT_enabled'}}</span>
|
||||
<span ng-show="value.rate">{{value.rate}}</span>
|
||||
</td>
|
||||
<td>{{value.date | amDateFormat:'L LT'}}</td>
|
||||
<td>{{value.user.name}}<span class="no-user-label" ng-hide="value.user" translate>{{ 'invoices.deleted_user' }}</span></td>
|
||||
<td>{{value.user.name}}<span class="no-user-label" ng-hide="value.user" translate>{{ 'app.admin.invoices.deleted_user' }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
@ -1,18 +1,18 @@
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$invalid}">
|
||||
<label for="user_group_id" class="col-sm-3 control-label">
|
||||
<span translate>{{ 'group' }}</span>
|
||||
<span translate>{{ 'app.shared.user_admin.group' }}</span>
|
||||
<span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<select ng-model="user.group_id" ng-disabled="user.subscribed_plan" class="form-control" name="user[group_id]" id="user_group_id" ng-options="g.id as g.name for g in groups" required>
|
||||
</select>
|
||||
<input type="hidden" name="user[group_id]" ng-value="user.group_id" />
|
||||
<span class="help-block" ng-show="userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$error.required" translate>{{ 'group_is_required' }}</span>
|
||||
<span class="help-block" ng-show="userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$error.required" translate>{{ 'app.shared.user_admin.group_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" translate>{{ 'trainings' }}</label>
|
||||
<label class="col-sm-2 control-label" translate>{{ 'app.shared.user_admin.trainings' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden" name="user[statistic_profile_attributes][training_ids][]" value="" />
|
||||
<ui-select multiple ng-model="user.training_ids" class="form-control">
|
||||
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" translate>{{ 'tags' }}</label>
|
||||
<label class="col-sm-2 control-label" translate>{{ 'app.shared.user_admin.tags' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden" name="user[tag_ids][]" value="" />
|
||||
<ui-select multiple ng-model="user.tag_ids" name="user[tag_ids][]" class="form-control">
|
||||
|
@ -2,23 +2,23 @@
|
||||
<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="searchFilter" class="form-control" placeholder="{{ 'search_for_an_administrator' | translate }}">
|
||||
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'app.admin.members.search_for_an_administrator' | translate }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" translate>{{ 'add_a_new_administrator' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" translate>{{ 'app.admin.members.add_a_new_administrator' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:10%"><a href="" ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -36,4 +36,4 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
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