mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
Merge branch 'dev' of git.sleede.com:projets/fab-manager into dev
This commit is contained in:
commit
2255335727
@ -1 +1 @@
|
||||
2.4.0-dev
|
||||
2.5.0-dev
|
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
This issue tracker is **reserved** for bug reports and feature requests.
|
||||
|
||||
The place to ask a question or call for help is at Fab-manager forums at https://forum.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.
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -15,6 +15,7 @@
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*.log
|
||||
/tmp
|
||||
/coverage
|
||||
|
||||
/public/uploads
|
||||
/public/assets
|
||||
@ -39,5 +40,8 @@
|
||||
.vagrant
|
||||
.docker
|
||||
|
||||
# Do not versionate coveralls token
|
||||
.coveralls.yml
|
||||
|
||||
# Plugins are versioned is their own repository
|
||||
/plugins/*
|
||||
|
134
CHANGELOG.md
134
CHANGELOG.md
@ -1,7 +1,119 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## next release
|
||||
- Project images will show in full-size on a click
|
||||
## next release (v2.5.0)
|
||||
- Ability to remove an unused custom price for an event (#61)
|
||||
- Prevent polling notifications when the application is in background
|
||||
- Ability to export the availabilities and their reservation rate from the admin calendar
|
||||
- Ability to create, manage and reserve spaces
|
||||
- Improved admin's interface to create availabilities
|
||||
- Complete rewrote of the reservation cart functionality with improved stability, performance and sustainability
|
||||
- Replaced letter_opener by MailCatcher to preview e-mails in development environments
|
||||
- Fix a bug: trainings reservations are not shown in the admin's calendar
|
||||
- Fix a bug: unable to delete an administrator from the system
|
||||
- Fix a bug: unable to delete an event with a linked custom price (#61)
|
||||
- Fix a bug: navigation in client calendar is bogus when browsing months (#59)
|
||||
- [TODO DEPLOY] `rake db:migrate`, then `rake db:seed`
|
||||
- [TODO DEPLOY] add the `FABLAB_WITHOUT_SPACES` environment variable
|
||||
- [TODO DEPLOY] `rake fablab:es_add_spaces`
|
||||
|
||||
## v2.4.10 2017 January 9
|
||||
|
||||
- Optimized notifications system
|
||||
- Fix a bug: when many users with too many unread notifications are connected at the same time, the system kill the application due to memory overflow
|
||||
- Fix a bug: ReservationReminderWorker crash with undefined method find_by
|
||||
- Fix a bug: navigation to about page duplicates admin's links in left menu
|
||||
- Fix a bug: changing the price of a plan lost its past statistics
|
||||
- [TODO DEPLOY] `rake db:migrate`
|
||||
- [TODO DEPLOY] `rake fablab:set_plans_slugs`
|
||||
|
||||
## v2.4.9 2017 January 4
|
||||
|
||||
- Mask new notifications alerts when more than 3
|
||||
- Added an asterisk on group select in admin's member form
|
||||
- Statistics custom aggregations can handle custom filtering
|
||||
- Statistics about hours available for machine reservations and tickets available for training reservations, now handle custom filtering on date and type
|
||||
- Fix a bug: display more than 15 unread notifications (number on the bell icon & full list)
|
||||
- Fix a bug: in invoice configuration panel, VAT amount and total excl. taxes are inverted
|
||||
- Fix a bug: unable to compute user's age when they were born on february 29th and current year is not a leap year
|
||||
- Fix a bug: wrong statistics about hours available for machines reservation. Fix requires user action (1)
|
||||
- Fix a bug: when regenerating statistics, previous values are not fully removed (only 10 firsts), resulting in wrong statistics generation (2)
|
||||
- Fix a bug: when deleting an availability just after its creation, the indexer workers crash and retries for a month
|
||||
- [TODO DEPLOY] remove possible value `application/` in `ALLOWED_MIME_TYPES` list, in environment variable
|
||||
- [TODO DEPLOY] `rails runner StatisticCustomAggregation.destroy_all`, then `rake db:seed`, then `rake fablab:es_build_availabilities_index` (1)
|
||||
- [TODO DEPLOY] `rake fablab:generate_stats[1095]` if you already has regenerated the statistics in the past, then they are very likely corrupted. Run this task to fix (2)
|
||||
|
||||
## v2.4.8 2016 December 15
|
||||
|
||||
- Added asterisks on mandatory fields in member's form
|
||||
- Fixed wording on SSO screens
|
||||
- Ability to send again the auth-system migration token by email
|
||||
- Fix a bug: notification email about refund invoice tells about subscription while concerning wallet credit
|
||||
|
||||
## v2.4.7 2016 December 14
|
||||
|
||||
- Improved automated testing
|
||||
- Added an information notice about the processing time of deleting an administrator
|
||||
- Ability to change the expiration date of a coupon after its creation
|
||||
- Ability to generate a refund invoice when crediting user's wallet
|
||||
- Fix a bug: unable to run rake db:migrate on first install
|
||||
- Fix a bug: unable to create or edit a coupon of type 'percentage'
|
||||
|
||||
## v2.4.6 2016 November 30
|
||||
|
||||
- Change display of message about coupon application status
|
||||
- Fix a bug: compute price API return error 500 if reservable_id is not provided
|
||||
|
||||
## v2.4.5 2016 November 29
|
||||
|
||||
- Ability to create coupons with cash amounts (previously only percentages were allowed)
|
||||
- Improved error messages when something wrong append when paying a machine reservation by stripe
|
||||
- Ability to display optional information message on event reservation page
|
||||
- Fix a bug: misconfigured Twitter's ENV variables results in HTTP error 500
|
||||
- Fix a bug: wallet is not debited when paying locally with a user who have invoices disabled
|
||||
- Fix a bug: wrong error message about rounding inconsistency is logged on invoice generation
|
||||
- Fix a bug: reservation calendar of a specific training shows availabilities for all trainings
|
||||
- [TODO DEPLOY] `rake db:migrate`
|
||||
|
||||
## v2.4.4 2016 November 24
|
||||
|
||||
- Fix a bug: unable to rollback migration 20160906145713
|
||||
- Fix a bug: Title's translation for plan's forms is not loaded in French
|
||||
- Fix a bug: invoice of reservation show payment by debit card when user pay with wallet
|
||||
|
||||
## v2.4.3 2016 November 21
|
||||
|
||||
- Export user's invoicing status in members' excel export
|
||||
- Fix a bug: Next events descriptions, shown on the home page, display raw html
|
||||
- Fix a bug: number of reserved seats for an event is always of 1 in the excel export of reservations
|
||||
- Fix a bug: conflict between similar translations around "reservations"
|
||||
- Fix a bug: later occurrences of recurrent events does not have the initially configured theme and age range
|
||||
- Fix a bug: some graphs do not display: events, users, trainings and machine hours
|
||||
- [TODO DEPLOY] delete the `exports/users/reservations` folder to prevent the usage of old invalid exports
|
||||
|
||||
## v2.4.2 2016 November 8
|
||||
|
||||
- Image max size is configurable, default size is 2 megabytes
|
||||
- Allow add more pictures for project step
|
||||
- Ability to use HTML in event's descriptions using a WYSIWYG editor
|
||||
- Fix a bug: statistics graphs were not showing
|
||||
- Fix a bug: On invoices, only starting date is shown for multi-days events
|
||||
- Fix a bug: In the sign-up modal, the translation for 'i_accept_to_receive_information_from_the_fablab' was not loaded
|
||||
- [TODO DEPLOY] add `MAX_IMAGE_SIZE` environment variable in `application.yml` and docker env
|
||||
|
||||
## v2.4.1 2016 October 11
|
||||
|
||||
- Fix a bug: unable to share a project/event without image on social networks
|
||||
- Fix a bug: after creating an element in the admin calendar, browsing through the calendar and coming back cause the element to appear duplicated
|
||||
- Fix a bug: after deleting an element in the admin calendar, the confirmation message is wrong and an error is logged in the console
|
||||
- Fix a bug: erroneous syntax in docker env example file
|
||||
|
||||
## v2.4.0 2016 October 4
|
||||
|
||||
- RSS feeds to follow new projects and events published
|
||||
- Use slugs in projects URL opened from notifications
|
||||
- Ask for confirmation on machine deletion from the public view
|
||||
- Ability to delete a training from the public view for an admin
|
||||
- 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
|
||||
@ -16,14 +128,14 @@
|
||||
- Trainings are associated with a picture and an HTML textual description
|
||||
- Public gallery of trainings with ability to view details or to book a training on its own calendar
|
||||
- Ability to switch back to all trainings booking view
|
||||
- Rename "Courses and Workshops" to "Events"
|
||||
- Rename "Courses and Workshops" to "Events"
|
||||
- Admin: Events can be associated with a theme and an age range
|
||||
- Admin: Event categories, themes and age ranges can be customized
|
||||
- Filter events by category, theme and age range in public view
|
||||
- Ability to customise price's categories for the events
|
||||
- Events can be associated with many custom price's categories, instead of only one "reduced price"
|
||||
- Statistics views can trigger and display custom aggregations from ElasticSearch
|
||||
- Machine hours/Trainings statistics: display number of tickets/hours available for booking
|
||||
- Machine hours/Trainings statistics: display number of tickets/hours available for booking
|
||||
- Statistics will include informations abouts events category, theme and age range
|
||||
- Ability to export the current statistics table to an Excel file
|
||||
- Ability to export every statistics on a given dates range to an Excel file
|
||||
@ -38,25 +150,29 @@
|
||||
- Admins can toggle reminders on/off and customize the delay
|
||||
- More file types allowed as project CAD attachements
|
||||
- Project CAD attachements are now checked by MIME type in addition of extension check
|
||||
- Project CAD attachement allowed are now configured in environment variables
|
||||
- Project CAD attachement extensions allowed are shown next to input field
|
||||
- Display strategy's name in SSO providers list
|
||||
- SSO: documentation improved with an usage example
|
||||
- SSO: mapped fields display their data type. Integers, booleans and dates allow some transformations.
|
||||
- Fix a bug: project drafts are shown on public profiles
|
||||
- Fix a bug: event category disappear when editing the event
|
||||
- Fix a bug: machine name is not shown in plan edition
|
||||
- Fix a bug: machine name is not shown in plan edition
|
||||
- Fix a bug: machine slots with tags are not displayed correctly on reservation calendar
|
||||
- Fix a bug: avatar, address and organization details mapping from SSO were broken
|
||||
- Fix a bug: in SSO configuration some valid endpoints were recognized as erroneous
|
||||
- Fix a bug: in SSO configuration some valid endpoints were recognized as erroneous
|
||||
- Fix a bug: clicking on the text in stripe's payment modal, does not validate the checkbox
|
||||
- Fix a bug: move event reservation is not limited by admin settings (prior-delay & disable)
|
||||
- Fix a bug: UI issues on small devices (dashboard + admin views)
|
||||
- Fix a bug: embedded video not working in training/machine description
|
||||
- Fix a bug: reordering project's steps trigger the unsaved-warning dialog
|
||||
- Fix a bug: unable to compile assets in Docker with CoffeeScript error
|
||||
- Fix a bug: do not force HTTPS for URLs in production environments
|
||||
- [TODO DEPLOY] `rake fablab:es_build_availabilities_index`
|
||||
- [TODO DEPLOY] `rake fablab:es_add_event_filters`
|
||||
- [TODO DEPLOY] `rake db:migrate`
|
||||
- [TODO DEPLOY] `bundle install`
|
||||
- [TODO DEPLOY] add `EXCEL_DATE_FORMAT` environment variable in `application.yml`
|
||||
- [TODO DEPLOY] add `EXCEL_DATE_FORMAT`, `ALLOWED_EXTENSIONS` and `ALLOWED_MIME_TYPES` environment variable in `application.yml`
|
||||
- [OPTIONAL] `rake fablab:fix:assign_category_to_uncategorized_events` (will put every non-categorized events into a new category called "No Category", to ease re-categorization)
|
||||
|
||||
## v2.3.1 2016 September 26
|
||||
@ -76,7 +192,7 @@
|
||||
- [TODO DEPLOY] `bundle install` and `rake db:migrate`
|
||||
|
||||
## v2.2.2 2016 June 23
|
||||
- Fix some bugs: users with uncompleted account (sso imported) won't appear in statistics, in listings and in searches. Moreover, they won't block statistics generation
|
||||
- Fix some bugs: users with uncompleted account (sso imported) won't appear in statistics, in listings and in searches. Moreover, they won't block statistics generation
|
||||
- Fix a bug: unable to display next results in statistics tables
|
||||
- Admin: Category is mandatory when creating an event
|
||||
|
||||
@ -92,7 +208,7 @@
|
||||
- User public profile: UI re-design with possible admin's customization
|
||||
- Admin: Invoices list and users list are now loaded per 10 items to improve pages load time
|
||||
- Admin: select member (eg. to buy a subscription for a member) is now loading the user's list dynamically when you type
|
||||
- Project collaborators selection is now using a list dynamically loaded as you type
|
||||
- Project collaborators selection is now using a list dynamically loaded as you type
|
||||
- Admin: select a training before monitoring its reservations -> improves page load time
|
||||
- API: GET /api/trainings do not load nor send the associated availabilities until they are requested
|
||||
- List of members is now loaded 10 members by 10, to improve page load time
|
||||
|
@ -13,9 +13,8 @@ patches and features.
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The [issue tracker](https://github.com/LaCasemate/fab-manager/issues) is the preferred channel for [bug reports](#bugs),
|
||||
[features requests](#features) and [submitting pull requests](#pull-requests), but please respect the following
|
||||
restrictions:
|
||||
The [issue tracker](https://github.com/LaCasemate/fab-manager/issues) is the preferred channel for [bug reports](#bugs)
|
||||
and [submitting pull requests](#pull-requests), but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for personal support requests (use [the forum](https://forum.fab-manager.com)).
|
||||
|
||||
@ -70,6 +69,9 @@ Feature requests are welcome. But take a moment to find out whether your idea fi
|
||||
project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature.
|
||||
Please provide as much detail and context as possible.
|
||||
|
||||
Please note also that [the forum](https://forum.fab-manager.com) is probably a better place for discussing about feature
|
||||
requests.
|
||||
|
||||
|
||||
<a name="pull-requests"></a>
|
||||
## Pull requests
|
||||
|
5
Gemfile
5
Gemfile
@ -40,7 +40,7 @@ end
|
||||
|
||||
group :development do
|
||||
# Preview mail in the browser
|
||||
gem 'letter_opener'
|
||||
gem 'mailcatcher'
|
||||
gem 'awesome_print'
|
||||
|
||||
gem "puma"
|
||||
@ -52,6 +52,8 @@ group :development do
|
||||
gem 'capistrano-maintenance', '0.0.5', require: false
|
||||
|
||||
gem 'active_record_query_trace'
|
||||
|
||||
gem 'coveralls', require: false
|
||||
end
|
||||
|
||||
group :test do
|
||||
@ -62,6 +64,7 @@ group :test do
|
||||
gem 'webmock'
|
||||
gem 'vcr'
|
||||
gem 'byebug'
|
||||
gem 'pdf-reader'
|
||||
end
|
||||
|
||||
group :production do
|
||||
|
75
Gemfile.lock
75
Gemfile.lock
@ -1,6 +1,7 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
Ascii85 (1.0.2)
|
||||
aasm (4.1.0)
|
||||
actionmailer (4.2.5)
|
||||
actionpack (= 4.2.5)
|
||||
@ -41,6 +42,7 @@ GEM
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.3.8)
|
||||
afm (0.2.2)
|
||||
ansi (1.5.0)
|
||||
api-pagination (4.3.0)
|
||||
apipie-rails (0.3.6)
|
||||
@ -94,13 +96,13 @@ GEM
|
||||
cldr-plurals-runtime-rb (1.0.1)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
coffee-rails (4.1.0)
|
||||
coffee-rails (4.1.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
coffee-script (2.3.0)
|
||||
railties (>= 4.0.0, < 5.1.x)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.9.1)
|
||||
coffee-script-source (1.10.0)
|
||||
compass (1.0.3)
|
||||
chunky_png (~> 1.2)
|
||||
compass-core (~> 1.0.2)
|
||||
@ -118,8 +120,15 @@ GEM
|
||||
sass-rails (<= 5.0.1)
|
||||
sprockets (< 2.13)
|
||||
connection_pool (2.2.0)
|
||||
coveralls (0.8.16)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov (~> 0.12.0)
|
||||
term-ansicolor (~> 1.3.0)
|
||||
thor (~> 0.19.1)
|
||||
tins (>= 1.6.0, < 2)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
daemons (1.2.4)
|
||||
database_cleaner (1.4.1)
|
||||
debug_inspector (0.0.2)
|
||||
descendants_tracker (0.0.4)
|
||||
@ -133,6 +142,7 @@ GEM
|
||||
warden (~> 1.2.3)
|
||||
devise-async (0.9.0)
|
||||
devise (~> 3.2)
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
elasticsearch (1.0.12)
|
||||
@ -157,7 +167,8 @@ GEM
|
||||
multi_json
|
||||
equalizer (0.0.11)
|
||||
erubis (2.7.0)
|
||||
execjs (2.4.0)
|
||||
eventmachine (1.0.9.1)
|
||||
execjs (2.7.0)
|
||||
faker (1.4.3)
|
||||
i18n (~> 0.5)
|
||||
faraday (0.9.1)
|
||||
@ -177,6 +188,7 @@ GEM
|
||||
has_secure_token (1.0.0)
|
||||
activerecord (>= 3.0)
|
||||
hashdiff (0.3.0)
|
||||
hashery (2.1.2)
|
||||
hashie (3.4.2)
|
||||
highline (1.7.1)
|
||||
hike (1.2.3)
|
||||
@ -207,23 +219,27 @@ GEM
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
kgio (2.9.3)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
letter_opener (1.3.0)
|
||||
launchy (~> 2.2)
|
||||
libv8 (3.16.14.11)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.6.3)
|
||||
mime-types (>= 1.16, < 3)
|
||||
mailcatcher (0.6.5)
|
||||
eventmachine (= 1.0.9.1)
|
||||
mail (~> 2.3)
|
||||
rack (~> 1.5)
|
||||
sinatra (~> 1.2)
|
||||
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)
|
||||
mini_magick (4.2.0)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (5.9.0)
|
||||
mini_portile2 (2.1.0)
|
||||
minitest (5.9.1)
|
||||
minitest-reporters (1.1.8)
|
||||
ansi
|
||||
builder
|
||||
@ -241,8 +257,9 @@ GEM
|
||||
net-ssh-gateway (1.2.0)
|
||||
net-ssh (>= 2.6.5)
|
||||
netrc (0.10.3)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
nokogiri (1.6.8)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
notify_with (0.0.2)
|
||||
jbuilder (~> 2.0)
|
||||
rails (>= 4.2.0)
|
||||
@ -264,7 +281,14 @@ GEM
|
||||
httparty (~> 0.13)
|
||||
orm_adapter (0.5.0)
|
||||
pdf-core (0.5.1)
|
||||
pdf-reader (1.4.0)
|
||||
Ascii85 (~> 1.0.0)
|
||||
afm (~> 0.2.1)
|
||||
hashery (~> 2.0)
|
||||
ruby-rc4
|
||||
ttfunk
|
||||
pg (0.18.1)
|
||||
pkg-config (1.1.7)
|
||||
prawn (2.0.1)
|
||||
pdf-core (~> 0.5.1)
|
||||
ttfunk (~> 1.4.0)
|
||||
@ -313,7 +337,7 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.13.0)
|
||||
rake (11.1.2)
|
||||
rake (11.3.0)
|
||||
rb-fsevent (0.9.4)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
@ -333,6 +357,7 @@ GEM
|
||||
netrc (~> 0.7)
|
||||
rolify (4.0.0)
|
||||
ruby-progressbar (1.7.5)
|
||||
ruby-rc4 (0.1.5)
|
||||
rubyzip (1.1.7)
|
||||
rufus-scheduler (3.0.9)
|
||||
tzinfo
|
||||
@ -363,10 +388,18 @@ GEM
|
||||
sidekiq (>= 2.17.3)
|
||||
tilt (< 2.0.0)
|
||||
simple_oauth (0.3.1)
|
||||
simplecov (0.12.0)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
sinatra (1.4.6)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
skinny (0.2.4)
|
||||
eventmachine (~> 1.0.0)
|
||||
thin (>= 1.5, < 1.7)
|
||||
spring (1.3.5)
|
||||
sprockets (2.12.4)
|
||||
hike (~> 1.2)
|
||||
@ -377,19 +410,27 @@ GEM
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sqlite3 (1.3.13)
|
||||
stripe (1.30.2)
|
||||
json (~> 1.8.1)
|
||||
rest-client (~> 1.4)
|
||||
term-ansicolor (1.3.2)
|
||||
tins (~> 1.0)
|
||||
test_after_commit (1.0.0)
|
||||
activerecord (>= 3.2)
|
||||
therubyracer (0.12.0)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thin (1.5.1)
|
||||
daemons (>= 1.0.9)
|
||||
eventmachine (>= 0.12.6)
|
||||
rack (>= 1.0.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (1.4.1)
|
||||
timers (4.0.1)
|
||||
hitimes
|
||||
tins (1.13.0)
|
||||
ttfunk (1.4.0)
|
||||
twitter (5.14.0)
|
||||
addressable (~> 2.3)
|
||||
@ -460,6 +501,7 @@ DEPENDENCIES
|
||||
chroma
|
||||
coffee-rails (~> 4.1.0)
|
||||
compass-rails (= 2.0.4)
|
||||
coveralls
|
||||
database_cleaner
|
||||
devise
|
||||
devise-async
|
||||
@ -477,7 +519,7 @@ DEPENDENCIES
|
||||
jbuilder_cache_multi
|
||||
jquery-rails
|
||||
kaminari
|
||||
letter_opener
|
||||
mailcatcher
|
||||
message_format
|
||||
mini_magick
|
||||
minitest-reporters
|
||||
@ -486,6 +528,7 @@ DEPENDENCIES
|
||||
omniauth
|
||||
omniauth-oauth2
|
||||
openlab_ruby
|
||||
pdf-reader
|
||||
pg
|
||||
prawn
|
||||
prawn-table
|
||||
@ -520,4 +563,4 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.12.5
|
||||
1.13.1
|
||||
|
53
LICENSE.md
53
LICENSE.md
@ -14,39 +14,42 @@ Copyright (C) 2015 La Casemate
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
FabManager uses some external components, which are licenced under the
|
||||
terms of [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0):
|
||||
Fab-Manager uses some external components, which are licenced under the
|
||||
terms of the following licences:
|
||||
|
||||
- [jasny-bootstrap](https://github.com/jasny/bootstrap/)
|
||||
- [elasticsearch](https://github.com/elasticsearch/bower-elasticsearch-js)
|
||||
- [nvd3](https://github.com/novus/nvd3)
|
||||
- [angular-bootstrap-switch](https://github.com/frapontillo/angular-bootstrap-switch)
|
||||
- [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails)
|
||||
- [elasticsearch-model](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model)
|
||||
- [elasticsearch-persistence](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence)
|
||||
- font [Open Sans](http://www.fontsquirrel.com/fonts/open-sans)
|
||||
- [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0):
|
||||
- [jasny-bootstrap](https://github.com/jasny/bootstrap/)
|
||||
- [elasticsearch](https://github.com/elasticsearch/bower-elasticsearch-js)
|
||||
- [nvd3](https://github.com/novus/nvd3)
|
||||
- [angular-bootstrap-switch](https://github.com/frapontillo/angular-bootstrap-switch)
|
||||
- [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails)
|
||||
- [elasticsearch-model](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model)
|
||||
- [elasticsearch-persistence](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence)
|
||||
- font [Open Sans](http://www.fontsquirrel.com/fonts/open-sans)
|
||||
|
||||
- [General Public License version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0-faq.en.html):
|
||||
- [railroady](https://github.com/preston/railroady)
|
||||
- [unicorn](https://github.com/defunkt/unicorn)
|
||||
- [prawn](https://github.com/prawnpdf/prawn)
|
||||
- [prawn-table](https://github.com/prawnpdf/prawn-table)
|
||||
|
||||
Some other used libraries/components are licenced under the terms of the
|
||||
[General Public License version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0-faq.en.html):
|
||||
- [BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause)
|
||||
- [ruby](https://www.ruby-lang.org)
|
||||
- [rubyzip](https://github.com/rubyzip/rubyzip)
|
||||
- [byebug](https://github.com/deivid-rodriguez/byebug)
|
||||
|
||||
- [ruby](https://www.ruby-lang.org)
|
||||
- [railroady](https://github.com/preston/railroady)
|
||||
- [unicorn](https://github.com/defunkt/unicorn)
|
||||
- [prawn](https://github.com/prawnpdf/prawn)
|
||||
- [prawn-table](https://github.com/prawnpdf/prawn-table)
|
||||
- [MIT Licence](https://opensource.org/licenses/MIT)
|
||||
- Errors and omissions excepted, all the other external libraries used
|
||||
in this project.
|
||||
|
||||
|
||||
Errors and omissions excepted, the other external libraries used in this
|
||||
project are licenced under the terms of the [MIT Licence](https://opensource.org/licenses/MIT).
|
||||
Please refer to the libraries documentation for more informations about
|
||||
their licences.
|
||||
Please refer to the libraries documentation for more information about
|
||||
their licences.
|
||||
|
||||
Complete lists of used libraries are available in `bower.json` for the
|
||||
EcmaScript libraries and in `Gemfile` for Ruby libraries.
|
||||
JS/EcmaScript libraries and in `Gemfile` for Ruby libraries.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
@ -666,4 +669,4 @@ an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
1
Procfile
1
Procfile
@ -1,2 +1,3 @@
|
||||
web: bundle exec rails server puma -p $PORT -b0.0.0.0
|
||||
worker: bundle exec sidekiq -C ./config/sidekiq.yml
|
||||
mail: bundle exec mailcatcher --foreground
|
||||
|
102
README.md
102
README.md
@ -3,7 +3,7 @@
|
||||
FabManager is the FabLab management solution. It is web-based, open-source and totally free.
|
||||
|
||||
|
||||
##### Table of Contents
|
||||
##### Table of Contents
|
||||
1. [Software stack](#software-stack)
|
||||
2. [Contributing](#contributing)
|
||||
3. [Setup a production environment](#setup-a-production-environment)
|
||||
@ -122,9 +122,12 @@ In you only intend to run fab-manager on your local machine for testing purposes
|
||||
```
|
||||
|
||||
8. Build the database. You may have to follow the steps described in [the PostgreSQL configuration chapter](#setup-fabmanager-in-postgresql) before, if you don't already had done it.
|
||||
**Warning**: **NO NOT** run `rake db:setup` instead of these commands, as this will not run some required raw SQL instructions.
|
||||
|
||||
```bash
|
||||
rake db:setup
|
||||
rake db:create
|
||||
rake db:migrate
|
||||
rake db:seed
|
||||
```
|
||||
|
||||
9. Create the pids folder used by Sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml`
|
||||
@ -145,6 +148,9 @@ In you only intend to run fab-manager on your local machine for testing purposes
|
||||
- user: admin@fab-manager.com
|
||||
- password: adminadmin
|
||||
|
||||
13. 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="environment-configuration"></a>
|
||||
### Environment Configuration
|
||||
|
||||
@ -159,7 +165,7 @@ This value is only used when deploying with Docker, otherwise this is configured
|
||||
POSTGRES_PASSWORD
|
||||
|
||||
Password for the PostgreSQL user, as specified in `database.yml`.
|
||||
Please see [Setup the FabManager database in PostgreSQL](#setup-fabmanager-in-postgresql) for informations on how to create a user and set his password.
|
||||
Please see [Setup the FabManager database in PostgreSQL](#setup-fabmanager-in-postgresql) for information on how to create a user and set his password.
|
||||
This value is only used when deploying with Docker, otherwise this is configured in `config/database.yml`.
|
||||
|
||||
REDIS_HOST
|
||||
@ -198,6 +204,12 @@ The PDF file name will be of the form "(INVOICE_PREFIX) - (invoice ID) _ (invoic
|
||||
FABLAB_WITHOUT_PLANS
|
||||
|
||||
If set to 'true', the subscription plans will be fully disabled and invisible in the application.
|
||||
It is not recommended to disable plans if at least one subscription was took on the platform.
|
||||
|
||||
FABLAB_WITHOUT_SPACES
|
||||
|
||||
If set to 'false', enable the spaces management and reservation in the application.
|
||||
It is not recommended to disable spaces if at least one space reservation was made on the system.
|
||||
|
||||
DEFAULT_MAIL_FROM
|
||||
|
||||
@ -222,11 +234,12 @@ Identifier of your Google Analytics account.
|
||||
|
||||
Unique identifier of your [Disqus](http://www.disqus.com) forum.
|
||||
Disqus forums are used to allow visitors to comment on projects.
|
||||
See https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- for more informations.
|
||||
See https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- for more information.
|
||||
|
||||
TWITTER_NAME
|
||||
|
||||
Identifier of the Twitter account, from witch the last tweet will be fetched and displayed on the home page.
|
||||
Identifier of the Twitter account, from witch the last tweet will be fetched and displayed on the home page.
|
||||
This value can be graphically overridden during the application's lifecycle in Admin/Customization/Home page/Twitter Feed.
|
||||
It will also be used for [Twitter Card analytics](https://dev.twitter.com/cards/analytics).
|
||||
|
||||
TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN & TWITTER_ACCESS_TOKEN_SECRET
|
||||
@ -239,6 +252,35 @@ Retrieve them from https://apps.twitter.com
|
||||
This is optional. You can follow [this guide to get your personal App ID](https://developers.facebook.com/docs/apps/register).
|
||||
If you do so, you'll be able to customize and get statistics about project shares on Facebook.
|
||||
|
||||
LOG_LEVEL
|
||||
|
||||
This parameter configures the logs verbosity.
|
||||
Available log levels can be found [here](http://guides.rubyonrails.org/debugging_rails_applications.html#log-levels).
|
||||
|
||||
ALLOWED_EXTENSIONS
|
||||
|
||||
Exhaustive list of file's extensions available for public upload as project's CAO attachements.
|
||||
Each item in the list must be separated from the others by a space char.
|
||||
You will probably want to check that this list match the `ALLOWED_MIME_TYPES` values below.
|
||||
Please consider that allowing file archives (eg. ZIP) or binary executable (eg. EXE) may result in a **dangerous** security issue and must be avoided in any cases.
|
||||
|
||||
ALLOWED_MIME_TYPES
|
||||
|
||||
Exhaustive list of file's mime-types available for public upload as project's CAO attachements.
|
||||
Each item in the list must be separated from the others by a space char.
|
||||
You will probably want to check that this list match the `ALLOWED_EXTENSIONS` values above.
|
||||
Please consider that allowing file archives (eg. application/zip) or binary executable (eg. application/exe) may result in a **dangerous** security issue and must be avoided in any cases.
|
||||
|
||||
MAX_IMAGE_SIZE
|
||||
|
||||
Maximum size (in bytes) allowed for image uploaded on the platform.
|
||||
This parameter concerns events, plans, user's avatars, projects and steps of projects.
|
||||
If this parameter is not specified the maximum size allowed will be 2MB.
|
||||
|
||||
Settings related to Open Projects
|
||||
|
||||
See the [Open Projects](#open-projects) section for a detailed description of these parameters.
|
||||
|
||||
Settings related to i18n
|
||||
|
||||
See the [Settings](#i18n-settings) section of the [Internationalization (i18n)](#i18n) paragraph for a detailed description of these parameters.
|
||||
@ -295,7 +337,7 @@ Otherwise, please follow the official instructions on the project's website.
|
||||
<a name="setup-fabmanager-in-postgresql"></a>
|
||||
### Setup the FabManager database in PostgreSQL
|
||||
|
||||
Before running `rake db:setup`, you have to make sure that the user configured in [config/database.yml](config/database.yml.default) for the `development` environment exists.
|
||||
Before running `rake db:create`, you have to make sure that the user configured in [config/database.yml](config/database.yml.default) for the `development` environment exists.
|
||||
To create it, please follow these instructions:
|
||||
|
||||
1. Run the PostgreSQL administration command line interface, logged as the postgres user
|
||||
@ -325,20 +367,14 @@ To create it, please follow these instructions:
|
||||
ALTER ROLE sleede WITH CREATEDB;
|
||||
```
|
||||
|
||||
4. Then, create the fabmanager_development and fabmanager_test databases
|
||||
|
||||
```sql
|
||||
CREATE DATABASE fabmanager_development OWNER sleede;
|
||||
CREATE DATABASE fabmanager_test OWNER sleede;
|
||||
```
|
||||
|
||||
5. To finish, attribute a password to this user
|
||||
4. Then, attribute a password to this user
|
||||
|
||||
```sql
|
||||
ALTER USER sleede WITH ENCRYPTED PASSWORD 'sleede';
|
||||
```
|
||||
6. Finally, have a look at the [PostgreSQL Limitations](#postgresql-limitations) section or some errors will occurs preventing you from finishing the installation procedure.
|
||||
|
||||
|
||||
5. Finally, have a look at the [PostgreSQL Limitations](#postgresql-limitations) section or some errors will occurs preventing you from finishing the installation procedure.
|
||||
|
||||
<a name="postgresql-limitations"></a>
|
||||
### PostgreSQL Limitations
|
||||
|
||||
@ -347,14 +383,14 @@ To create it, please follow these instructions:
|
||||
So here's your choices, mainly depending on your security requirements:
|
||||
- Use the default PostgreSQL super-user (postgres) as the database user of fab-manager.
|
||||
- Set your user as _SUPERUSER_; run the following command in `psql` (after replacing `sleede` with you user name):
|
||||
|
||||
|
||||
```sql
|
||||
ALTER USER sleede WITH SUPERUSER;
|
||||
```
|
||||
|
||||
- Install and configure the PostgreSQL extension [pgextwlist](https://github.com/dimitri/pgextwlist).
|
||||
|
||||
- Install and configure the PostgreSQL extension [pgextwlist](https://github.com/dimitri/pgextwlist).
|
||||
Please follow the instructions detailed on the extension website to whitelist `unaccent` and `trigram` for the user configured in `config/database.yml`.
|
||||
- Some users may want to use another DBMS than PostgreSQL.
|
||||
- Some users may want to use another DBMS than PostgreSQL.
|
||||
This is currently not supported, because of some PostgreSQL specific instructions that cannot be efficiently handled with the ActiveRecord ORM:
|
||||
- `app/controllers/api/members_controllers.rb@list` is using `ILIKE`
|
||||
- `app/controllers/api/invoices_controllers.rb@list` is using `ILIKE` and `date_trunc()`
|
||||
@ -363,8 +399,8 @@ To create it, please follow these instructions:
|
||||
- `db/migrate/20150604131525_add_meta_data_to_notifications.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype.
|
||||
- `db/migrate/20160915105234_add_transformation_to_o_auth2_mapping.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype.
|
||||
- If you intend to contribute to the project code, you will need to run the test suite with `rake test`.
|
||||
This also requires your user to have the _SUPERUSER_ role.
|
||||
Please see the [known issues](#known-issues) section for more informations about this.
|
||||
This also requires your user to have the _SUPERUSER_ role.
|
||||
Please see the [known issues](#known-issues) section for more information about this.
|
||||
|
||||
<a name="elasticsearch"></a>
|
||||
## ElasticSearch
|
||||
@ -495,7 +531,7 @@ Back-end translations uses the [Ruby on Rails syntax](http://guides.rubyonrails.
|
||||
|
||||
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 informations about the sentence to translate.
|
||||
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.
|
||||
|
||||
|
||||
<a name="i18n-configuration"></a>
|
||||
@ -629,7 +665,7 @@ Fab-manager can be connected to a [Single Sign-On](https://en.wikipedia.org/wiki
|
||||
Currently OAuth 2 is the only supported protocol for SSO authentication.
|
||||
|
||||
For an example of how to use configure a SSO in Fab-manager, please read [sso_with_github.md](doc/sso_with_github.md).
|
||||
Developers may find informations on how to implement their own authentication protocol in [sso_authentication.md](doc/sso_authentication.md).
|
||||
Developers may find information on how to implement their own authentication protocol in [sso_authentication.md](doc/sso_authentication.md).
|
||||
|
||||
<a name="known-issues"></a>
|
||||
## Known issues
|
||||
@ -648,17 +684,17 @@ Developers may find informations on how to implement their own authentication pr
|
||||
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 ble to create plans longer than one year.
|
||||
- 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 "groups".
|
||||
: ...
|
||||
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'
|
||||
...
|
||||
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 "groups".
|
||||
: ...
|
||||
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.
|
||||
@ -669,10 +705,10 @@ Developers may find informations on how to implement their own authentication pr
|
||||
|
||||
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.
|
||||
- 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
|
||||
```
|
||||
|
@ -71,7 +71,7 @@ config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfig
|
||||
// Angular-xeditable (click-to-edit elements, used in admin backoffice)
|
||||
editableOptions.theme = 'bs3';
|
||||
|
||||
// Alter the UI-Router's $state, registering into some informations concerning the previous $state.
|
||||
// Alter the UI-Router's $state, registering into some information concerning the previous $state.
|
||||
// This is used to allow the user to navigate to the previous state
|
||||
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
|
||||
$state.prevState = fromState;
|
||||
@ -80,6 +80,8 @@ config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfig
|
||||
|
||||
// Global config: if true, the whole 'Plans & Subscriptions' feature will be disabled in the application
|
||||
$rootScope.fablabWithoutPlans = Fablab.withoutPlans;
|
||||
// Global config: it true, the whole 'Spaces' features will be disabled in the application
|
||||
$rootScope.fablabWithoutSpaces = Fablab.withoutSpaces;
|
||||
|
||||
// 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
|
||||
|
@ -4,8 +4,8 @@
|
||||
# Controller used in the calendar management page
|
||||
##
|
||||
|
||||
Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $state, $uibModal, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig) ->
|
||||
Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig) ->
|
||||
|
||||
|
||||
|
||||
@ -50,6 +50,8 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
calendarEventClickCb(event, jsEvent, view)
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element)
|
||||
loading: (isLoading, view ) ->
|
||||
loadingCb(isLoading, view)
|
||||
|
||||
|
||||
|
||||
@ -62,8 +64,8 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t("do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION"
|
||||
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')
|
||||
, ->
|
||||
@ -76,9 +78,9 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
resa.canceled_at = data.canceled_at
|
||||
break
|
||||
# notify the admin
|
||||
growl.success(_t('reservation_was_successfully_cancelled'))
|
||||
growl.success(_t('admin_calendar.reservation_was_successfully_cancelled'))
|
||||
, (data, status) -> # failed
|
||||
growl.error(_t('reservation_cancellation_failed'))
|
||||
growl.error(_t('admin_calendar.reservation_cancellation_failed'))
|
||||
|
||||
|
||||
|
||||
@ -89,16 +91,16 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
##
|
||||
$scope.removeMachine = (machine) ->
|
||||
if $scope.availability.machine_ids.length == 1
|
||||
growl.error(_t('unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'))
|
||||
growl.error(_t('admin_calendar.unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'))
|
||||
else
|
||||
# open a confirmation dialog
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_remove_MACHINE_from_this_slot', {GENDER:getGender($scope.currentUser), MACHINE:machine.name}, "messageformat") + ' ' +
|
||||
_t('this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' +
|
||||
_t('beware_this_cannot_be_reverted')
|
||||
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')
|
||||
, ->
|
||||
# the admin has confirmed, remove the machine
|
||||
machines = $scope.availability.machine_ids
|
||||
@ -113,9 +115,20 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
$scope.availability.title = data.title
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
# notify the admin
|
||||
growl.success(_t('the_machine_was_successfully_removed_from_the_slot'))
|
||||
growl.success(_t('admin_calendar.the_machine_was_successfully_removed_from_the_slot'))
|
||||
, (data, status) -> # failed
|
||||
growl.error(_t('deletion_failed'))
|
||||
growl.error(_t('admin_calendar.deletion_failed'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to alert the admin that the export request was acknowledged and is
|
||||
# processing right now.
|
||||
##
|
||||
$scope.alertExport = (type) ->
|
||||
Export.status({category: 'availabilities', type: type}).then (res) ->
|
||||
unless (res.data.exists)
|
||||
growl.success _t('admin_calendar.export_is_running_you_ll_be_notified_when_its_ready')
|
||||
|
||||
|
||||
|
||||
@ -148,6 +161,15 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
resolve:
|
||||
start: -> start
|
||||
end: -> end
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
Machine.query().$promise
|
||||
]
|
||||
trainingsPromise: ['Training', (Training)->
|
||||
Training.query().$promise
|
||||
]
|
||||
spacesPromise: ['Space', (Space)->
|
||||
Space.query().$promise
|
||||
]
|
||||
# when the modal is closed, we send the slot to the server for saving
|
||||
modalInstance.result.then (availability) ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'renderEvent',
|
||||
@ -181,13 +203,10 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
if ($(jsEvent.target).hasClass('remove-event'))
|
||||
Availability.delete id: event.id, ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents', event.id
|
||||
for _event, i in $scope.eventSources[0].events
|
||||
if _event.id == event.id
|
||||
$scope.eventSources[0].events.splice(i,1)
|
||||
|
||||
growl.success(_t('the_slot_START-END_has_been_successfully_deleted', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
|
||||
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')}))
|
||||
,->
|
||||
growl.error(_t('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')}))
|
||||
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')}))
|
||||
# if the user has only clicked on the event, display its reservations
|
||||
else
|
||||
Availability.reservations {id: event.id}, (reservations) ->
|
||||
@ -207,8 +226,20 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
for tag in event.tags
|
||||
html += "<span class='label label-success text-white'>#{tag.name}</span> "
|
||||
element.find('.fc-title').append("<br/>"+html)
|
||||
# force return to prevent coffee-script auto-return to return random value (possiblity falsy)
|
||||
return
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Triggered when resource fetching starts/stops.
|
||||
# @see https://fullcalendar.io/docs/resource_data/loading/
|
||||
##
|
||||
loadingCb = (isLoading, view) ->
|
||||
if (isLoading)
|
||||
# we remove existing events when fetching starts to prevent duplicates
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents')
|
||||
|
||||
]
|
||||
|
||||
|
||||
@ -216,7 +247,8 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
##
|
||||
# Controller used in the slot creation modal window
|
||||
##
|
||||
Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "Machine", "Availability", "Training", 'Tag', 'growl', '_t', ($scope, $uibModalInstance, moment, start, end, Machine, Availability, Training, Tag, growl, _t) ->
|
||||
Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "machinesPromise", "Availability", "trainingsPromise", "spacesPromise", 'Tag', 'growl', '_t'
|
||||
, ($scope, $uibModalInstance, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, Tag, growl, _t) ->
|
||||
|
||||
## $uibModal parameter
|
||||
$scope.start = start
|
||||
@ -225,14 +257,26 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
$scope.end = end
|
||||
|
||||
## machines list
|
||||
$scope.machines = []
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## trainings list
|
||||
$scope.trainings = []
|
||||
$scope.trainings = trainingsPromise
|
||||
|
||||
## spaces list
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
## machines associated with the created slot
|
||||
$scope.selectedMachines = []
|
||||
|
||||
## training associated with the created slot
|
||||
$scope.selectedTraining = null
|
||||
|
||||
## space associated with the created slot
|
||||
$scope.selectedSpace = null
|
||||
|
||||
## UI step
|
||||
$scope.step = 1
|
||||
|
||||
## the user is not able to edit the ending time of the availability, unless he set the type to 'training'
|
||||
$scope.endDateReadOnly = true
|
||||
|
||||
@ -270,14 +314,16 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
# Callback for the modal window validation: save the slot and closes the modal
|
||||
##
|
||||
$scope.ok = ->
|
||||
if $scope.availability.available_type == "machines"
|
||||
if $scope.availability.available_type == 'machines'
|
||||
if $scope.selectedMachines.length > 0
|
||||
$scope.availability.machine_ids = $scope.selectedMachines.map (m) -> m.id
|
||||
else
|
||||
growl.error(_t('you_should_link_a_training_or_a_machine_to_this_slot'))
|
||||
growl.error(_t('admin_calendar.you_should_select_at_least_a_machine'))
|
||||
return
|
||||
else
|
||||
else if $scope.availability.available_type == 'training'
|
||||
$scope.availability.training_ids = [$scope.selectedTraining.id]
|
||||
else if $scope.availability.available_type == 'space'
|
||||
$scope.availability.space_ids = [$scope.selectedSpace.id]
|
||||
Availability.save
|
||||
availability: $scope.availability
|
||||
, (availability) ->
|
||||
@ -285,6 +331,23 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Move the modal UI to the next step
|
||||
##
|
||||
$scope.next = ->
|
||||
$scope.setNbTotalPlaces() if $scope.step == 1
|
||||
$scope.step++
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Move the modal UI to the next step
|
||||
##
|
||||
$scope.previous = ->
|
||||
$scope.step--
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to cancel the slot creation
|
||||
##
|
||||
@ -293,22 +356,14 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switches the slot type : machine availability or training availability
|
||||
##
|
||||
$scope.changeAvailableType = ->
|
||||
if $scope.availability.available_type == "machines"
|
||||
$scope.availability.available_type = "training"
|
||||
else
|
||||
$scope.availability.available_type = "machines"
|
||||
|
||||
|
||||
|
||||
##
|
||||
# For training avaiabilities, set the maximum number of people allowed to register on this slot
|
||||
##
|
||||
$scope.setNbTotalPlaces = ->
|
||||
$scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places
|
||||
if $scope.availability.available_type == 'training'
|
||||
$scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places
|
||||
else if $scope.availability.available_type == 'space'
|
||||
$scope.availability.nb_total_places = $scope.selectedSpace.default_places
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
@ -317,18 +372,11 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
Machine.query().$promise.then (data)->
|
||||
$scope.machines = data.map (d) ->
|
||||
id: d.id
|
||||
name: d.name
|
||||
Training.query().$promise.then (data)->
|
||||
$scope.trainings = data.map (d) ->
|
||||
id: d.id
|
||||
name: d.name
|
||||
nb_total_places: d.nb_total_places
|
||||
if $scope.trainings.length > 0
|
||||
$scope.selectedTraining = $scope.trainings[0]
|
||||
$scope.setNbTotalPlaces()
|
||||
if $scope.trainings.length > 0
|
||||
$scope.selectedTraining = $scope.trainings[0]
|
||||
if $scope.spaces.length > 0
|
||||
$scope.selectedSpace = $scope.spaces[0]
|
||||
|
||||
Tag.query().$promise.then (data) ->
|
||||
$scope.tags = data
|
||||
|
||||
@ -336,7 +384,7 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
## time must be dividable by 60 minutes (base slot duration). For training availabilities, the user
|
||||
## can configure any duration as it does not matters.
|
||||
$scope.$watch 'availability.available_type', (newValue, oldValue, scope) ->
|
||||
if newValue == 'machines'
|
||||
if newValue == 'machines' or newValue == 'space'
|
||||
$scope.endDateReadOnly = true
|
||||
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()
|
||||
@ -347,8 +395,8 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
|
||||
## When the start date is changed, if we are configuring a machine availability,
|
||||
## maintain the relative length of the slot (ie. change the end time accordingly)
|
||||
$scope.$watch 'start', (newValue, oldValue, scope) ->
|
||||
# for machine availabilities, adjust the end time
|
||||
if $scope.availability.available_type == 'machines'
|
||||
# for machine or space availabilities, adjust the end time
|
||||
if $scope.availability.available_type == 'machines' or $scope.availability.available_type == 'space'
|
||||
end = moment($scope.end)
|
||||
end.add(moment(newValue).diff(oldValue), 'milliseconds')
|
||||
$scope.end = end.toDate()
|
||||
|
@ -8,12 +8,13 @@ userValidities = ['once', 'forever']
|
||||
##
|
||||
# Controller used in the coupon creation page
|
||||
##
|
||||
Application.Controllers.controller "NewCouponController", ["$scope", "$state",'Coupon', 'growl', '_t'
|
||||
Application.Controllers.controller "NewCouponController", ["$scope", "$state", 'Coupon', 'growl', '_t'
|
||||
, ($scope, $state, Coupon, growl, _t) ->
|
||||
|
||||
## Values for the coupon currently created
|
||||
$scope.coupon =
|
||||
active: true
|
||||
type: 'percent_off'
|
||||
|
||||
## Options for the validity per user
|
||||
$scope.validities = userValidities
|
||||
@ -57,8 +58,8 @@ Application.Controllers.controller "NewCouponController", ["$scope", "$state",'C
|
||||
##
|
||||
# Controller used in the coupon edition page
|
||||
##
|
||||
Application.Controllers.controller "EditCouponController", ["$scope", "$state", 'Coupon', 'couponPromise', '_t'
|
||||
, ($scope, $state, Coupon, couponPromise, _t) ->
|
||||
Application.Controllers.controller "EditCouponController", ["$scope", "$state", 'Coupon', 'couponPromise', '_t', 'growl'
|
||||
, ($scope, $state, Coupon, couponPromise, _t, growl) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
@ -72,6 +73,9 @@ Application.Controllers.controller "EditCouponController", ["$scope", "$state",
|
||||
## Options for the validity per user
|
||||
$scope.validities = userValidities
|
||||
|
||||
## Mapping for validation errors
|
||||
$scope.errors = {}
|
||||
|
||||
## Default parameters for AngularUI-Bootstrap datepicker (used for coupon validity limit selection)
|
||||
$scope.datePicker =
|
||||
format: Fablab.uibDateFormat
|
||||
@ -97,11 +101,12 @@ Application.Controllers.controller "EditCouponController", ["$scope", "$state",
|
||||
# Callback to save the coupon's changes to the API
|
||||
##
|
||||
$scope.updateCoupon = ->
|
||||
$scope.errors = {}
|
||||
Coupon.update {id: $scope.coupon.id}, coupon: $scope.coupon, (coupon) ->
|
||||
$state.go('app.admin.pricing')
|
||||
, (err)->
|
||||
growl.error(_t('unable_to_update_the_coupon_an_error_occurred'))
|
||||
console.error(err)
|
||||
$scope.errors = err.data
|
||||
|
||||
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
# - $scope.toggleStartDatePicker($event)
|
||||
# - $scope.toggleEndDatePicker($event)
|
||||
# - $scope.toggleRecurrenceEnd(e)
|
||||
# - $scope.addPrice()
|
||||
# - $scope.removePrice(price, $event)
|
||||
#
|
||||
# Requires :
|
||||
# - $scope.event.event_files_attributes = []
|
||||
@ -137,6 +139,21 @@ class EventsController
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Remove the price or mark it as 'to delete'
|
||||
##
|
||||
$scope.removePrice = (price, event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if price.id
|
||||
price._destroy = true
|
||||
else
|
||||
index = $scope.event.prices.indexOf(price)
|
||||
$scope.event.prices.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the events listing page (admin view)
|
||||
##
|
||||
@ -382,8 +399,8 @@ Application.Controllers.controller "ShowEventReservationsController", ["$scope",
|
||||
##
|
||||
# Controller used in the event creation page
|
||||
##
|
||||
Application.Controllers.controller "NewEventController", ["$scope", "$state", "$locale", 'CSRF', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t'
|
||||
, ($scope, $state, $locale, CSRF, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t) ->
|
||||
Application.Controllers.controller "NewEventController", ["$scope", "$state", 'CSRF', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t'
|
||||
, ($scope, $state, CSRF, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
@ -425,10 +442,6 @@ Application.Controllers.controller "NewEventController", ["$scope", "$state", "$
|
||||
{label: _t('every_year'), value: 'year'}
|
||||
]
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
|
||||
|
||||
## Using the EventsController
|
||||
new EventsController($scope, $state)
|
||||
]
|
||||
@ -438,8 +451,8 @@ Application.Controllers.controller "NewEventController", ["$scope", "$state", "$
|
||||
##
|
||||
# Controller used in the events edition page
|
||||
##
|
||||
Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", "$locale", 'CSRF', 'eventPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise'
|
||||
, ($scope, $state, $stateParams, $locale, CSRF, eventPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise) ->
|
||||
Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", 'CSRF', 'eventPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise'
|
||||
, ($scope, $state, $stateParams, CSRF, eventPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
@ -454,9 +467,6 @@ 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
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
|
||||
## List of categories for the events
|
||||
$scope.categories = categoriesPromise
|
||||
|
||||
|
@ -217,7 +217,7 @@ Application.Controllers.controller "GraphsController", ["$scope", "$state", "$ro
|
||||
for it_st in [0.. cur_type.subtypes.length-1] by 1 # when we've found it, iterate over its subtypes ...
|
||||
cur_subtype = cur_type.subtypes[it_st]
|
||||
if subgroup.key == cur_subtype.key # ... which match $SUBTYPE
|
||||
# then we construct NVD3 dataSource according to these informations
|
||||
# then we construct NVD3 dataSource according to these information
|
||||
dataSource =
|
||||
values: []
|
||||
key: cur_subtype.label
|
||||
@ -362,6 +362,10 @@ Application.Controllers.controller "GraphsController", ["$scope", "$state", "$ro
|
||||
"index": "stats"
|
||||
"type": esType
|
||||
"searchType": "count"
|
||||
"stat-type": statType
|
||||
"custom-query": ''
|
||||
"start-date": moment($scope.datePickerStart.selected).format()
|
||||
"end-date": moment($scope.datePickerEnd.selected).format()
|
||||
"body": buildElasticAggregationsQuery(statType, $scope.display.interval, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected))
|
||||
, (error, response) ->
|
||||
if (error)
|
||||
|
@ -125,7 +125,7 @@ Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'I
|
||||
sample = sample.replace(/y+(?![^\[]*])/g, (match, offset, string) ->
|
||||
padWithZeros(8, match.length)
|
||||
)
|
||||
# date informations
|
||||
# date information
|
||||
sample = sample.replace(/[YMD]+(?![^\[]*])/g, (match, offset, string) ->
|
||||
$scope.today.format(match)
|
||||
)
|
||||
@ -133,9 +133,8 @@ Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'I
|
||||
sample = sample.replace(/X\[([^\]]+)\]/g, (match, p1, offset, string) ->
|
||||
p1
|
||||
)
|
||||
# # information about wallet (W[text]) - does not apply here
|
||||
# sample = sample.replace(/W\[([^\]]+)\]/g, "")
|
||||
|
||||
# information about wallet (W[text]) - does not apply here
|
||||
sample = sample.replace(/W\[([^\]]+)\]/g, "")
|
||||
# information about refunds (R[text]) - does not apply here
|
||||
sample = sample.replace(/R\[([^\]]+)\]/g, "")
|
||||
sample
|
||||
@ -164,7 +163,7 @@ Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'I
|
||||
sample = sample.replace(/d+(?![^\[]*])/g, (match, offset, string) ->
|
||||
padWithZeros(2, match.length)
|
||||
)
|
||||
# date informations
|
||||
# date information
|
||||
sample = sample.replace(/[YMD]+(?![^\[]*])/g, (match, offset, string) ->
|
||||
$scope.today.format(match)
|
||||
)
|
||||
@ -335,7 +334,7 @@ Application.Controllers.controller "InvoicesController", ["$scope", "$state", 'I
|
||||
|
||||
|
||||
##
|
||||
# Callback to save the value of the legal informations zone when editing is done
|
||||
# Callback to save the value of the legal information zone when editing is done
|
||||
##
|
||||
$scope.legalsEditEnd = (event) ->
|
||||
parsed = parseHtml($scope.invoice.legals.content)
|
||||
|
@ -105,8 +105,8 @@ class MembersController
|
||||
##
|
||||
# Controller used in the members/groups management page
|
||||
##
|
||||
Application.Controllers.controller "AdminMembersController", ["$scope", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export'
|
||||
, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member, Export) ->
|
||||
Application.Controllers.controller "AdminMembersController", ["$scope","$sce", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export'
|
||||
, ($scope, $sce, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member, Export) ->
|
||||
|
||||
|
||||
|
||||
@ -177,7 +177,7 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_administrator_this_cannot_be_undone')
|
||||
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'))
|
||||
, -> # cancel confirmed
|
||||
Admin.delete id: admin.id, ->
|
||||
admins.splice(findAdminIdxById(admins, admin.id), 1)
|
||||
@ -421,16 +421,42 @@ Application.Controllers.controller "EditMemberController", ["$scope", "$state",
|
||||
modalInstance = $uibModal.open
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "wallet/credit_modal.html" %>'
|
||||
controller: ['$scope', '$uibModalInstance', 'Wallet', '$locale', ($scope, $uibModalInstance, Wallet, $locale) ->
|
||||
controller: ['$scope', '$uibModalInstance', 'Wallet', ($scope, $uibModalInstance, Wallet) ->
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
# default: do not generate a refund invoice
|
||||
$scope.generate_avoir = false
|
||||
|
||||
# date of the generated refund invoice
|
||||
$scope.avoir_date = null
|
||||
|
||||
# optional description shown on the refund invoice
|
||||
$scope.description = ''
|
||||
|
||||
# default configuration for the avoir date selector widget
|
||||
$scope.datePicker =
|
||||
format: Fablab.uibDateFormat
|
||||
opened: false
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
##
|
||||
# Callback to open/close the date picker
|
||||
##
|
||||
$scope.toggleDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.opened = !$scope.datePicker.opened
|
||||
|
||||
##
|
||||
# Modal dialog validation callback
|
||||
##
|
||||
$scope.ok = ->
|
||||
Wallet.credit { id: wallet.id }, { amount: $scope.amount }, (_wallet)->
|
||||
Wallet.credit { id: wallet.id },
|
||||
amount: $scope.amount
|
||||
avoir: $scope.generate_avoir
|
||||
avoir_date: $scope.avoir_date
|
||||
avoir_description: $scope.description
|
||||
, (_wallet)->
|
||||
|
||||
growl.success(_t('wallet_credit_successfully'))
|
||||
$uibModalInstance.close(_wallet)
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
class PlanController
|
||||
|
||||
constructor: ($scope, groups, plans, machines, prices, partners, CSRF) ->
|
||||
constructor: ($scope, groups, prices, partners, CSRF) ->
|
||||
# protection against request forgery
|
||||
CSRF.setMetaTags()
|
||||
|
||||
@ -15,12 +15,6 @@ class PlanController
|
||||
## groups list
|
||||
$scope.groups = groups
|
||||
|
||||
## plans list
|
||||
$scope.plans = plans
|
||||
|
||||
## machines list
|
||||
$scope.machines = machines
|
||||
|
||||
## users with role 'partner', notifiables for a partner plan
|
||||
$scope.partners = partners.users
|
||||
|
||||
@ -48,38 +42,11 @@ class PlanController
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve a plan from its numeric identifier
|
||||
# @param id {number} plan ID
|
||||
# @returns {Object} Plan, inherits from $resource
|
||||
##
|
||||
$scope.getPlanFromId = (id) ->
|
||||
for plan in $scope.plans
|
||||
if plan.id == id
|
||||
return plan
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve the name of a machine from its ID
|
||||
# @param machine_id {number} machine identifier
|
||||
# @returns {string} Machine's name
|
||||
##
|
||||
$scope.getMachineName = (machine_id) ->
|
||||
for machine in $scope.machines
|
||||
if machine.id == machine_id
|
||||
return machine.name
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the plan creation form
|
||||
##
|
||||
Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal', 'groups', 'plans', 'machines', 'prices', 'partners', 'CSRF', '$state', 'growl', '_t', '$locale'
|
||||
, ($scope, $uibModal, groups, plans, machines, prices, partners, CSRF, $state, growl, _t, $locale) ->
|
||||
Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal', 'groups', 'prices', 'partners', 'CSRF', '$state', 'growl', '_t'
|
||||
, ($scope, $uibModal, groups, prices, partners, CSRF, $state, growl, _t) ->
|
||||
|
||||
|
||||
|
||||
@ -119,10 +86,6 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
$scope.method = 'POST'
|
||||
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks if the partner contact is a valid data. Used in the form validation process
|
||||
@ -150,7 +113,7 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
$scope.partner.name = "#{user.first_name} #{user.last_name}"
|
||||
$uibModalInstance.close($scope.partner)
|
||||
, (error)->
|
||||
growl.error(_t('unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name'))
|
||||
growl.error(_t('new_plan.unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name'))
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
@ -168,9 +131,9 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
##
|
||||
$scope.afterSubmit = (content) ->
|
||||
if !content.id? and !content.plan_ids?
|
||||
growl.error(_t('unable_to_create_the_subscription_please_try_again'))
|
||||
growl.error(_t('new_plan.unable_to_create_the_subscription_please_try_again'))
|
||||
else
|
||||
growl.success(_t('successfully_created_subscription(s)_dont_forget_to_redefine_prices'))
|
||||
growl.success(_t('new_plan.successfully_created_subscription(s)_dont_forget_to_redefine_prices'))
|
||||
if content.plan_ids?
|
||||
$state.go('app.admin.pricing')
|
||||
else
|
||||
@ -179,7 +142,7 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
|
||||
|
||||
|
||||
new PlanController($scope, groups, plans, machines, prices, partners, CSRF)
|
||||
new PlanController($scope, groups, prices, partners, CSRF)
|
||||
]
|
||||
|
||||
|
||||
@ -187,13 +150,25 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
##
|
||||
# Controller used in the plan edition form
|
||||
##
|
||||
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', '$locale', 'Plan'
|
||||
, ($scope, groups, plans, planPromise, machines, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, $locale, Plan) ->
|
||||
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'spaces', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', 'Plan'
|
||||
, ($scope, groups, plans, planPromise, machines, spaces, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, Plan) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## List of spaces
|
||||
$scope.spaces = spaces
|
||||
|
||||
## List of plans
|
||||
$scope.plans = plans
|
||||
|
||||
## List of machines
|
||||
$scope.machines = machines
|
||||
|
||||
## List of groups
|
||||
$scope.groups = groups
|
||||
|
||||
## current form is used for edition mode
|
||||
$scope.mode = 'edition'
|
||||
|
||||
@ -208,10 +183,6 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
$scope.method = 'PATCH'
|
||||
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
|
||||
|
||||
|
||||
##
|
||||
# If a parent plan was set ($scope.plan.parent), the prices will be copied from this parent plan into
|
||||
@ -239,9 +210,9 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
##
|
||||
$scope.afterSubmit = (content) ->
|
||||
if !content.id? and !content.plan_ids?
|
||||
growl.error(_t('unable_to_save_subscription_changes_please_try_again'))
|
||||
growl.error(_t('edit_plan.unable_to_save_subscription_changes_please_try_again'))
|
||||
else
|
||||
growl.success(_t('subscription_successfully_changed'))
|
||||
growl.success(_t('edit_plan.subscription_successfully_changed'))
|
||||
$state.go('app.admin.pricing')
|
||||
|
||||
|
||||
@ -259,6 +230,30 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve the name of a machine from its ID
|
||||
# @param machine_id {number} machine identifier
|
||||
# @returns {string} Machine's name
|
||||
##
|
||||
$scope.getMachineName = (machine_id) ->
|
||||
for machine in $scope.machines
|
||||
if machine.id == machine_id
|
||||
return machine.name
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve the name of a space from its ID
|
||||
# @param space_id {number} space identifier
|
||||
# @returns {string} Space's name
|
||||
##
|
||||
$scope.getSpaceName = (space_id) ->
|
||||
for space in $scope.spaces
|
||||
if space.id == space_id
|
||||
return space.name
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
@ -266,7 +261,7 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
##
|
||||
initialize = ->
|
||||
# Using the PlansController
|
||||
new PlanController($scope, groups, plans, machines, prices, partners, CSRF)
|
||||
new PlanController($scope, groups, prices, partners, CSRF)
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
@ -3,8 +3,8 @@
|
||||
##
|
||||
# Controller used in the prices edition page
|
||||
##
|
||||
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', 'TrainingsPricing', '$filter', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', '_t'
|
||||
, ($scope, $state, $uibModal, TrainingsPricing, $filter, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, _t) ->
|
||||
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'
|
||||
, ($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 ###
|
||||
## List of machines prices (not considering any plan)
|
||||
@ -37,6 +37,15 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
## List of coupons
|
||||
$scope.coupons = couponsPromise
|
||||
|
||||
## List of spaces
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
## Associate free space hours with subscriptions
|
||||
$scope.spaceCredits = spacesCreditsPromise
|
||||
|
||||
## List of spaces prices (not considering any plan)
|
||||
$scope.spacesPrices = spacesPricesPromise
|
||||
|
||||
## The plans list ordering. Default: by group
|
||||
$scope.orderPlans = 'group_id'
|
||||
|
||||
@ -56,7 +65,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
if data?
|
||||
TrainingsPricing.update({ id: trainingsPricing.id }, { trainings_pricing: { amount: data } }).$promise
|
||||
else
|
||||
_t('please_specify_a_number')
|
||||
_t('pricing.please_specify_a_number')
|
||||
|
||||
##
|
||||
# Retrieve a plan from its given identifier and returns it
|
||||
@ -89,13 +98,13 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
##
|
||||
$scope.showTrainings = (trainings) ->
|
||||
unless angular.isArray(trainings) and trainings.length > 0
|
||||
return _t('none')
|
||||
return _t('pricing.none')
|
||||
|
||||
selected = []
|
||||
angular.forEach $scope.trainings, (t) ->
|
||||
if trainings.indexOf(t.id) >= 0
|
||||
selected.push t.name
|
||||
return if selected.length then selected.join(' | ') else _t('none')
|
||||
return if selected.length then selected.join(' | ') else _t('pricing.none')
|
||||
|
||||
|
||||
|
||||
@ -110,7 +119,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
training_credit_nb: newdata.training_credits
|
||||
, angular.noop() # do nothing in case of success
|
||||
, (error) ->
|
||||
growl.error(_t('an_error_occurred_while_saving_the_number_of_credits'))
|
||||
growl.error(_t('pricing.an_error_occurred_while_saving_the_number_of_credits'))
|
||||
|
||||
# save the associated trainings
|
||||
angular.forEach $scope.trainingCreditsGroups, (original, key) ->
|
||||
@ -126,9 +135,9 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.trainingCredits.splice($scope.trainingCredits.indexOf(tc), 1)
|
||||
$scope.trainingCreditsGroups[planId].splice($scope.trainingCreditsGroups[planId].indexOf(tc.id), 1)
|
||||
, (error) ->
|
||||
growl.error(_t('an_error_occurred_while_deleting_credit_with_the_TRAINING', {TRAINING:tc.creditable.name}))
|
||||
growl.error(_t('pricing.an_error_occurred_while_deleting_credit_with_the_TRAINING', {TRAINING:tc.creditable.name}))
|
||||
else
|
||||
growl.error(_t('an_error_occurred_unable_to_find_the_credit_to_revoke'))
|
||||
growl.error(_t('pricing.an_error_occurred_unable_to_find_the_credit_to_revoke'))
|
||||
|
||||
# iterate through the new credits to add
|
||||
angular.forEach newdata.training_ids, (newTrainingId) ->
|
||||
@ -143,7 +152,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.trainingCreditsGroups[newTc.plan_id].push(newTc.creditable_id)
|
||||
, (error) -> # failed
|
||||
training = getTrainingFromId(newTrainingId)
|
||||
growl.error(_t('an_error_occurred_while_creating_credit_with_the_TRAINING', {TRAINING: training.name}))
|
||||
growl.error(_t('pricing.an_error_occurred_while_creating_credit_with_the_TRAINING', {TRAINING: training.name}))
|
||||
console.error(error)
|
||||
|
||||
|
||||
@ -177,11 +186,16 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
# @param credit {Object} credit object, inherited from $resource
|
||||
##
|
||||
$scope.showCreditableName = (credit) ->
|
||||
selected = _t('not_set')
|
||||
selected = _t('pricing.not_set')
|
||||
if credit and credit.creditable_id
|
||||
angular.forEach $scope.machines, (m)->
|
||||
if m.id == credit.creditable_id
|
||||
selected = m.name+' ( id. '+m.id+' )'
|
||||
if credit.creditable_type == 'Machine'
|
||||
angular.forEach $scope.machines, (m)->
|
||||
if m.id == credit.creditable_id
|
||||
selected = m.name + ' ( id. ' + m.id + ' )'
|
||||
else if credit.creditable_type == 'Space'
|
||||
angular.forEach $scope.spaces, (s)->
|
||||
if s.id == credit.creditable_id
|
||||
selected = s.name
|
||||
return selected
|
||||
|
||||
|
||||
@ -195,27 +209,27 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
$scope.saveMachineCredit = (data, id) ->
|
||||
for mc in $scope.machineCredits
|
||||
if mc.plan_id == data.plan_id and mc.creditable_id == data.creditable_id and (id == null or mc.id != id)
|
||||
growl.error(_t('error_a_credit_linking_this_machine_with_that_subscription_already_exists'))
|
||||
growl.error(_t('pricing.error_a_credit_linking_this_machine_with_that_subscription_already_exists'))
|
||||
unless id
|
||||
$scope.machineCredits.pop()
|
||||
return false
|
||||
|
||||
if id?
|
||||
Credit.update {id: id}, credit: data, ->
|
||||
growl.success(_t('changes_have_been_successfully_saved'))
|
||||
growl.success(_t('pricing.changes_have_been_successfully_saved'))
|
||||
else
|
||||
data.creditable_type = 'Machine'
|
||||
Credit.save
|
||||
credit: data
|
||||
, (resp) ->
|
||||
$scope.machineCredits[$scope.machineCredits.length-1].id = resp.id
|
||||
growl.success(_t('credit_was_successfully_saved'))
|
||||
growl.success(_t('pricing.credit_was_successfully_saved'))
|
||||
|
||||
|
||||
##
|
||||
# Removes the newly inserted but not saved machine credit / Cancel the current machine credit modification
|
||||
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||
# @param index {number} theme index in the $scope.machineCredits array
|
||||
# @param index {number} credit index in the $scope.machineCredits array
|
||||
##
|
||||
$scope.cancelMachineCredit = (rowform, index) ->
|
||||
if $scope.machineCredits[index].id?
|
||||
@ -235,6 +249,70 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create a new empty entry in the $scope.spaceCredits array
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.addSpaceCredit = (e)->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$scope.inserted =
|
||||
creditable_type: 'Space'
|
||||
$scope.spaceCredits.push($scope.inserted)
|
||||
$scope.status.isopen = !$scope.status.isopen
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Validation callback when editing space's credits. Save the changes.
|
||||
# This will prevent the creation of two credits associated with the same space and plan.
|
||||
# @param data {Object} space, associated plan and number of credit hours.
|
||||
# @param [id] {number} credit id for edition, create a new credit object if not provided
|
||||
##
|
||||
$scope.saveSpaceCredit = (data, id) ->
|
||||
for sc in $scope.spaceCredits
|
||||
if sc.plan_id == data.plan_id and sc.creditable_id == data.creditable_id and (id == null or sc.id != id)
|
||||
growl.error(_t('pricing.error_a_credit_linking_this_space_with_that_subscription_already_exists'))
|
||||
unless id
|
||||
$scope.spaceCredits.pop()
|
||||
return false
|
||||
|
||||
if id?
|
||||
Credit.update {id: id}, credit: data, ->
|
||||
growl.success(_t('pricing.changes_have_been_successfully_saved'))
|
||||
else
|
||||
data.creditable_type = 'Space'
|
||||
Credit.save
|
||||
credit: data
|
||||
, (resp) ->
|
||||
$scope.spaceCredits[$scope.spaceCredits.length - 1].id = resp.id
|
||||
growl.success(_t('pricing.credit_was_successfully_saved'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Removes the newly inserted but not saved space credit / Cancel the current space credit modification
|
||||
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||
# @param index {number} credit index in the $scope.spaceCredits array
|
||||
##
|
||||
$scope.cancelSpaceCredit = (rowform, index) ->
|
||||
if $scope.spaceCredits[index].id?
|
||||
rowform.$cancel()
|
||||
else
|
||||
$scope.spaceCredits.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Deletes the space credit at the specified index
|
||||
# @param index {number} space credit index in the $scope.spaceCredits array
|
||||
##
|
||||
$scope.removeSpaceCredit = (index) ->
|
||||
Credit.delete $scope.spaceCredits[index]
|
||||
$scope.spaceCredits.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# If the plan does not have a type, return a default value for display purposes
|
||||
# @param type {string|undefined|null} plan's type (eg. 'partner')
|
||||
@ -242,8 +320,8 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
##
|
||||
$scope.getPlanType = (type) ->
|
||||
if type == 'PartnerPlan'
|
||||
return _t('partner')
|
||||
else return _t('standard')
|
||||
return _t('pricing.partner')
|
||||
else return _t('pricing.standard')
|
||||
|
||||
##
|
||||
# Change the plans ordering criterion to the one provided
|
||||
@ -270,7 +348,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
if data?
|
||||
Price.update({ id: price.id }, { price: { amount: data } }).$promise
|
||||
else
|
||||
_t('please_specify_a_number')
|
||||
_t('pricing.please_specify_a_number')
|
||||
|
||||
##
|
||||
# Delete the specified subcription plan
|
||||
@ -284,17 +362,17 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_subscription_plan')
|
||||
title: _t('pricing.confirmation_required')
|
||||
msg: _t('pricing.do_you_really_want_to_delete_this_subscription_plan')
|
||||
, ->
|
||||
# the admin has confirmed, delete the plan
|
||||
Plan.delete {id: id}, (res) ->
|
||||
growl.success(_t('subscription_plan_was_successfully_deleted'))
|
||||
growl.success(_t('pricing.subscription_plan_was_successfully_deleted'))
|
||||
$scope.plans.splice(findItemIdxById(plans, id), 1)
|
||||
|
||||
, (error) ->
|
||||
console.error('[EditPricingController::deletePlan] Error: '+error.statusText) if error.statusText
|
||||
growl.error(_t('unable_to_delete_the_specified_subscription_an_error_occurred'))
|
||||
growl.error(_t('pricing.unable_to_delete_the_specified_subscription_an_error_occurred'))
|
||||
|
||||
|
||||
|
||||
@ -324,8 +402,8 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_coupon')
|
||||
title: _t('pricing.confirmation_required')
|
||||
msg: _t('pricing.do_you_really_want_to_delete_this_coupon')
|
||||
, ->
|
||||
# the admin has confirmed, delete the coupon
|
||||
Coupon.delete {id: id}, (res) ->
|
||||
@ -335,9 +413,9 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
, (error) ->
|
||||
console.error('[EditPricingController::deleteCoupon] Error: '+error.statusText) if error.statusText
|
||||
if error.status == 422
|
||||
growl.error(_t('unable_to_delete_the_specified_coupon_already_in_use'))
|
||||
growl.error(_t('pricing.unable_to_delete_the_specified_coupon_already_in_use'))
|
||||
else
|
||||
growl.error(_t('unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'))
|
||||
growl.error(_t('pricing.unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'))
|
||||
|
||||
|
||||
|
||||
@ -363,10 +441,10 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
## Callback to validate sending of the coupon
|
||||
$scope.ok = ->
|
||||
Coupon.send {coupon_code: coupon.code, user_id: $scope.ctrl.member.id}, (res) ->
|
||||
growl.success(_t('coupon_successfully_sent_to_USER', {USER: $scope.ctrl.member.name}))
|
||||
growl.success(_t('pricing.coupon_successfully_sent_to_USER', {USER: $scope.ctrl.member.name}))
|
||||
$uibModalInstance.close({user_id: $scope.ctrl.member.id})
|
||||
, (err) ->
|
||||
growl.error(_t('an_error_occurred_unable_to_send_the_coupon'))
|
||||
growl.error(_t('pricing.an_error_occurred_unable_to_send_the_coupon'))
|
||||
|
||||
## Callback to close the modal and cancel the sending process
|
||||
$scope.cancel = ->
|
||||
|
@ -45,6 +45,8 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
$scope.trainingExplicationsAlert = { name: 'training_explications_alert', value: settingsPromise.training_explications_alert }
|
||||
$scope.trainingInformationMessage = { name: 'training_information_message', value: settingsPromise.training_information_message}
|
||||
$scope.subscriptionExplicationsAlert = { name: 'subscription_explications_alert', value: settingsPromise.subscription_explications_alert }
|
||||
$scope.eventExplicationsAlert = {name: 'event_explications_alert', value: settingsPromise.event_explications_alert }
|
||||
$scope.spaceExplicationsAlert = { name: 'space_explications_alert', value: settingsPromise.space_explications_alert }
|
||||
$scope.windowStart = { name: 'booking_window_start', value: settingsPromise.booking_window_start }
|
||||
$scope.windowEnd = { name: 'booking_window_end', value: settingsPromise.booking_window_end }
|
||||
$scope.mainColorSetting = { name: 'main_color', value: settingsPromise.main_color }
|
||||
@ -73,7 +75,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
$scope.cancelDelay =
|
||||
name: 'booking_cancel_delay'
|
||||
value: parseInt(settingsPromise.booking_cancel_delay)
|
||||
|
||||
|
||||
$scope.enableReminder =
|
||||
name: 'reminder_enable'
|
||||
value: (settingsPromise.reminder_enable == 'true')
|
||||
@ -115,7 +117,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
value = setting.value
|
||||
|
||||
Setting.update { name: setting.name }, { value: value }, (data)->
|
||||
growl.success(_t('customization_of_SETTING_successfully_saved', {SETTING:_t(setting.name)}))
|
||||
growl.success(_t('settings.customization_of_SETTING_successfully_saved', { SETTING:_t('settings.' + setting.name) }))
|
||||
, (error)->
|
||||
console.log(error)
|
||||
|
||||
@ -134,7 +136,7 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
angular.forEach v, (err)->
|
||||
growl.error(err)
|
||||
else
|
||||
growl.success(_t('file_successfully_updated'))
|
||||
growl.success(_t('settings.file_successfully_updated'))
|
||||
if content.custom_asset.name is 'cgu-file'
|
||||
$scope.cguFile = content.custom_asset
|
||||
$scope.methods.cgu = 'put'
|
||||
|
@ -144,7 +144,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
##
|
||||
# Callback called when the active tab is changed.
|
||||
# recover the current tab and store its value in $scope.selectedIndex
|
||||
# @param tab {Object} elasticsearch statistic structure
|
||||
# @param tab {Object} elasticsearch statistic structure (from statistic_indices table)
|
||||
##
|
||||
$scope.setActiveTab = (tab) ->
|
||||
$scope.selectedIndex = tab
|
||||
@ -160,6 +160,23 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Returns true if the provided tab must be hidden due to some global or local configuration
|
||||
# @param tab {Object} elasticsearch statistic structure (from statistic_indices table)
|
||||
##
|
||||
$scope.hiddenTab = (tab) ->
|
||||
if tab.table
|
||||
if tab.es_type_key == 'subscription' && $rootScope.fablabWithoutPlans
|
||||
true
|
||||
else if tab.es_type_key == 'space' && $rootScope.fablabWithoutSpaces
|
||||
true
|
||||
else
|
||||
false
|
||||
else
|
||||
true
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to validate the filters and send a new request to elastic
|
||||
##
|
||||
@ -380,6 +397,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
"size": RESULTS_PER_PAGE
|
||||
"scroll": ES_SCROLL_TIME+'m'
|
||||
"stat-type": type
|
||||
"custom-query": if custom then JSON.stringify(Object.assign({exclude: custom.exclude}, buildElasticCustomCriterion(custom))) else ''
|
||||
"start-date": moment($scope.datePickerStart.selected).format()
|
||||
"end-date": moment($scope.datePickerEnd.selected).format()
|
||||
"body": buildElasticDataQuery(type, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting)
|
||||
@ -427,15 +445,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
"lte": ageMax
|
||||
# optional criterion
|
||||
if custom
|
||||
criterion = {
|
||||
"match" : {}
|
||||
}
|
||||
switch $scope.getCustomValueInputType($scope.customFilter.criterion)
|
||||
when 'input_date' then criterion.match[custom.key] = moment(custom.value).format('YYYY-MM-DD')
|
||||
when 'input_select' then criterion.match[custom.key] = custom.value.key
|
||||
when 'input_list' then criterion.match[custom.key+".name"] = custom.value
|
||||
else criterion.match[custom.key] = custom.value
|
||||
|
||||
criterion = buildElasticCustomCriterion(custom)
|
||||
if (custom.exclude)
|
||||
q = "query": {
|
||||
"filtered": {
|
||||
@ -470,6 +480,27 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Build the elasticSearch query DSL to match the selected cutom filter
|
||||
# @param custom {Object} if custom is empty or undefined, an empty string will be returned
|
||||
# @returns {{match:{*}}|string}
|
||||
##
|
||||
buildElasticCustomCriterion = (custom) ->
|
||||
if (custom)
|
||||
criterion = {
|
||||
"match" : {}
|
||||
}
|
||||
switch $scope.getCustomValueInputType($scope.customFilter.criterion)
|
||||
when 'input_date' then criterion.match[custom.key] = moment(custom.value).format('YYYY-MM-DD')
|
||||
when 'input_select' then criterion.match[custom.key] = custom.value.key
|
||||
when 'input_list' then criterion.match[custom.key+".name"] = custom.value
|
||||
else criterion.match[custom.key] = custom.value
|
||||
criterion
|
||||
else
|
||||
''
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Parse the provided criteria array and return the corresponding elasticSearch syntax
|
||||
# @param criteria {Array} array of {key_to_sort:order}
|
||||
|
@ -298,6 +298,13 @@ Application.Controllers.controller "TrainingsAdminController", ["$scope", "$stat
|
||||
$scope.selectTrainingToMonitor = ->
|
||||
Training.availabilities {id: $scope.monitoring.training.id}, (training) ->
|
||||
$scope.groupedAvailabilities = groupAvailabilities([training])
|
||||
# we open current year/month by default
|
||||
now = moment()
|
||||
$scope.accordions[training.name] = {}
|
||||
$scope.accordions[training.name][now.year()] =
|
||||
isOpenFirst: true
|
||||
$scope.accordions[training.name][now.year()][now.month()] =
|
||||
isOpenFirst: true
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "$window", "Session", "AuthService", "Auth", "$uibModal", "$state", 'growl', 'Notification', '$interval', "Setting", '_t', 'Version'
|
||||
, ($rootScope, $scope, $window, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version) ->
|
||||
Application.Controllers.controller 'ApplicationController', ["$rootScope", "$scope", "$window", '$locale', "Session", "AuthService", "Auth", "$uibModal", "$state", 'growl', 'Notification', '$interval', "Setting", '_t', 'Version'
|
||||
, ($rootScope, $scope, $window, $locale, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version) ->
|
||||
|
||||
|
||||
|
||||
@ -18,19 +18,24 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
$scope.version =
|
||||
version: ''
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$rootScope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
|
||||
|
||||
##
|
||||
# Set the current user to the provided value and initialize the session
|
||||
# @param user {Object} Rails/Devise user
|
||||
##
|
||||
$scope.setCurrentUser = (user) ->
|
||||
$rootScope.currentUser = user
|
||||
Session.create(user);
|
||||
getNotifications()
|
||||
# fab-manager's app-version
|
||||
if user.role == 'admin'
|
||||
$scope.version = Version.get()
|
||||
else
|
||||
$scope.version = {version: ''}
|
||||
unless angular.isUndefinedOrNull(user)
|
||||
$rootScope.currentUser = user
|
||||
Session.create(user);
|
||||
getNotifications()
|
||||
# fab-manager's app-version
|
||||
if user.role == 'admin'
|
||||
$scope.version = Version.get()
|
||||
else
|
||||
$scope.version = {version: ''}
|
||||
|
||||
|
||||
##
|
||||
@ -55,7 +60,9 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
Session.destroy()
|
||||
$rootScope.currentUser = null
|
||||
$rootScope.toCheckNotifications = false
|
||||
$scope.notifications = []
|
||||
$scope.notifications =
|
||||
total: 0
|
||||
unread: 0
|
||||
$state.go('app.public.home')
|
||||
, (error) ->
|
||||
# An error occurred logging out.
|
||||
@ -235,6 +242,10 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
# user is not logged in
|
||||
openLoginModal(toState, toParams)
|
||||
|
||||
# we stop polling notifications when the page is not in foreground
|
||||
onPageVisible (state) ->
|
||||
$rootScope.toCheckNotifications = (state is 'visible')
|
||||
|
||||
Setting.get { name: 'fablab_name' }, (data)->
|
||||
$scope.fablabName = data.setting.value
|
||||
Setting.get { name: 'name_genre' }, (data)->
|
||||
@ -243,8 +254,9 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
|
||||
|
||||
# shorthands
|
||||
$scope.isAuthenticated = Auth.isAuthenticated;
|
||||
$scope.isAuthorized = AuthService.isAuthorized;
|
||||
$scope.isAuthenticated = Auth.isAuthenticated
|
||||
$scope.isAuthorized = AuthService.isAuthorized
|
||||
$rootScope.login = $scope.login
|
||||
|
||||
|
||||
|
||||
@ -255,29 +267,31 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
getNotifications = ->
|
||||
$rootScope.toCheckNotifications = true
|
||||
unless $rootScope.checkNotificationsIsInit or !$rootScope.currentUser
|
||||
$scope.notifications = Notification.query {is_read: false}
|
||||
$scope.$watch 'notifications', (newValue, oldValue) ->
|
||||
diff = []
|
||||
angular.forEach newValue, (value) ->
|
||||
find = false
|
||||
for i in [0..oldValue.length] by 1
|
||||
if oldValue[i] and (value.id is oldValue[i].id)
|
||||
find = true
|
||||
break
|
||||
setTimeout ->
|
||||
# we request the most recent notifications
|
||||
Notification.last_unread (notifications) ->
|
||||
$rootScope.lastCheck = new Date()
|
||||
$scope.notifications = notifications.totals
|
||||
|
||||
unless find
|
||||
diff.push(value)
|
||||
toDisplay = []
|
||||
angular.forEach notifications.notifications, (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")}})
|
||||
|
||||
angular.forEach diff, (notification, key) ->
|
||||
growl.info(notification.message.description)
|
||||
|
||||
, true
|
||||
angular.forEach toDisplay, (notification) ->
|
||||
growl.info(notification.message.description)
|
||||
, 2000
|
||||
|
||||
checkNotifications = ->
|
||||
if $rootScope.toCheckNotifications
|
||||
Notification.query({is_read: false}).$promise.then (data) ->
|
||||
$scope.notifications = data;
|
||||
Notification.polling({last_poll: $rootScope.lastCheck}).$promise.then (data) ->
|
||||
$rootScope.lastCheck = new Date()
|
||||
$scope.notifications = data.totals
|
||||
|
||||
angular.forEach data.notifications, (notification) ->
|
||||
growl.info(notification.message.description)
|
||||
|
||||
$interval(checkNotifications, NOTIFICATIONS_CHECK_PERIOD)
|
||||
$rootScope.checkNotificationsIsInit = true
|
||||
@ -361,6 +375,52 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Detect if the current page (tab/window) is active of put as background.
|
||||
# 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
|
||||
##
|
||||
onPageVisible = (callback) ->
|
||||
hidden = 'hidden'
|
||||
|
||||
onchange = (evt) ->
|
||||
v = 'visible'
|
||||
h = 'hidden'
|
||||
evtMap =
|
||||
focus: v
|
||||
focusin: v
|
||||
pageshow: v
|
||||
blur: h
|
||||
focusout: h
|
||||
pagehide: h
|
||||
evt = evt or window.event
|
||||
if evt.type of evtMap
|
||||
if typeof callback == 'function' then callback(evtMap[evt.type])
|
||||
else
|
||||
if typeof callback == 'function' then callback(if @[hidden] then 'hidden' else 'visible')
|
||||
return
|
||||
|
||||
# Standards:
|
||||
if hidden of document
|
||||
document.addEventListener 'visibilitychange', onchange
|
||||
else if (hidden = 'mozHidden') of document
|
||||
document.addEventListener 'mozvisibilitychange', onchange
|
||||
else if (hidden = 'webkitHidden') of document
|
||||
document.addEventListener 'webkitvisibilitychange', onchange
|
||||
else if (hidden = 'msHidden') of document
|
||||
document.addEventListener 'msvisibilitychange', onchange
|
||||
# IE 9 and lower
|
||||
else if 'onfocusin' of document
|
||||
document.onfocusin = document.onfocusout = onchange
|
||||
# All others
|
||||
else
|
||||
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange
|
||||
# set the initial state (but only if browser supports the Page Visibility API)
|
||||
if document[hidden] != undefined
|
||||
onchange type: if document[hidden] then 'blur' else 'focus'
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
||||
|
@ -4,14 +4,15 @@
|
||||
# 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',
|
||||
($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise) ->
|
||||
Application.Controllers.controller "CalendarController", ["$scope", "$state", "$aside", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise',
|
||||
($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) ->
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
currentMachineEvent = null
|
||||
machinesPromise.forEach((m) -> m.checked = true)
|
||||
trainingsPromise.forEach((t) -> t.checked = true)
|
||||
spacesPromise.forEach((s) -> s.checked = true)
|
||||
|
||||
## check all formation/machine is select in filter
|
||||
isSelectAll = (type, scope) ->
|
||||
@ -25,6 +26,9 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
## List of machines
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## List of spaces
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
## add availabilities source to event sources
|
||||
$scope.eventSources = []
|
||||
|
||||
@ -34,6 +38,7 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
scope.filter = $scope.filter =
|
||||
trainings: isSelectAll('trainings', scope)
|
||||
machines: isSelectAll('machines', scope)
|
||||
spaces: isSelectAll('spaces', scope)
|
||||
evt: filter.evt
|
||||
dispo: filter.dispo
|
||||
$scope.calendarConfig.events = availabilitySourceUrl()
|
||||
@ -43,6 +48,7 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
$scope.filter =
|
||||
trainings: isSelectAll('trainings', $scope)
|
||||
machines: isSelectAll('machines', $scope)
|
||||
spaces: isSelectAll('spaces', $scope)
|
||||
evt: true
|
||||
dispo: true
|
||||
|
||||
@ -62,15 +68,18 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
$scope.trainings
|
||||
machines: ->
|
||||
$scope.machines
|
||||
spaces: ->
|
||||
$scope.spaces
|
||||
filter: ->
|
||||
$scope.filter
|
||||
toggleFilter: ->
|
||||
$scope.toggleFilter
|
||||
filterAvailabilities: ->
|
||||
$scope.filterAvailabilities
|
||||
controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'filter', 'toggleFilter', 'filterAvailabilities', ($scope, $uibModalInstance, trainings, machines, filter, toggleFilter, filterAvailabilities) ->
|
||||
controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'filter', 'toggleFilter', 'filterAvailabilities', ($scope, $uibModalInstance, trainings, machines, spaces, filter, toggleFilter, filterAvailabilities) ->
|
||||
$scope.trainings = trainings
|
||||
$scope.machines = machines
|
||||
$scope.spaces = spaces
|
||||
$scope.filter = filter
|
||||
|
||||
$scope.toggleFilter = (type, filter) ->
|
||||
@ -94,13 +103,19 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
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.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
|
||||
@ -109,10 +124,10 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
# ui-calendar will trigger rerender calendar
|
||||
$scope.calendarConfig.defaultView = view.type
|
||||
today = if currentMachineEvent then currentMachineEvent.start else moment().utc().startOf('day')
|
||||
if today > view.start and today < view.end and today != view.start
|
||||
if today > view.intervalStart and today < view.intervalEnd and today != view.intervalStart
|
||||
$scope.calendarConfig.defaultDate = today
|
||||
else
|
||||
$scope.calendarConfig.defaultDate = view.start
|
||||
$scope.calendarConfig.defaultDate = view.intervalStart
|
||||
if view.type == 'agendaDay'
|
||||
$scope.calendarConfig.slotEventOverlap = false
|
||||
else
|
||||
@ -136,7 +151,8 @@ Application.Controllers.controller "CalendarController", ["$scope", "$state", "$
|
||||
getFilter = ->
|
||||
t = $scope.trainings.filter((t) -> t.checked).map((t) -> t.id)
|
||||
m = $scope.machines.filter((m) -> m.checked).map((m) -> m.id)
|
||||
{t: t, m: m, evt: $scope.filter.evt, dispo: $scope.filter.dispo}
|
||||
s = $scope.spaces.filter((s) -> s.checked).map((s) -> s.id)
|
||||
{t: t, m: m, s: s, evt: $scope.filter.evt, dispo: $scope.filter.dispo}
|
||||
|
||||
availabilitySourceUrl = ->
|
||||
"/api/availabilities/public?#{$.param(getFilter())}"
|
||||
|
@ -154,6 +154,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
tickets: {}
|
||||
toReserve: false
|
||||
amountTotal : 0
|
||||
totalNoCoupon: 0
|
||||
totalSeats: 0
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
@ -172,6 +173,9 @@ 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)
|
||||
|
||||
## Message displayed to the end user about rules that applies to events reservations
|
||||
$scope.eventExplicationsAlert = settingsPromise.event_explications_alert
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -400,6 +404,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
r = mkReservation($scope.ctrl.member, $scope.reserve, $scope.event)
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.reserve.amountTotal = res.price
|
||||
$scope.reserve.totalNoCoupon = res.price_without_coupon
|
||||
else
|
||||
$scope.reserve.amountTotal = null
|
||||
|
||||
@ -560,9 +565,9 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
member: $scope.ctrl.member
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $locale, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl, wallet, helpers, $filter, coupon) ->
|
||||
# User's wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
@ -574,8 +579,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Callback for the stripe payment authorization
|
||||
@ -616,9 +620,9 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', '$locale', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, $locale, helpers, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) ->
|
||||
# User's wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
@ -630,8 +634,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Button label
|
||||
@ -675,6 +678,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
$scope.event.nb_free_places = $scope.event.nb_free_places - reservation.total_booked_seats
|
||||
resetEventReserve()
|
||||
$scope.reserveSuccess = true
|
||||
$scope.coupon.applied = null
|
||||
$scope.reservations.push reservation
|
||||
if $scope.currentUser.role == 'admin'
|
||||
$scope.ctrl.member = null
|
||||
|
@ -93,7 +93,7 @@ _reserveMachine = (machine, e) ->
|
||||
# if the currently logged'in user has completed the training for this machine, or this machine does not require
|
||||
# a prior training, just redirect him to the machine's booking page
|
||||
if machine.current_user_is_training or machine.trainings.length == 0
|
||||
_this.$state.go('app.logged.machines_reserve', {id: machine.id})
|
||||
_this.$state.go('app.logged.machines_reserve', {id: machine.slug})
|
||||
else
|
||||
# otherwise, if a user is authenticated ...
|
||||
if _this.$scope.isAuthenticated()
|
||||
@ -231,10 +231,10 @@ Application.Controllers.controller "EditMachineController", ["$scope", '$state',
|
||||
##
|
||||
# Controller used in the machine details page (public)
|
||||
##
|
||||
Application.Controllers.controller "ShowMachineController", ['$scope', '$state', '$uibModal', '$stateParams', '_t', 'Machine', 'growl', 'machinePromise'
|
||||
, ($scope, $state, $uibModal, $stateParams, _t, Machine, growl, machinePromise) ->
|
||||
Application.Controllers.controller "ShowMachineController", ['$scope', '$state', '$uibModal', '$stateParams', '_t', 'Machine', 'growl', 'machinePromise', 'dialogs'
|
||||
, ($scope, $state, $uibModal, $stateParams, _t, Machine, growl, machinePromise, dialogs) ->
|
||||
|
||||
## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list
|
||||
## Retrieve the details for the machine id in the URL, if an error occurs redirect the user to the machines list
|
||||
$scope.machine = machinePromise
|
||||
|
||||
##
|
||||
@ -245,11 +245,17 @@ Application.Controllers.controller "ShowMachineController", ['$scope', '$state',
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
console.error _t('unauthorized_operation')
|
||||
else
|
||||
# delete the machine then redirect to the machines listing
|
||||
machine.$delete ->
|
||||
$state.go('app.public.machines_list')
|
||||
, (error)->
|
||||
growl.warning(_t('the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users'))
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_machine')
|
||||
, -> # deletion confirmed
|
||||
# delete the machine then redirect to the machines listing
|
||||
machine.$delete ->
|
||||
$state.go('app.public.machines_list')
|
||||
, (error)->
|
||||
growl.warning(_t('the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users'))
|
||||
##
|
||||
# Callback to book a reservation for the current machine
|
||||
##
|
||||
@ -268,20 +274,20 @@ Application.Controllers.controller "ShowMachineController", ['$scope', '$state',
|
||||
# This controller workflow is pretty similar to the trainings reservation controller.
|
||||
##
|
||||
|
||||
Application.Controllers.controller "ReserveMachineController", ["$scope", "$state", '$stateParams', "$uibModal", '_t', "moment", 'Machine', 'Auth', 'dialogs', '$timeout', 'Price', 'Member', 'Availability', 'Slot', 'Setting', 'CustomAsset', 'plansPromise', 'groupsPromise', 'growl', 'machinePromise', 'settingsPromise', 'Wallet', 'helpers', 'uiCalendarConfig', 'CalendarConfig',
|
||||
($scope, $state, $stateParams, $uibModal, _t, moment, Machine, Auth, dialogs, $timeout, Price, Member, Availability, Slot, Setting, CustomAsset, plansPromise, groupsPromise, growl, machinePromise, settingsPromise, Wallet, helpers, uiCalendarConfig, CalendarConfig) ->
|
||||
Application.Controllers.controller "ReserveMachineController", ["$scope", '$stateParams', '_t', "moment", 'Auth', '$timeout', 'Member', 'Availability', 'plansPromise', 'groupsPromise', 'machinePromise', 'settingsPromise', 'uiCalendarConfig', 'CalendarConfig',
|
||||
($scope, $stateParams, _t, moment, Auth, $timeout, Member, Availability, plansPromise, groupsPromise, machinePromise, settingsPromise, uiCalendarConfig, CalendarConfig) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# Slot already booked by the current user
|
||||
# Slot free to be booked
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::MACHINE_COLOR %>'
|
||||
|
||||
# Slot already booked by another user
|
||||
UNAVAILABLE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::MACHINE_IS_RESERVED_BY_USER %>'
|
||||
|
||||
# Slot free to be booked
|
||||
# Slot already booked by the current user
|
||||
BOOKED_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::IS_RESERVED_BY_CURRENT_USER %>'
|
||||
|
||||
|
||||
@ -291,30 +297,31 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
## bind the machine availabilities with full-Calendar events
|
||||
$scope.eventSources = []
|
||||
|
||||
## fullCalendar event. The last selected slot that the user want to book
|
||||
$scope.slotToPlace = null
|
||||
|
||||
## fullCalendar event. An already booked slot that the user want to modify
|
||||
$scope.slotToModify = null
|
||||
|
||||
## indicates the state of the current view : calendar or plans informations
|
||||
## indicates the state of the current view : calendar or plans information
|
||||
$scope.plansAreShown = false
|
||||
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## array of fullCalendar events. Slots where the user want to book
|
||||
$scope.eventsReserved = []
|
||||
## the moment when the plan selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.planSelectionTime = null
|
||||
|
||||
## total amount of the bill to pay
|
||||
$scope.amountTotal = 0
|
||||
## mapping of fullCalendar events.
|
||||
$scope.events =
|
||||
reserved: [] # Slots that the user wants to book
|
||||
modifiable: null # Slot that the user wants to change
|
||||
placable: null # Destination slot for the change
|
||||
paid: [] # Slots that were just booked by the user (transaction ok)
|
||||
moved: null # Slots that were just moved by the user (change done) -> {newSlot:* oldSlot: *}
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
## the moment when the slot selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.selectionTime = null
|
||||
|
||||
## is the user allowed to change the date of his booking
|
||||
$scope.enableBookingMove = true
|
||||
## the last clicked event in the calender
|
||||
$scope.selectedEvent = null
|
||||
|
||||
## the application global settings
|
||||
$scope.settings = settingsPromise
|
||||
|
||||
## list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = []
|
||||
@ -343,86 +350,87 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
## Global config: message to the end user concerning the subscriptions rules
|
||||
$scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert
|
||||
|
||||
## Gloabl config: message to the end user concerning the machine bookings
|
||||
## Global config: message to the end user concerning the machine bookings
|
||||
$scope.machineExplicationsAlert = settingsPromise.machine_explications_alert
|
||||
|
||||
## Global config: is the user authorized to change his bookings slots?
|
||||
$scope.enableBookingMove = (settingsPromise.booking_move_enable == "true")
|
||||
|
||||
## 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 bookings?
|
||||
$scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true")
|
||||
|
||||
## Global config: delay in hours before a booking while the cancellation is forbidden
|
||||
$scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay)
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'added to cart'
|
||||
##
|
||||
$scope.markSlotAsAdded = ->
|
||||
$scope.selectedEvent.backgroundColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.selectedEvent.title = _t('i_reserve')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, removing the previously booked slot from the selection
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
# Change the last selected slot's appearence to looks like 'never added to cart'
|
||||
##
|
||||
$scope.removeSlotToModify = (e) ->
|
||||
e.preventDefault()
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
$scope.markSlotAsRemoved = (slot) ->
|
||||
slot.backgroundColor = 'white'
|
||||
slot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
slot.title = ''
|
||||
slot.isValid = false
|
||||
slot.id = null
|
||||
slot.is_reserved = false
|
||||
slot.can_modify = false
|
||||
slot.offered = false
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, cancel the choice of the new slot
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
# Callback when a slot was successfully cancelled. Reset the slot style as 'ready to book'
|
||||
##
|
||||
$scope.removeSlotToPlace = (e)->
|
||||
e.preventDefault()
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
$scope.slotCancelled = ->
|
||||
$scope.markSlotAsRemoved($scope.selectedEvent)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, confirm the modification.
|
||||
# Change the last selected slot's appearence to looks like 'currently looking for a new destination to exchange'
|
||||
##
|
||||
$scope.markSlotAsModifying = ->
|
||||
$scope.selectedEvent.backgroundColor = '#eee'
|
||||
$scope.selectedEvent.title = _t('i_change')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'the slot being exchanged will take this place'
|
||||
##
|
||||
$scope.changeModifyMachineSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.selectedEvent._id
|
||||
$scope.selectedEvent.backgroundColor = '#bbb'
|
||||
$scope.selectedEvent.title = _t('i_shift')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
##
|
||||
$scope.modifyMachineSlot = ->
|
||||
Slot.update {id: $scope.slotToModify.id},
|
||||
slot:
|
||||
start_at: $scope.slotToPlace.start
|
||||
end_at: $scope.slotToPlace.end
|
||||
availability_id: $scope.slotToPlace.availability_id
|
||||
, -> # success
|
||||
$scope.modifiedSlots =
|
||||
newReservedSlot: $scope.slotToPlace
|
||||
oldReservedSlot: $scope.slotToModify
|
||||
$scope.slotToPlace.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.borderColor = $scope.slotToModify.borderColor
|
||||
$scope.slotToPlace.id = $scope.slotToModify.id
|
||||
$scope.slotToPlace.is_reserved = true
|
||||
$scope.slotToPlace.can_modify = true
|
||||
$scope.slotToPlace = null
|
||||
$scope.events.placable.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.borderColor = $scope.events.modifiable.borderColor
|
||||
$scope.events.placable.id = $scope.events.modifiable.id
|
||||
$scope.events.placable.is_reserved = true
|
||||
$scope.events.placable.can_modify = true
|
||||
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify.title = ''
|
||||
$scope.slotToModify.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.slotToModify.id = null
|
||||
$scope.slotToModify.is_reserved = false
|
||||
$scope.slotToModify.can_modify = false
|
||||
$scope.slotToModify = null
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
$scope.events.modifiable.title = ''
|
||||
$scope.events.modifiable.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.events.modifiable.id = null
|
||||
$scope.events.modifiable.is_reserved = false
|
||||
$scope.events.modifiable.can_modify = false
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, (err) -> # failure
|
||||
growl.error(_t('unable_to_change_the_reservation'))
|
||||
console.error(err)
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
@ -430,14 +438,13 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyMachineSlot = ->
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
$scope.events.modifiable.title = if $scope.currentUser.role isnt 'admin' then _t('i_ve_reserved') else _t('not_available')
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
@ -446,67 +453,10 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# reservations. (admins only)
|
||||
##
|
||||
$scope.updateMember = ->
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Add the provided slot to the shopping cart (state transition from free to 'about to be reserved')
|
||||
# and increment the total amount of the cart if needed.
|
||||
# @param machineSlot {Object} fullCalendar event object
|
||||
##
|
||||
$scope.validMachineSlot = (machineSlot)->
|
||||
machineSlot.isValid = true
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Remove the provided slot from the shopping cart (state transition from 'about to be reserved' to free)
|
||||
# and decrement the total amount of the cart if needed.
|
||||
# @param machineSlot {Object} fullCalendar event object
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeMachineSlot = (machineSlot, e)->
|
||||
e.preventDefault() if e
|
||||
machineSlot.backgroundColor = 'white'
|
||||
machineSlot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
machineSlot.title = ''
|
||||
machineSlot.isValid = false
|
||||
|
||||
if machineSlot.machine.is_reduced_amount
|
||||
angular.forEach $scope.ctrl.member.machine_credits, (credit)->
|
||||
if credit.machine_id = machineSlot.machine.id
|
||||
credit.hours_used--
|
||||
machineSlot.machine.is_reduced_amount = false
|
||||
|
||||
index = $scope.eventsReserved.indexOf(machineSlot)
|
||||
$scope.eventsReserved.splice(index, 1)
|
||||
if $scope.eventsReserved.length == 0
|
||||
if $scope.plansAreShown
|
||||
$scope.selectedPlan = null
|
||||
$scope.plansAreShown = false
|
||||
updateCartPrice()
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks that every selected slots were added to the shopping cart. Ie. will return false if
|
||||
# any checked slot was not validated by the user.
|
||||
##
|
||||
$scope.machineSlotsValid = ->
|
||||
isValid = true
|
||||
angular.forEach $scope.eventsReserved, (m)->
|
||||
isValid = false if !m.isValid
|
||||
isValid
|
||||
|
||||
|
||||
|
||||
@ -518,30 +468,10 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
e.preventDefault()
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectPlan($scope.selectedPlan)
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Validates the shopping chart and redirect the user to the payment step
|
||||
##
|
||||
$scope.payMachine = ->
|
||||
|
||||
# first, we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
reservation = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan)
|
||||
|
||||
Wallet.getWalletByUser {user_id: $scope.ctrl.member.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount)
|
||||
if $scope.currentUser.role isnt 'admin' and amountToPay > 0
|
||||
payByStripe(reservation)
|
||||
else
|
||||
if $scope.currentUser.role is 'admin' or amountToPay is 0
|
||||
payOnSite(reservation)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.error(_t('please_select_a_member_first'))
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
##
|
||||
@ -555,34 +485,41 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# @param plan {Object} the plan to subscribe
|
||||
##
|
||||
$scope.selectPlan = (plan) ->
|
||||
if $scope.isAuthenticated()
|
||||
angular.forEach $scope.eventsReserved, (machineSlot)->
|
||||
angular.forEach $scope.ctrl.member.machine_credits, (credit)->
|
||||
if credit.machine_id = machineSlot.machine.id
|
||||
credit.hours_used = 0
|
||||
machineSlot.machine.is_reduced_amount = false
|
||||
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
updateCartPrice()
|
||||
# toggle selected plan
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.login null, ->
|
||||
$scope.selectedPlan = plan
|
||||
updateCartPrice()
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks if $scope.slotToModify and $scope.slotToPlace have tag incompatibilities
|
||||
# @returns {boolean} true in case of incompatibility
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
$scope.tagMissmatch = ->
|
||||
for tag in $scope.slotToModify.tags
|
||||
if tag.id not in $scope.slotToPlace.tag_ids
|
||||
return true
|
||||
false
|
||||
$scope.afterPayment = (reservation)->
|
||||
angular.forEach $scope.events.reserved, (machineSlot, key) ->
|
||||
machineSlot.is_reserved = true
|
||||
machineSlot.can_modify = true
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
machineSlot.title = _t('i_ve_reserved')
|
||||
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.currentUser)
|
||||
else
|
||||
machineSlot.title = _t('not_available')
|
||||
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member)
|
||||
machineSlot.backgroundColor = 'white'
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
|
||||
refetchCalendar()
|
||||
|
||||
|
||||
|
||||
@ -600,74 +537,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
|
||||
# watch when a coupon is applied to re-compute the total price
|
||||
$scope.$watch 'coupon.applied', (newValue, oldValue) ->
|
||||
unless newValue == null and oldValue == null
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create an hash map implementing the Reservation specs
|
||||
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
|
||||
# @param slots {Array<Object>} Array of fullCalendar events: slots selected on the calendar
|
||||
# @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation
|
||||
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||
##
|
||||
mkReservation = (member, slots, plan = null) ->
|
||||
reservation =
|
||||
user_id: member.id
|
||||
reservable_id: (slots[0].machine.id if slots.length > 0)
|
||||
reservable_type: 'Machine'
|
||||
slots_attributes: []
|
||||
plan_id: (plan.id if plan)
|
||||
angular.forEach slots, (slot, key) ->
|
||||
reservation.slots_attributes.push
|
||||
start_at: slot.start
|
||||
end_at: slot.end
|
||||
availability_id: slot.availability_id
|
||||
offered: slot.offered || false
|
||||
|
||||
reservation
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
|
||||
# @param reservation {Object} as returned by mkReservation()
|
||||
# @param coupon {Object} Coupon as returned from the API
|
||||
# @return {{reservation:Object, coupon_code:string}}
|
||||
##
|
||||
mkRequestParams = (reservation, coupon) ->
|
||||
params =
|
||||
reservation: reservation
|
||||
coupon_code: (coupon.code if coupon)
|
||||
|
||||
params
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Update the total price of the current selection/reservation
|
||||
##
|
||||
updateCartPrice = ->
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan)
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
setSlotsDetails(res.details)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.warning(_t('please_select_a_member_first'))
|
||||
$scope.amountTotal = null
|
||||
|
||||
|
||||
setSlotsDetails = (details) ->
|
||||
angular.forEach $scope.eventsReserved, (slot) ->
|
||||
angular.forEach details.slots, (s) ->
|
||||
if moment(s.start_at).isSame(slot.start)
|
||||
slot.promo = s.promo
|
||||
slot.price = s.price
|
||||
|
||||
|
||||
##
|
||||
@ -677,67 +546,8 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# if it's too late).
|
||||
##
|
||||
calendarEventClickCb = (event, jsEvent, view) ->
|
||||
|
||||
if !event.is_reserved && !$scope.slotToModify
|
||||
index = $scope.eventsReserved.indexOf(event)
|
||||
if index == -1
|
||||
event.backgroundColor = FREE_SLOT_BORDER_COLOR
|
||||
event.title = _t('i_reserve')
|
||||
$scope.eventsReserved.push event
|
||||
else
|
||||
$scope.removeMachineSlot(event)
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
else if !event.is_reserved && $scope.slotToModify
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = event
|
||||
event.backgroundColor = '#bbb'
|
||||
event.title = _t('i_shift')
|
||||
else if event.is_reserved and (slotCanBeModified(event) or slotCanBeCanceled(event)) and !$scope.slotToModify and $scope.eventsReserved.length == 0
|
||||
event.movable = slotCanBeModified(event)
|
||||
event.cancelable = slotCanBeCanceled(event)
|
||||
dialogs.confirm
|
||||
templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>'
|
||||
resolve:
|
||||
object: -> event
|
||||
, (type) ->
|
||||
if type == 'move'
|
||||
$scope.modifiedSlots = null
|
||||
$scope.slotToModify = event
|
||||
event.backgroundColor = '#eee'
|
||||
event.title = _t('i_change')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_cancel_this_reservation')
|
||||
, -> # cancel confirmed
|
||||
Slot.cancel {id: event.id}, -> # successfully canceled
|
||||
growl.success _t('reservation_was_cancelled_successfully')
|
||||
$scope.canceledSlot = event
|
||||
$scope.canceledSlot.backgroundColor = 'white'
|
||||
$scope.canceledSlot.title = ''
|
||||
$scope.canceledSlot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.canceledSlot.id = null
|
||||
$scope.canceledSlot.is_reserved = false
|
||||
$scope.canceledSlot.can_modify = false
|
||||
$scope.canceledSlot = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cancellation_failed')
|
||||
, ->
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
updateCartPrice()
|
||||
$scope.selectedEvent = event
|
||||
$scope.selectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
@ -757,196 +567,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
##
|
||||
payByStripe = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $locale, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Currency symbol or abreviation for the current locale
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
##
|
||||
# Callback to process the payment with Stripe, triggered on button click
|
||||
##
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
growl.error(response.error.message)
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push
|
||||
msg: response.data.card[0]
|
||||
type: 'danger'
|
||||
$scope.attempting = false
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
##
|
||||
payOnSite = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>'
|
||||
size: 'sm'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', '$locale', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, $locale, coupon) ->
|
||||
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Global price (total of all items)
|
||||
$scope.price = price.price
|
||||
|
||||
# Price to pay (wallet deducted)
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Currency symbol or abreviation for the current locale
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Button label
|
||||
if $scope.amount > 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')($scope.amount)}, "messageformat")
|
||||
else
|
||||
if price.price > 0 and $scope.walletAmount == 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')(price.price)}, "messageformat")
|
||||
else
|
||||
$scope.validButtonName = _t('confirm')
|
||||
|
||||
##
|
||||
# Callback to process the local payment, triggered on button click
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be modified by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeModified = (slot)->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be canceled by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeCanceled = (slot) ->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
##
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
afterPayment = (reservation)->
|
||||
angular.forEach $scope.eventsReserved, (machineSlot, key) ->
|
||||
machineSlot.is_reserved = true
|
||||
machineSlot.can_modify = true
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
machineSlot.title = _t('i_ve_reserved')
|
||||
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.currentUser)
|
||||
else
|
||||
machineSlot.title = _t('not_available')
|
||||
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR
|
||||
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member)
|
||||
machineSlot.backgroundColor = 'white'
|
||||
$scope.paidMachineSlots = $scope.eventsReserved
|
||||
|
||||
$scope.eventsReserved = []
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# After payment, update the id of the newly reserved slot with the id returned by the server.
|
||||
# This will allow the user to modify the reservation he just booked. The associated user will also be registered
|
||||
@ -964,15 +584,20 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
|
||||
|
||||
##
|
||||
# Search for the requested plan in the provided array and return its price.
|
||||
# @param plansArray {Array} full list of plans
|
||||
# @param planId {Number} plan identifier
|
||||
# @returns {Number|null} price of the given plan or null if not found
|
||||
# Update the calendar's display to render the new attributes of the events
|
||||
##
|
||||
findAmountByPlanId = (plansArray, planId)->
|
||||
for plan in plansArray
|
||||
return plan.amount if plan.plan_id == planId
|
||||
return null
|
||||
updateCalendar = ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Asynchronously fetch the events from the API and refresh the calendar's view with these new events
|
||||
##
|
||||
refetchCalendar = ->
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
|
@ -48,9 +48,16 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
linkIcon: 'credit-card'
|
||||
})
|
||||
|
||||
unless Fablab.withoutSpaces
|
||||
$scope.navLinks.splice(3, 0, {
|
||||
state: 'app.public.spaces_list'
|
||||
linkText: 'reserve_a_space'
|
||||
linkIcon: 'rocket'
|
||||
})
|
||||
|
||||
|
||||
Fablab.adminNavLinks = Fablab.adminNavLinks || []
|
||||
Fablab.adminNavLinks = [
|
||||
adminNavLinks = [
|
||||
{
|
||||
state: 'app.admin.trainings'
|
||||
linkText: 'trainings_monitoring'
|
||||
@ -108,5 +115,12 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
}
|
||||
].concat(Fablab.adminNavLinks)
|
||||
|
||||
$scope.adminNavLinks = Fablab.adminNavLinks
|
||||
$scope.adminNavLinks = adminNavLinks
|
||||
|
||||
unless Fablab.withoutSpaces
|
||||
$scope.adminNavLinks.splice(7, 0, {
|
||||
state: 'app.public.spaces_list'
|
||||
linkText: 'manage_the_spaces'
|
||||
linkIcon: 'rocket'
|
||||
})
|
||||
]
|
||||
|
@ -14,10 +14,10 @@ Application.Controllers.controller "MembersController", ["$scope", 'Member', 'me
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
|
||||
## currently displayed page of members
|
||||
$scope.page = 1
|
||||
|
||||
|
||||
## members list
|
||||
$scope.members = membersPromise
|
||||
|
||||
@ -31,12 +31,12 @@ Application.Controllers.controller "MembersController", ["$scope", 'Member', 'me
|
||||
$scope.showNextMembers = ->
|
||||
$scope.page += 1
|
||||
Member.query {
|
||||
requested_attributes:'[profile]',
|
||||
page: $scope.page,
|
||||
requested_attributes:'[profile]',
|
||||
page: $scope.page,
|
||||
size: MEMBERS_PER_PAGE
|
||||
}, (members) ->
|
||||
$scope.members = $scope.members.concat(members)
|
||||
|
||||
|
||||
if (!members[0] || members[0].maxMembers <= $scope.members.length)
|
||||
$scope.noMoreResults = true
|
||||
|
||||
@ -225,7 +225,9 @@ Application.Controllers.controller "EditProfileController", ["$scope", "$rootSco
|
||||
Session.destroy()
|
||||
$rootScope.currentUser = null
|
||||
$rootScope.toCheckNotifications = false
|
||||
$scope.notifications = []
|
||||
$scope.notifications =
|
||||
total: 0
|
||||
unread: 0
|
||||
$window.location.href = $scope.activeProvider.link_to_sso_connect
|
||||
|
||||
|
||||
@ -260,7 +262,7 @@ Application.Controllers.controller "EditProfileController", ["$scope", "$rootSco
|
||||
##
|
||||
Application.Controllers.controller "ShowProfileController", ["$scope", 'memberPromise', 'SocialNetworks', ($scope, memberPromise, SocialNetworks) ->
|
||||
|
||||
## Selected user's informations
|
||||
## Selected user's information
|
||||
$scope.user = memberPromise # DEPENDENCY WITH NAVINUM GAMIFICATION PLUGIN !!!!
|
||||
|
||||
## List of social networks associated with this user and toggle 'show all' state
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
##
|
||||
# Controller used in notifications page
|
||||
# inherits $scope.$parent.notifications (unread notifications) from ApplicationController
|
||||
# inherits $scope.$parent.notifications (global notifications state) from ApplicationController
|
||||
##
|
||||
Application.Controllers.controller "NotificationsController", ["$scope", 'Notification', ($scope, Notification) ->
|
||||
|
||||
@ -20,6 +20,15 @@ Application.Controllers.controller "NotificationsController", ["$scope", 'Notifi
|
||||
## Array containg the archived notifications (already read)
|
||||
$scope.notificationsRead = []
|
||||
|
||||
## Array containg the new notifications (not read)
|
||||
$scope.notificationsUnread = []
|
||||
|
||||
## Total number of notifications for the current user
|
||||
$scope.total = 0
|
||||
|
||||
## Total number of unread notifications for the current user
|
||||
$scope.totalUnread = 0
|
||||
|
||||
## By default, the pagination mode is activated to limit the page size
|
||||
$scope.paginateActive = true
|
||||
|
||||
@ -39,10 +48,15 @@ Application.Controllers.controller "NotificationsController", ["$scope", 'Notifi
|
||||
Notification.update {id: notification.id},
|
||||
id: notification.id
|
||||
is_read: true
|
||||
, ->
|
||||
index = $scope.$parent.notifications.indexOf(notification)
|
||||
$scope.$parent.notifications.splice(index,1)
|
||||
$scope.notificationsRead.push notification
|
||||
, (updatedNotif) ->
|
||||
# remove notif from unreads
|
||||
index = $scope.notificationsUnread.indexOf(notification)
|
||||
$scope.notificationsUnread.splice(index,1)
|
||||
# add update notif to read
|
||||
$scope.notificationsRead.push updatedNotif
|
||||
# update counters
|
||||
$scope.$parent.notifications.unread -= 1
|
||||
$scope.totalUnread -= 1
|
||||
|
||||
|
||||
|
||||
@ -52,21 +66,32 @@ Application.Controllers.controller "NotificationsController", ["$scope", 'Notifi
|
||||
$scope.markAllAsRead = ->
|
||||
Notification.update {}
|
||||
, -> # success
|
||||
angular.forEach $scope.$parent.notifications, (n)->
|
||||
# add notifs to read
|
||||
angular.forEach $scope.notificationsUnread, (n)->
|
||||
n.is_read = true
|
||||
$scope.notificationsRead.push n
|
||||
|
||||
$scope.$parent.notifications.splice(0, $scope.$parent.notifications.length)
|
||||
# clear unread
|
||||
$scope.notificationsUnread = []
|
||||
# update counters
|
||||
$scope.$parent.notifications.unread = 0
|
||||
$scope.totalUnread = 0
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Request the server to retrieve the next undisplayed notifications and add them
|
||||
# to the archived notifications list.
|
||||
# Request the server to retrieve the next notifications and add them
|
||||
# to their corresponding notifications list (read or unread).
|
||||
##
|
||||
$scope.addMoreNotificationsReaded = ->
|
||||
Notification.query {is_read: true, page: $scope.page}, (notifications) ->
|
||||
$scope.notificationsRead = $scope.notificationsRead.concat notifications
|
||||
$scope.paginateActive = false if notifications.length < NOTIFICATIONS_PER_PAGE
|
||||
$scope.addMoreNotifications = ->
|
||||
Notification.query {page: $scope.page}, (notifications) ->
|
||||
$scope.total = notifications.totals.total
|
||||
$scope.totalUnread = notifications.totals.unread
|
||||
angular.forEach notifications.notifications, (notif) ->
|
||||
if notif.is_read
|
||||
$scope.notificationsRead.push(notif)
|
||||
else
|
||||
$scope.notificationsUnread.push(notif)
|
||||
$scope.paginateActive = (notifications.totals.total > ($scope.notificationsRead.length + $scope.notificationsUnread.length))
|
||||
|
||||
$scope.page += 1
|
||||
|
||||
@ -78,7 +103,7 @@ Application.Controllers.controller "NotificationsController", ["$scope", 'Notifi
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
$scope.addMoreNotificationsReaded()
|
||||
$scope.addMoreNotifications()
|
||||
|
||||
|
||||
|
||||
|
@ -177,11 +177,14 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
updateCartPrice = ->
|
||||
# first we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
$scope.cart.total = $scope.selectedPlan.amount
|
||||
# apply the coupon if any
|
||||
if $scope.coupon.applied
|
||||
$scope.cart.total = $scope.selectedPlan.amount
|
||||
# apply the coupon if any
|
||||
if $scope.coupon.applied
|
||||
if $scope.coupon.applied.type == 'percent_off'
|
||||
discount = $scope.cart.total * $scope.coupon.applied.percent_off / 100
|
||||
$scope.cart.total -= discount
|
||||
else if $scope.coupon.applied.type == 'amount_off'
|
||||
discount = $scope.coupon.applied.amount_off
|
||||
$scope.cart.total -= discount
|
||||
else
|
||||
$scope.reserve.amountTotal = null
|
||||
|
||||
@ -200,9 +203,9 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: $scope.ctrl.member.id}).$promise
|
||||
coupon: -> $scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'CustomAsset', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, CustomAsset, wallet, helpers, $locale, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'CustomAsset', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, CustomAsset, wallet, helpers, $filter, coupon) ->
|
||||
# User's wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Final price to pay by the user
|
||||
@ -211,9 +214,6 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
# The plan that the user is about to subscribe
|
||||
$scope.selectedPlan = selectedPlan
|
||||
|
||||
# Currency symbol or abreviation for the current locale
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
@ -249,6 +249,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.paid.plan = angular.copy($scope.selectedPlan)
|
||||
$scope.selectedPlan = null
|
||||
$scope.coupon.applied = null
|
||||
|
||||
|
||||
|
||||
@ -266,8 +267,8 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: $scope.ctrl.member.id}).$promise
|
||||
coupon: -> $scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'wallet', 'helpers', '$locale', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, wallet, helpers, $locale, $filter, coupon) ->
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'price', 'Subscription', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, selectedPlan, member, price, Subscription, wallet, helpers, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
@ -277,9 +278,6 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
# price to pay
|
||||
$scope.amount = helpers.getAmountToPay($scope.price, wallet.amount)
|
||||
|
||||
# Currency symbol or abreviation for the current locale
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
@ -329,6 +327,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
$scope.ctrl.member = null
|
||||
$scope.paid.plan = angular.copy($scope.selectedPlan)
|
||||
$scope.selectedPlan = null
|
||||
$scope.coupon.applied = null
|
||||
|
||||
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "CompleteProfileController", ["$scope", "$rootScope", "$state", "$window", "_t", "growl", "CSRF", "Auth", "Member", "settingsPromise", "activeProviderPromise", "groupsPromise", "cguFile", "memberPromise", "Session"
|
||||
, ($scope, $rootScope, $state, $window, _t, growl, CSRF, Auth, Member, settingsPromise, activeProviderPromise, groupsPromise, cguFile, memberPromise, Session) ->
|
||||
Application.Controllers.controller "CompleteProfileController", ["$scope", "$rootScope", "$state", "$window", "_t", "growl", "CSRF", "Auth", "Member", "settingsPromise", "activeProviderPromise", "groupsPromise", "cguFile", "memberPromise", "Session", "dialogs", "AuthProvider"
|
||||
, ($scope, $rootScope, $state, $window, _t, growl, CSRF, Auth, Member, settingsPromise, activeProviderPromise, groupsPromise, cguFile, memberPromise, Session, dialogs, AuthProvider) ->
|
||||
|
||||
|
||||
|
||||
@ -20,13 +20,13 @@ Application.Controllers.controller "CompleteProfileController", ["$scope", "$roo
|
||||
## name of the current fablab application (eg. "Fablab de la Casemate")
|
||||
$scope.fablabName = settingsPromise.fablab_name
|
||||
|
||||
## informations from the current SSO provider
|
||||
## information from the current SSO provider
|
||||
$scope.activeProvider = activeProviderPromise
|
||||
|
||||
## list of user's groups (student/standard/...)
|
||||
$scope.groups = groupsPromise
|
||||
|
||||
## current user, contains informations retrieved from the SSO
|
||||
## current user, contains information retrieved from the SSO
|
||||
$scope.user = memberPromise
|
||||
|
||||
## disallow the user to change his password as he connect from SSO
|
||||
@ -141,6 +141,27 @@ Application.Controllers.controller "CompleteProfileController", ["$scope", "$roo
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Ask for email confirmation and send the SSO merging token again
|
||||
# @param $event {Object} jQuery event object
|
||||
##
|
||||
$scope.resendCode = (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
dialogs.confirm
|
||||
templateUrl: '<%= asset_path "profile/resend_code_modal.html" %>'
|
||||
resolve:
|
||||
object: ->
|
||||
email: memberPromise.email
|
||||
, (email) ->
|
||||
# Request the server to send an auth-migration email to the current user
|
||||
AuthProvider.send_code {email: email}, (res) ->
|
||||
growl.info(_t('code_successfully_sent_again'))
|
||||
, (err) ->
|
||||
growl.error(err.data.error)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Disconnect and re-connect the user to the SSO to force the synchronisation of the profile's data
|
||||
##
|
||||
@ -149,7 +170,9 @@ Application.Controllers.controller "CompleteProfileController", ["$scope", "$roo
|
||||
Session.destroy()
|
||||
$rootScope.currentUser = null
|
||||
$rootScope.toCheckNotifications = false
|
||||
$scope.notifications = []
|
||||
$scope.notifications =
|
||||
total: 0
|
||||
unread: 0
|
||||
$window.location.href = activeProviderPromise.link_to_sso_connect
|
||||
|
||||
|
||||
@ -177,4 +200,4 @@ Application.Controllers.controller "CompleteProfileController", ["$scope", "$roo
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
]
|
||||
]
|
@ -12,6 +12,7 @@
|
||||
# - $scope.components = [{Component}]
|
||||
# - $scope.themes = [{Theme}]
|
||||
# - $scope.licences = [{Licence}]
|
||||
# - $scope.allowedExtensions = [{String}]
|
||||
# - $scope.submited(content)
|
||||
# - $scope.cancel()
|
||||
# - $scope.addFile()
|
||||
@ -26,7 +27,7 @@
|
||||
# - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ]
|
||||
##
|
||||
class ProjectsController
|
||||
constructor: ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, _t)->
|
||||
constructor: ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, allowedExtensions, _t)->
|
||||
|
||||
## Retrieve the list of machines from the server
|
||||
Machine.query().$promise.then (data)->
|
||||
@ -52,8 +53,12 @@ class ProjectsController
|
||||
id: d.id
|
||||
name: d.name
|
||||
|
||||
## Total number of documentation steps for the current project
|
||||
$scope.totalSteps = $scope.project.project_steps_attributes.length
|
||||
|
||||
## List of extensions allowed for CAD attachements upload
|
||||
$scope.allowedExtensions = allowedExtensions
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -119,7 +124,7 @@ class ProjectsController
|
||||
##
|
||||
$scope.addStep = ->
|
||||
$scope.totalSteps += 1
|
||||
$scope.project.project_steps_attributes.push { step_nb: $scope.totalSteps }
|
||||
$scope.project.project_steps_attributes.push { step_nb: $scope.totalSteps, project_step_images_attributes: [] }
|
||||
|
||||
|
||||
|
||||
@ -180,6 +185,26 @@ class ProjectsController
|
||||
console.error(error)
|
||||
|
||||
|
||||
##
|
||||
# This will create a single new empty entry into the project's step image list.
|
||||
##
|
||||
$scope.addProjectStepImage = (step)->
|
||||
step.project_step_images_attributes.push {}
|
||||
|
||||
|
||||
|
||||
##
|
||||
# This will remove the given image from the project's step image list.
|
||||
# @param step {Object} the project step has images
|
||||
# @param image {Object} the image to delete
|
||||
##
|
||||
$scope.deleteProjectStepImage = (step, image) ->
|
||||
index = step.project_step_images_attributes.indexOf(image)
|
||||
if image.id?
|
||||
image._destroy = true
|
||||
else
|
||||
step.project_step_images_attributes.splice(index, 1)
|
||||
|
||||
|
||||
##
|
||||
# Controller used on projects listing page
|
||||
@ -192,10 +217,25 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
# Number of projects added to the page when the user clicks on 'load more projects'
|
||||
PROJECTS_PER_PAGE = 16
|
||||
|
||||
$scope.openlabAppId = Fablab.openlabAppId
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
$scope.search = { q: ($location.$$search.q || ""), from: ($location.$$search.from || undefined), machine_id: (parseInt($location.$$search.machine_id) || undefined), component_id: (parseInt($location.$$search.component_id) || undefined), theme_id: (parseInt($location.$$search.theme_id) || undefined) }
|
||||
|
||||
## Fab-manager's instance ID in the openLab network
|
||||
$scope.openlabAppId = Fablab.openlabAppId
|
||||
|
||||
## Is openLab enabled on the instance?
|
||||
$scope.openlab =
|
||||
projectsActive: Fablab.openlabProjectsActive
|
||||
searchOverWholeNetwork: false
|
||||
|
||||
## default search parameters
|
||||
$scope.search =
|
||||
q: ($location.$$search.q || "")
|
||||
from: ($location.$$search.from || undefined)
|
||||
machine_id: (parseInt($location.$$search.machine_id) || undefined)
|
||||
component_id: (parseInt($location.$$search.component_id) || undefined)
|
||||
theme_id: (parseInt($location.$$search.theme_id) || undefined)
|
||||
|
||||
## list of projects to display
|
||||
$scope.projects = []
|
||||
@ -209,32 +249,14 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
## list of components / used for filtering
|
||||
$scope.components = componentsPromise
|
||||
|
||||
$scope.openlab = {}
|
||||
$scope.openlab.projectsActive = Fablab.openlabProjectsActive
|
||||
|
||||
if $location.$$search.whole_network is 'f'
|
||||
$scope.openlab.searchOverWholeNetwork = false
|
||||
else
|
||||
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive || false
|
||||
|
||||
normalizeProjectsAttrs = (projects)->
|
||||
projects.map((project)->
|
||||
project.project_image = project.image_url
|
||||
return project
|
||||
)
|
||||
|
||||
$scope.searchOverWholeNetworkChanged = ->
|
||||
setTimeout ->
|
||||
$scope.resetFiltersAndTriggerSearch()
|
||||
, 150
|
||||
|
||||
loadMoreCallback = (projectsPromise)->
|
||||
$scope.projects = $scope.projects.concat(projectsPromise.projects)
|
||||
updateUrlParam('page', $scope.projectsPagination.currentPage)
|
||||
|
||||
loadMoreOpenlabCallback = (projectsPromise)->
|
||||
$scope.projects = $scope.projects.concat(normalizeProjectsAttrs(projectsPromise.projects))
|
||||
updateUrlParam('page', $scope.projectsPagination.currentPage)
|
||||
|
||||
$scope.loadMore = ->
|
||||
if $scope.openlab.searchOverWholeNetwork is true
|
||||
@ -243,6 +265,7 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
$scope.projectsPagination.loadMore(search: $scope.search)
|
||||
|
||||
|
||||
|
||||
$scope.resetFiltersAndTriggerSearch = ->
|
||||
$scope.search.q = ""
|
||||
$scope.search.from = undefined
|
||||
@ -252,6 +275,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
$scope.setUrlQueryParams($scope.search)
|
||||
$scope.triggerSearch()
|
||||
|
||||
|
||||
|
||||
$scope.triggerSearch = ->
|
||||
currentPage = parseInt($location.$$search.page) || 1
|
||||
if $scope.openlab.searchOverWholeNetwork is true
|
||||
@ -273,6 +298,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
$scope.projectsPagination.totalCount = projectsPromise.meta.total
|
||||
$scope.projects = projectsPromise.projects
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to switch the user's view to the detailled project page
|
||||
# @param project {{slug:string}} The project to display
|
||||
@ -284,6 +311,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
else
|
||||
$state.go('app.public.projects_show', {id: project.slug})
|
||||
|
||||
|
||||
|
||||
##
|
||||
# function to set all url query search parameters from search object
|
||||
##
|
||||
@ -295,6 +324,21 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
updateUrlParam('component_id', search.component_id)
|
||||
updateUrlParam('machine_id', search.machine_id)
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
if $location.$$search.whole_network is 'f'
|
||||
$scope.openlab.searchOverWholeNetwork = false
|
||||
else
|
||||
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive || false
|
||||
$scope.triggerSearch()
|
||||
|
||||
|
||||
##
|
||||
# function to update url query param, little hack to turn off reloadOnSearch and re-enable it after setting the params
|
||||
# params example: 'q' , 'presse-purée'
|
||||
@ -305,9 +349,30 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
$timeout ->
|
||||
$state.current.reloadOnSearch = undefined
|
||||
|
||||
## initialization
|
||||
$scope.triggerSearch()
|
||||
|
||||
|
||||
loadMoreCallback = (projectsPromise)->
|
||||
$scope.projects = $scope.projects.concat(projectsPromise.projects)
|
||||
updateUrlParam('page', $scope.projectsPagination.currentPage)
|
||||
|
||||
|
||||
|
||||
loadMoreOpenlabCallback = (projectsPromise)->
|
||||
$scope.projects = $scope.projects.concat(normalizeProjectsAttrs(projectsPromise.projects))
|
||||
updateUrlParam('page', $scope.projectsPagination.currentPage)
|
||||
|
||||
|
||||
|
||||
normalizeProjectsAttrs = (projects)->
|
||||
projects.map((project)->
|
||||
project.project_image = project.image_url
|
||||
return project
|
||||
)
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
||||
|
||||
|
||||
@ -315,8 +380,8 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
##
|
||||
# Controller used in the project creation page
|
||||
##
|
||||
Application.Controllers.controller "NewProjectController", ["$scope", "$state", 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'Diacritics', 'dialogs', '_t'
|
||||
, ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, Diacritics, dialogs, _t) ->
|
||||
Application.Controllers.controller "NewProjectController", ["$scope", "$state", 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'Diacritics', 'dialogs', 'allowedExtensions', '_t'
|
||||
, ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, Diacritics, dialogs, allowedExtensions, _t) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
@ -333,7 +398,7 @@ Application.Controllers.controller "NewProjectController", ["$scope", "$state",
|
||||
$scope.matchingMembers = []
|
||||
|
||||
## Using the ProjectsController
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, _t)
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, allowedExtensions, _t)
|
||||
]
|
||||
|
||||
|
||||
@ -341,8 +406,8 @@ Application.Controllers.controller "NewProjectController", ["$scope", "$state",
|
||||
##
|
||||
# Controller used in the project edition page
|
||||
##
|
||||
Application.Controllers.controller "EditProjectController", ["$scope", "$state", '$stateParams', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'projectPromise', 'Diacritics', 'dialogs', '_t'
|
||||
, ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, projectPromise, Diacritics, dialogs, _t) ->
|
||||
Application.Controllers.controller "EditProjectController", ["$scope", "$state", '$stateParams', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', '$document', 'CSRF', 'projectPromise', 'Diacritics', 'dialogs', 'allowedExtensions', '_t'
|
||||
, ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, projectPromise, Diacritics, dialogs, allowedExtensions, _t) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
@ -359,7 +424,7 @@ Application.Controllers.controller "EditProjectController", ["$scope", "$state",
|
||||
name: u.full_name
|
||||
|
||||
## Using the ProjectsController
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, _t)
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, allowedExtensions, _t)
|
||||
]
|
||||
|
||||
|
||||
|
518
app/assets/javascripts/controllers/spaces.coffee.erb
Normal file
518
app/assets/javascripts/controllers/spaces.coffee.erb
Normal file
@ -0,0 +1,518 @@
|
||||
|
||||
### COMMON CODE ###
|
||||
|
||||
##
|
||||
# Provides a set of common callback methods to the $scope parameter. These methods are used
|
||||
# in the various spaces' admin controllers.
|
||||
#
|
||||
# Provides :
|
||||
# - $scope.submited(content)
|
||||
# - $scope.cancel()
|
||||
# - $scope.fileinputClass(v)
|
||||
# - $scope.addFile()
|
||||
# - $scope.deleteFile(file)
|
||||
#
|
||||
# Requires :
|
||||
# - $scope.space.space_files_attributes = []
|
||||
# - $state (Ui-Router) [ 'app.public.spaces_list' ]
|
||||
##
|
||||
class SpacesController
|
||||
constructor: ($scope, $state) ->
|
||||
##
|
||||
# For use with ngUpload (https://github.com/twilson63/ngUpload).
|
||||
# Intended to be the callback when the upload is done: any raised error will be stacked in the
|
||||
# $scope.alerts array. If everything goes fine, the user is redirected to the spaces list.
|
||||
# @param content {Object} JSON - The upload's result
|
||||
##
|
||||
$scope.submited = (content) ->
|
||||
if !content.id?
|
||||
$scope.alerts = []
|
||||
angular.forEach content, (v, k)->
|
||||
angular.forEach v, (err)->
|
||||
$scope.alerts.push
|
||||
msg: k+': '+err
|
||||
type: 'danger'
|
||||
else
|
||||
$state.go('app.public.spaces_list')
|
||||
|
||||
##
|
||||
# Changes the current user's view, redirecting him to the spaces list
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.public.spaces_list')
|
||||
|
||||
##
|
||||
# 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.
|
||||
# @param v {*} any attribute, will be tested for truthiness (see JS evaluation rules)
|
||||
##
|
||||
$scope.fileinputClass = (v)->
|
||||
if v
|
||||
'fileinput-exists'
|
||||
else
|
||||
'fileinput-new'
|
||||
|
||||
##
|
||||
# This will create a single new empty entry into the space attachements list.
|
||||
##
|
||||
$scope.addFile = ->
|
||||
$scope.space.space_files_attributes.push {}
|
||||
|
||||
##
|
||||
# This will remove the given file from the space attachements list. If the file was previously uploaded
|
||||
# to the server, it will be marked for deletion on the server. Otherwise, it will be simply truncated from
|
||||
# the attachements array.
|
||||
# @param file {Object} the file to delete
|
||||
##
|
||||
$scope.deleteFile = (file) ->
|
||||
index = $scope.space.space_files_attributes.indexOf(file)
|
||||
if file.id?
|
||||
file._destroy = true
|
||||
else
|
||||
$scope.space.space_files_attributes.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the public listing page, allowing everyone to see the list of spaces
|
||||
##
|
||||
Application.Controllers.controller 'SpacesController', ['$scope', '$state', 'spacesPromise', ($scope, $state, spacesPromise) ->
|
||||
|
||||
## Retrieve the list of spaces
|
||||
$scope.spaces = spacesPromise
|
||||
|
||||
##
|
||||
# Redirect the user to the space details page
|
||||
##
|
||||
$scope.showSpace = (space) ->
|
||||
$state.go('app.public.space_show', { id: space.slug })
|
||||
|
||||
##
|
||||
# Callback to book a reservation for the current space
|
||||
##
|
||||
$scope.reserveSpace = (space) ->
|
||||
$state.go('app.logged.space_reserve', { id: space.slug })
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the space creation page (admin)
|
||||
##
|
||||
Application.Controllers.controller 'NewSpaceController', ['$scope', '$state', 'CSRF',($scope, $state, CSRF) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/spaces/"
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = "post"
|
||||
|
||||
## default space parameters
|
||||
$scope.space =
|
||||
space_files_attributes: []
|
||||
|
||||
## Using the SpacesController
|
||||
new SpacesController($scope, $state)
|
||||
]
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the space edition page (admin)
|
||||
##
|
||||
Application.Controllers.controller 'EditSpaceController', ['$scope', '$state', '$stateParams', 'spacePromise', 'CSRF',($scope, $state, $stateParams, spacePromise, CSRF) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/spaces/" + $stateParams.id
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = "put"
|
||||
|
||||
## space to modify
|
||||
$scope.space = spacePromise
|
||||
|
||||
## Using the SpacesController
|
||||
new SpacesController($scope, $state)
|
||||
]
|
||||
|
||||
Application.Controllers.controller 'ShowSpaceController', ['$scope', '$state', 'spacePromise', '_t', 'dialogs', 'growl', ($scope, $state, spacePromise, _t, dialogs, growl) ->
|
||||
|
||||
## Details of the space witch id/slug is provided in the URL
|
||||
$scope.space = spacePromise
|
||||
|
||||
##
|
||||
# Callback to book a reservation for the current space
|
||||
# @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.reserveSpace = (event) ->
|
||||
event.preventDefault()
|
||||
$state.go('app.logged.space_reserve', { id: $scope.space.slug })
|
||||
|
||||
##
|
||||
# Callback to book a reservation for the current space
|
||||
# @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.deleteSpace = (event) ->
|
||||
event.preventDefault()
|
||||
# check the permissions
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
console.error _t('space_show.unauthorized_operation')
|
||||
else
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('space_show.confirmation_required')
|
||||
msg: _t('space_show.do_you_really_want_to_delete_this_space')
|
||||
, -> # deletion confirmed
|
||||
# delete the machine then redirect to the machines listing
|
||||
$scope.space.$delete ->
|
||||
$state.go('app.public.spaces_list')
|
||||
, (error)->
|
||||
growl.warning(_t('space_show.the_space_cant_be_deleted_because_it_is_already_reserved_by_some_users'))
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the spaces reservation agenda page.
|
||||
# This controller is very similar to the machine reservation controller with one major difference: here, there is many places
|
||||
# per slots.
|
||||
##
|
||||
|
||||
Application.Controllers.controller "ReserveSpaceController", ["$scope", '$stateParams', 'Auth', '$timeout', 'Availability', 'Member', 'availabilitySpacesPromise', 'plansPromise', 'groupsPromise', 'settingsPromise', 'spacePromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $stateParams, Auth, $timeout, Availability, Member, availabilitySpacesPromise, plansPromise, groupsPromise, settingsPromise, spacePromise, _t, uiCalendarConfig, CalendarConfig) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# Color of the selected event backgound
|
||||
SELECTED_EVENT_BG_COLOR = '#ffdd00'
|
||||
|
||||
# Slot free to be booked
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::SPACE_COLOR %>'
|
||||
|
||||
# Slot with reservation from current user
|
||||
RESERVED_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::IS_RESERVED_BY_CURRENT_USER %>'
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## bind the spaces availabilities with full-Calendar events
|
||||
$scope.eventSources = [ { events: availabilitySpacesPromise, textColor: 'black' } ]
|
||||
|
||||
## the user to deal with, ie. the current user for non-admins
|
||||
$scope.ctrl =
|
||||
member: {}
|
||||
|
||||
## list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = []
|
||||
for group in groupsPromise
|
||||
groupObj = { id: group.id, name: group.name, plans: [] }
|
||||
for plan in plansPromise
|
||||
groupObj.plans.push(plan) if plan.group_id == group.id
|
||||
$scope.plansClassifiedByGroup.push(groupObj)
|
||||
|
||||
## mapping of fullCalendar events.
|
||||
$scope.events =
|
||||
reserved: [] # Slots that the user wants to book
|
||||
modifiable: null # Slot that the user wants to change
|
||||
placable: null # Destination slot for the change
|
||||
paid: [] # Slots that were just booked by the user (transaction ok)
|
||||
moved: null # Slots that were just moved by the user (change done) -> {newSlot:* oldSlot: *}
|
||||
|
||||
## the moment when the slot selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.selectionTime = null
|
||||
|
||||
## the last clicked event in the calender
|
||||
$scope.selectedEvent = null
|
||||
|
||||
## indicates the state of the current view : calendar or plans information
|
||||
$scope.plansAreShown = false
|
||||
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## the moment when the plan selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.planSelectionTime = null
|
||||
|
||||
## Selected space
|
||||
$scope.space = spacePromise
|
||||
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig = CalendarConfig
|
||||
minTime: moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss'))
|
||||
maxTime: moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss'))
|
||||
eventClick: (event, jsEvent, view) ->
|
||||
calendarEventClickCb(event, jsEvent, view)
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element, view)
|
||||
|
||||
## Application global settings
|
||||
$scope.settings = settingsPromise
|
||||
|
||||
## Global config: message to the end user concerning the subscriptions rules
|
||||
$scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert
|
||||
|
||||
## Global config: message to the end user concerning the space reservation
|
||||
$scope.spaceExplicationsAlert = settingsPromise.space_explications_alert
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'added to cart'
|
||||
##
|
||||
$scope.markSlotAsAdded = ->
|
||||
$scope.selectedEvent.backgroundColor = SELECTED_EVENT_BG_COLOR
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'never added to cart'
|
||||
##
|
||||
$scope.markSlotAsRemoved = (slot) ->
|
||||
slot.backgroundColor = 'white'
|
||||
slot.title = ''
|
||||
slot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
slot.id = null
|
||||
slot.isValid = false
|
||||
slot.is_reserved = false
|
||||
slot.can_modify = false
|
||||
slot.offered = false
|
||||
slot.is_completed = false if slot.is_completed
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when a slot was successfully cancelled. Reset the slot style as 'ready to book'
|
||||
##
|
||||
$scope.slotCancelled = ->
|
||||
$scope.markSlotAsRemoved($scope.selectedEvent)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'currently looking for a new destination to exchange'
|
||||
##
|
||||
$scope.markSlotAsModifying = ->
|
||||
$scope.selectedEvent.backgroundColor = '#eee'
|
||||
$scope.selectedEvent.title = _t('space_reserve.i_change')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'the slot being exchanged will take this place'
|
||||
##
|
||||
$scope.changeModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.selectedEvent._id
|
||||
$scope.selectedEvent.backgroundColor = '#bbb'
|
||||
$scope.selectedEvent.title = _t('space_reserve.i_shift')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
##
|
||||
$scope.modifyTrainingSlot = ->
|
||||
$scope.events.placable.title = _t('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
|
||||
$scope.events.placable.is_reserved = true
|
||||
$scope.events.placable.can_modify = true
|
||||
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
$scope.events.modifiable.title = ''
|
||||
$scope.events.modifiable.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.events.modifiable.id = null
|
||||
$scope.events.modifiable.is_reserved = false
|
||||
$scope.events.modifiable.can_modify = false
|
||||
$scope.events.modifiable.is_completed = false if $scope.events.modifiable.is_completed
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = ''
|
||||
$scope.events.modifiable.title = _t('space_reserve.i_ve_reserved')
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to deal with the reservations of the user selected in the dropdown list instead of the current user's
|
||||
# reservations. (admins only)
|
||||
##
|
||||
$scope.updateMember = ->
|
||||
if $scope.ctrl.member
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
Availability.spaces {spaceId: $scope.space.id, member_id: $scope.ctrl.member.id}, (spaces) ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents'
|
||||
$scope.eventSources.splice(0, 1,
|
||||
events: spaces
|
||||
textColor: 'black'
|
||||
)
|
||||
# as the events are re-fetched for the new user, we must re-init the cart
|
||||
$scope.events.reserved = []
|
||||
$scope.selectedPlan = null
|
||||
$scope.plansAreShown = false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Add the provided plan to the current shopping cart
|
||||
# @param plan {Object} the plan to subscribe
|
||||
##
|
||||
$scope.selectPlan = (plan) ->
|
||||
# toggle selected plan
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Changes the user current view from the plan subsription screen to the machine reservation agenda
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.doNotSubscribePlan = (e)->
|
||||
e.preventDefault()
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
##
|
||||
$scope.showPlans = ->
|
||||
$scope.plansAreShown = true
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
$scope.afterPayment = (reservation)->
|
||||
angular.forEach $scope.events.paid, (spaceSlot, key) ->
|
||||
spaceSlot.is_reserved = true
|
||||
spaceSlot.can_modify = true
|
||||
spaceSlot.title = _t('space_reserve.i_ve_reserved')
|
||||
spaceSlot.backgroundColor = 'white'
|
||||
spaceSlot.borderColor = RESERVED_SLOT_BORDER_COLOR
|
||||
updateSpaceSlotId(spaceSlot, reservation)
|
||||
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.ctrl.member.training_credits = angular.copy(reservation.user.training_credits)
|
||||
$scope.ctrl.member.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits)
|
||||
Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
|
||||
refetchCalendar()
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
Member.get id: $scope.currentUser.id, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Triggered when the user clicks on a reservation slot in the agenda.
|
||||
# Defines the behavior to adopt depending on the slot status (already booked, free, ready to be reserved ...),
|
||||
# the user's subscription (current or about to be took) and the time (the user cannot modify a booked reservation
|
||||
# if it's too late).
|
||||
# @see http://fullcalendar.io/docs/mouse/eventClick/
|
||||
##
|
||||
calendarEventClickCb = (event, jsEvent, view) ->
|
||||
$scope.selectedEvent = event
|
||||
if $stateParams.id is 'all'
|
||||
$scope.training = event.training
|
||||
$scope.selectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Triggered when fullCalendar tries to graphicaly render an event block.
|
||||
# Append the event tag into the block, just after the event title.
|
||||
# @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
##
|
||||
eventRenderCb = (event, element, view)->
|
||||
if $scope.currentUser.role is 'admin' and event.tags.length > 0
|
||||
html = ''
|
||||
for tag in event.tags
|
||||
html += "<span class='label label-success text-white' title='#{tag.name}'>#{tag.name}</span>"
|
||||
element.find('.fc-time').append(html)
|
||||
return
|
||||
|
||||
|
||||
|
||||
##
|
||||
# After payment, update the id of the newly reserved slot with the id returned by the server.
|
||||
# This will allow the user to modify the reservation he just booked.
|
||||
# @param slot {Object}
|
||||
# @param reservation {Object}
|
||||
##
|
||||
updateSpaceSlotId = (slot, reservation)->
|
||||
angular.forEach reservation.slots, (s)->
|
||||
if slot.start_at == slot.start_at
|
||||
slot.id = s.id
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Update the calendar's display to render the new attributes of the events
|
||||
##
|
||||
updateCalendar = ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Asynchronously fetch the events from the API and refresh the calendar's view with these new events
|
||||
##
|
||||
refetchCalendar = ->
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
]
|
@ -12,13 +12,13 @@ Application.Controllers.controller "TrainingsController", ['$scope', '$state', '
|
||||
# Callback for the 'reserve' button
|
||||
##
|
||||
$scope.reserveTraining = (training, event) ->
|
||||
$state.go('app.logged.trainings_reserve', {id: training.id})
|
||||
$state.go('app.logged.trainings_reserve', {id: training.slug})
|
||||
|
||||
##
|
||||
# Callback for the 'show' button
|
||||
##
|
||||
$scope.showTraining = (training) ->
|
||||
$state.go('app.public.training_show', {id: training.id})
|
||||
$state.go('app.public.training_show', {id: training.slug})
|
||||
]
|
||||
|
||||
|
||||
@ -26,17 +26,43 @@ Application.Controllers.controller "TrainingsController", ['$scope', '$state', '
|
||||
##
|
||||
# Public view of a specific training
|
||||
##
|
||||
Application.Controllers.controller "ShowTrainingController", ['$scope', '$state', 'trainingPromise', ($scope, $state, trainingPromise) ->
|
||||
Application.Controllers.controller "ShowTrainingController", ['$scope', '$state', 'trainingPromise', 'growl', '_t', 'dialogs', ($scope, $state, trainingPromise, growl, _t, dialogs) ->
|
||||
|
||||
## Current training
|
||||
$scope.training = trainingPromise
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to delete the current training (admins only)
|
||||
##
|
||||
$scope.delete = (training) ->
|
||||
# check the permissions
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
console.error _t('unauthorized_operation')
|
||||
else
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_training')
|
||||
, -> # deletion confirmed
|
||||
# delete the training then redirect to the trainings listing
|
||||
training.$delete ->
|
||||
$state.go('app.public.trainings_list')
|
||||
, (error)->
|
||||
growl.warning(_t('the_training_cant_be_deleted_because_it_is_already_reserved_by_some_users'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback for the 'reserve' button
|
||||
##
|
||||
$scope.reserveTraining = (training, event) ->
|
||||
$state.go('app.logged.trainings_reserve', {id: training.id})
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Revert view to the full list of trainings ("<-" button)
|
||||
##
|
||||
@ -51,8 +77,8 @@ Application.Controllers.controller "ShowTrainingController", ['$scope', '$state'
|
||||
# training can be reserved during the reservation process (the shopping cart may contains only one training and a subscription).
|
||||
##
|
||||
|
||||
Application.Controllers.controller "ReserveTrainingController", ["$scope", "$state", '$stateParams', '$filter', '$compile', "$uibModal", 'Auth', 'dialogs', '$timeout', 'Price', 'Availability', 'Slot', 'Member', 'Setting', 'CustomAsset', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'growl', 'settingsPromise', 'trainingPromise', '_t', 'Wallet', 'helpers', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $state, $stateParams, $filter, $compile, $uibModal, Auth, dialogs, $timeout, Price, Availability, Slot, Member, Setting, CustomAsset, availabilityTrainingsPromise, plansPromise, groupsPromise, growl, settingsPromise, trainingPromise, _t, Wallet, helpers, uiCalendarConfig, CalendarConfig) ->
|
||||
Application.Controllers.controller "ReserveTrainingController", ["$scope", '$stateParams', 'Auth', '$timeout', 'Availability', 'Member', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'settingsPromise', 'trainingPromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
|
||||
($scope, $stateParams, Auth, $timeout, Availability, Member, availabilityTrainingsPromise, plansPromise, groupsPromise, settingsPromise, trainingPromise, _t, uiCalendarConfig, CalendarConfig) ->
|
||||
|
||||
|
||||
|
||||
@ -61,7 +87,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# Color of the selected event backgound
|
||||
SELECTED_EVENT_BG_COLOR = '#ffdd00'
|
||||
|
||||
# Slot already booked by the current user
|
||||
# Slot free to be booked
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::TRAINING_COLOR %>'
|
||||
|
||||
|
||||
@ -83,33 +109,34 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
groupObj.plans.push(plan) if plan.group_id == group.id
|
||||
$scope.plansClassifiedByGroup.push(groupObj)
|
||||
|
||||
## indicates the state of the current view : calendar or plans informations
|
||||
## mapping of fullCalendar events.
|
||||
$scope.events =
|
||||
reserved: [] # Slots that the user wants to book
|
||||
modifiable: null # Slot that the user wants to change
|
||||
placable: null # Destination slot for the change
|
||||
paid: [] # Slots that were just booked by the user (transaction ok)
|
||||
moved: null # Slots that were just moved by the user (change done) -> {newSlot:* oldSlot: *}
|
||||
|
||||
## the moment when the slot selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.selectionTime = null
|
||||
|
||||
## the last clicked event in the calender
|
||||
$scope.selectedEvent = null
|
||||
|
||||
## indicates the state of the current view : calendar or plans information
|
||||
$scope.plansAreShown = false
|
||||
|
||||
## indicates if the selected training was validated (ie. added to the shopping cart)
|
||||
$scope.trainingIsValid = false
|
||||
|
||||
## contains the selected training once it was payed, allows to display a firendly end-of-shopping message
|
||||
$scope.paidTraining = null
|
||||
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## fullCalendar event. Training slot that the user want to book
|
||||
$scope.selectedTraining = null
|
||||
## the moment when the plan selection changed for the last time, used to trigger changes in the cart
|
||||
$scope.planSelectionTime = null
|
||||
|
||||
## fullCalendar event. An already booked slot that the user want to modify
|
||||
$scope.slotToModify = null
|
||||
|
||||
## Once a training reservation was modified, will contains {newReservedSlot:{}, oldReservedSlot:{}}
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
## Selected training unless 'all' trainings are displayed
|
||||
## Selected training
|
||||
$scope.training = trainingPromise
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
## 'all' OR training's slug
|
||||
$scope.mode = $stateParams.id
|
||||
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig = CalendarConfig
|
||||
@ -117,19 +144,113 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
maxTime: moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss'))
|
||||
eventClick: (event, jsEvent, view) ->
|
||||
calendarEventClickCb(event, jsEvent, view)
|
||||
eventAfterAllRender: (view)->
|
||||
$scope.events = uiCalendarConfig.calendars.calendar.fullCalendar 'clientEvents'
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element, view)
|
||||
|
||||
## Custom settings
|
||||
## Application global settings
|
||||
$scope.settings = settingsPromise
|
||||
|
||||
## Global config: message to the end user concerning the subscriptions rules
|
||||
$scope.subscriptionExplicationsAlert = settingsPromise.subscription_explications_alert
|
||||
|
||||
## Global config: message to the end user concerning the training reservation
|
||||
$scope.trainingExplicationsAlert = settingsPromise.training_explications_alert
|
||||
|
||||
## Global config: message to the end user giving advice about the training reservation
|
||||
$scope.trainingInformationMessage = settingsPromise.training_information_message
|
||||
$scope.enableBookingMove = (settingsPromise.booking_move_enable == "true")
|
||||
$scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay)
|
||||
$scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true")
|
||||
$scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay)
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'added to cart'
|
||||
##
|
||||
$scope.markSlotAsAdded = ->
|
||||
$scope.selectedEvent.backgroundColor = SELECTED_EVENT_BG_COLOR
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'never added to cart'
|
||||
##
|
||||
$scope.markSlotAsRemoved = (slot) ->
|
||||
slot.backgroundColor = 'white'
|
||||
slot.title = slot.training.name
|
||||
slot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
slot.id = null
|
||||
slot.isValid = false
|
||||
slot.is_reserved = false
|
||||
slot.can_modify = false
|
||||
slot.offered = false
|
||||
slot.is_completed = false if slot.is_completed
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when a slot was successfully cancelled. Reset the slot style as 'ready to book'
|
||||
##
|
||||
$scope.slotCancelled = ->
|
||||
$scope.markSlotAsRemoved($scope.selectedEvent)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'currently looking for a new destination to exchange'
|
||||
##
|
||||
$scope.markSlotAsModifying = ->
|
||||
$scope.selectedEvent.backgroundColor = '#eee'
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('i_change')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the last selected slot's appearence to looks like 'the slot being exchanged will take this place'
|
||||
##
|
||||
$scope.changeModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = $scope.events.placable.training.name
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.selectedEvent._id
|
||||
$scope.selectedEvent.backgroundColor = '#bbb'
|
||||
$scope.selectedEvent.title = $scope.selectedEvent.training.name + ' - ' + _t('i_shift')
|
||||
updateCalendar()
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, callback when the modification was successfully done.
|
||||
##
|
||||
$scope.modifyTrainingSlot = ->
|
||||
$scope.events.placable.title = if $scope.currentUser.role isnt 'admin' then $scope.events.placable.training.name + " - " + _t('i_ve_reserved') else $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
|
||||
$scope.events.placable.is_reserved = true
|
||||
$scope.events.placable.can_modify = true
|
||||
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
$scope.events.modifiable.title = $scope.events.modifiable.training.name
|
||||
$scope.events.modifiable.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.events.modifiable.id = null
|
||||
$scope.events.modifiable.is_reserved = false
|
||||
$scope.events.modifiable.can_modify = false
|
||||
$scope.events.modifiable.is_completed = false if $scope.events.modifiable.is_completed
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyTrainingSlot = ->
|
||||
if $scope.events.placable
|
||||
$scope.events.placable.backgroundColor = 'white'
|
||||
$scope.events.placable.title = $scope.events.placable.training.name
|
||||
$scope.events.modifiable.title = if $scope.currentUser.role isnt 'admin' then $scope.events.modifiable.training.name + " - " + _t('i_ve_reserved') else $scope.events.modifiable.training.name
|
||||
$scope.events.modifiable.backgroundColor = 'white'
|
||||
|
||||
updateCalendar()
|
||||
|
||||
|
||||
|
||||
@ -141,67 +262,17 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
if $scope.ctrl.member
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
Availability.trainings {trainingId: $stateParams.id, member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
id = if $stateParams.id is 'all' then $stateParams.id else $scope.training.id
|
||||
Availability.trainings {trainingId: id, member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents'
|
||||
$scope.eventSources.push
|
||||
$scope.eventSources.splice(0, 1,
|
||||
events: trainings
|
||||
textColor: 'black'
|
||||
$scope.trainingIsValid = false
|
||||
$scope.paidTraining = null
|
||||
$scope.plansAreShown = false
|
||||
)
|
||||
# as the events are re-fetched for the new user, we must re-init the cart
|
||||
$scope.events.reserved = []
|
||||
$scope.selectedPlan = null
|
||||
$scope.selectedTraining = null
|
||||
$scope.slotToModify = null
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to mark the selected training as validated (add it to the shopping cart).
|
||||
##
|
||||
$scope.validTraining = ->
|
||||
$scope.trainingIsValid = true
|
||||
$scope.updatePrices()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Remove the training from the shopping cart
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeTraining = (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$scope.selectedTraining.backgroundColor = 'white'
|
||||
$scope.selectedTraining = null
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.trainingIsValid = false
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Validates the shopping chart and redirect the user to the payment step
|
||||
##
|
||||
$scope.payTraining = ->
|
||||
|
||||
# first, we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
reservation = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan)
|
||||
|
||||
Wallet.getWalletByUser {user_id: $scope.ctrl.member.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount)
|
||||
if $scope.currentUser.role isnt 'admin' and amountToPay > 0
|
||||
payByStripe(reservation)
|
||||
else
|
||||
if $scope.currentUser.role is 'admin' or amountToPay is 0
|
||||
payOnSite(reservation)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.error(_t('please_select_a_member_first'))
|
||||
|
||||
|
||||
|
||||
@ -210,17 +281,12 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# @param plan {Object} the plan to subscribe
|
||||
##
|
||||
$scope.selectPlan = (plan) ->
|
||||
if $scope.isAuthenticated()
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
$scope.updatePrices()
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
$scope.updatePrices()
|
||||
# toggle selected plan
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
else
|
||||
$scope.login null, ->
|
||||
$scope.selectedPlan = plan
|
||||
$scope.updatePrices()
|
||||
$scope.selectedPlan = null
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
@ -232,7 +298,9 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
e.preventDefault()
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.updatePrices()
|
||||
$scope.planSelectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
@ -240,95 +308,33 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.showPlans = ->
|
||||
$scope.plansAreShown = true
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, removing the previously booked slot from the selection
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeSlotToModify = (e) ->
|
||||
e.preventDefault()
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToModify.training.name + " - " + _t('i_ve_reserved') else $scope.slotToModify.training.name
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, cancel the choice of the new slot
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
$scope.removeSlotToPlace = (e)->
|
||||
e.preventDefault()
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
$scope.afterPayment = (reservation)->
|
||||
$scope.events.paid[0].backgroundColor = 'white'
|
||||
$scope.events.paid[0].is_reserved = true
|
||||
$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')
|
||||
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.ctrl.member.training_credits = angular.copy(reservation.user.training_credits)
|
||||
$scope.ctrl.member.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits)
|
||||
Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, confirm the modification.
|
||||
##
|
||||
$scope.modifyTrainingSlot = ->
|
||||
Slot.update {id: $scope.slotToModify.slot_id},
|
||||
slot:
|
||||
start_at: $scope.slotToPlace.start
|
||||
end_at: $scope.slotToPlace.end
|
||||
availability_id: $scope.slotToPlace.id
|
||||
, -> # success
|
||||
$scope.modifiedSlots =
|
||||
newReservedSlot: $scope.slotToPlace
|
||||
oldReservedSlot: $scope.slotToModify
|
||||
$scope.slotToPlace.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToPlace.training.name + " - " + _t('i_ve_reserved') else $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.borderColor = $scope.slotToModify.borderColor
|
||||
$scope.slotToPlace.slot_id = $scope.slotToModify.slot_id
|
||||
$scope.slotToPlace.is_reserved = true
|
||||
$scope.slotToPlace.can_modify = true
|
||||
$scope.slotToPlace = null
|
||||
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify.title = $scope.slotToModify.training.name
|
||||
$scope.slotToModify.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.slotToModify.slot_id = null
|
||||
$scope.slotToModify.is_reserved = false
|
||||
$scope.slotToModify.can_modify = false
|
||||
$scope.slotToModify.is_completed = false if $scope.slotToModify.is_completed
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # failure
|
||||
growl.error('an_error_occured_preventing_the_booked_slot_from_being_modified')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
##
|
||||
$scope.cancelModifyMachineSlot = ->
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace = null
|
||||
$scope.slotToModify.title = if $scope.currentUser.role isnt 'admin' then $scope.slotToModify.training.name + " - " + _t('i_ve_reserved') else $scope.slotToModify.training.name
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Update the prices, based on the current selection
|
||||
##
|
||||
$scope.updatePrices = ->
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan)
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
else
|
||||
$scope.amountTotal = null
|
||||
refetchCalendar()
|
||||
|
||||
|
||||
|
||||
@ -342,51 +348,6 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
Member.get id: $scope.currentUser.id, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
|
||||
# watch when a coupon is applied to re-compute the total price
|
||||
$scope.$watch 'coupon.applied', (newValue, oldValue) ->
|
||||
unless newValue == null and oldValue == null
|
||||
$scope.updatePrices()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create an hash map implementing the Reservation specs
|
||||
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
|
||||
# @param training {Object} fullCalendar event: training slot selected on the calendar
|
||||
# @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation
|
||||
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||
##
|
||||
mkReservation = (member, training, plan = null) ->
|
||||
reservation =
|
||||
user_id: member.id
|
||||
reservable_id: training.training.id
|
||||
reservable_type: 'Training'
|
||||
slots_attributes: []
|
||||
plan_id: (plan.id if plan)
|
||||
|
||||
reservation.slots_attributes.push
|
||||
start_at: training.start
|
||||
end_at: training.end
|
||||
availability_id: training.id
|
||||
offered: training.offered || false
|
||||
|
||||
reservation
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
|
||||
# @param reservation {Object} as returned by mkReservation()
|
||||
# @param coupon {Object} Coupon as returned from the API
|
||||
# @return {{reservation:Object, coupon_code:string}}
|
||||
##
|
||||
mkRequestParams = (reservation, coupon) ->
|
||||
params =
|
||||
reservation: reservation
|
||||
coupon_code: (coupon.code if coupon)
|
||||
|
||||
params
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -397,310 +358,26 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# @see http://fullcalendar.io/docs/mouse/eventClick/
|
||||
##
|
||||
calendarEventClickCb = (event, jsEvent, view) ->
|
||||
if $scope.ctrl.member
|
||||
# reserve a training if this training will not be reserved and is not about to move and not is completed
|
||||
if !event.is_reserved && !$scope.slotToModify && !event.is_completed
|
||||
$scope.coupon.applied = null
|
||||
if event != $scope.selectedTraining
|
||||
$scope.selectedTraining = event
|
||||
$scope.selectedTraining.offered = false
|
||||
event.backgroundColor = SELECTED_EVENT_BG_COLOR
|
||||
computeTrainingAmount($scope.selectedTraining)
|
||||
else
|
||||
$scope.selectedTraining = null
|
||||
event.backgroundColor = 'white'
|
||||
$scope.trainingIsValid = false
|
||||
$scope.paidTraining = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
# clean all others events background
|
||||
angular.forEach $scope.events, (e)->
|
||||
if event.id != e.id
|
||||
e.backgroundColor = 'white'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
# two if below for move training reserved
|
||||
# if training isnt reserved and have a training to modify and same training and not complete
|
||||
else if !event.is_reserved && $scope.slotToModify && slotCanBePlaced(event)
|
||||
if $scope.slotToPlace
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = event.training.name
|
||||
$scope.slotToPlace = event
|
||||
event.backgroundColor = '#bbb'
|
||||
event.title = event.training.name + ' - ' + _t('i_shift')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
# if training reserved can modify
|
||||
else if event.is_reserved and (slotCanBeModified(event) or slotCanBeCanceled(event)) and !$scope.slotToModify and !$scope.selectedTraining
|
||||
event.movable = slotCanBeModified(event)
|
||||
event.cancelable = slotCanBeCanceled(event)
|
||||
if $scope.currentUser.role is 'admin'
|
||||
event.user =
|
||||
name: $scope.ctrl.member.name
|
||||
dialogs.confirm
|
||||
templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>'
|
||||
resolve:
|
||||
object: -> event
|
||||
, (type) -> # success
|
||||
if type == 'move'
|
||||
$scope.modifiedSlots = null
|
||||
$scope.slotToModify = event
|
||||
event.backgroundColor = '#eee'
|
||||
event.title = event.training.name + ' - ' + _t('i_change')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_cancel_this_reservation')
|
||||
, -> # cancel confirmed
|
||||
Slot.cancel {id: event.slot_id}, -> # successfully canceled
|
||||
growl.success _t('reservation_was_successfully_cancelled')
|
||||
$scope.canceledSlot = event
|
||||
$scope.canceledSlot.backgroundColor = 'white'
|
||||
$scope.canceledSlot.title = event.training.name
|
||||
$scope.canceledSlot.borderColor = FREE_SLOT_BORDER_COLOR
|
||||
$scope.canceledSlot.slot_id = null
|
||||
$scope.canceledSlot.is_reserved = false
|
||||
$scope.canceledSlot.can_modify = false
|
||||
$scope.canceledSlot.is_completed = false if event.is_completed
|
||||
$scope.canceledSlot = null
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cancellation_failed')
|
||||
, -> # canceled
|
||||
$scope.paidMachineSlots = null
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
$scope.selectedEvent = event
|
||||
if $stateParams.id is 'all'
|
||||
$scope.training = event.training
|
||||
$scope.selectionTime = new Date()
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When events are rendered, adds attributes for popover and compile
|
||||
# Triggered when fullCalendar tries to graphicaly render an event block.
|
||||
# Append the event tag into the block, just after the event title.
|
||||
# @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
##
|
||||
eventRenderCb = (event, element, view)->
|
||||
# Comment these codes for show a popup of description, because we add feature page of training
|
||||
#element.attr(
|
||||
# 'uib-popover': $filter('humanize')($filter('simpleText')(event.training.description), 70)
|
||||
# 'popover-trigger': 'mouseenter'
|
||||
#)
|
||||
#$compile(element)($scope)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
##
|
||||
payByStripe = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'wallet', 'cgv', 'Auth', 'Reservation', '$locale', 'helpers', '$filter', 'coupon'
|
||||
($scope, $uibModalInstance, $state, reservation, price, wallet, cgv, Auth, Reservation, $locale, helpers, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
##
|
||||
# Callback to process the payment with Stripe, triggered on button click
|
||||
##
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
growl.error(response.error.message)
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
if response.data.card
|
||||
$scope.alerts.push
|
||||
msg: response.data.card[0]
|
||||
type: 'danger'
|
||||
else
|
||||
$scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
##
|
||||
payOnSite = (reservation) ->
|
||||
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>'
|
||||
size: 'sm'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', '$filter', 'reservation', 'price', 'wallet', 'Auth', 'Reservation', '$locale', 'helpers', 'coupon'
|
||||
($scope, $uibModalInstance, $state, $filter, reservation, price, wallet, Auth, Reservation, $locale, helpers, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.price = price.price
|
||||
|
||||
# price to pay
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Button label
|
||||
if $scope.amount > 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')($scope.amount)}, "messageformat")
|
||||
else
|
||||
if price.price > 0 and $scope.walletAmount == 0
|
||||
$scope.validButtonName = _t('confirm_payment_of_html', {ROLE:$scope.currentUser.role, AMOUNT:$filter('currency')(price.price)}, "messageformat")
|
||||
else
|
||||
$scope.validButtonName = _t('confirm')
|
||||
|
||||
##
|
||||
# Callback to process the local payment, triggered on button click
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push({msg: _t('a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Computes the training amount depending of the member's credit
|
||||
# @param training {Object} training slot
|
||||
##
|
||||
computeTrainingAmount = (training)->
|
||||
# first we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, training) # reservation without any Plan -> we get the training price
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.selectedTrainingAmount = res.price
|
||||
else
|
||||
$scope.selectedTrainingAmount = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Once the reservation is booked (payment process successfully completed), change the event style
|
||||
# in fullCalendar, update the user's subscription and free-credits if needed
|
||||
# @param reservation {Object}
|
||||
##
|
||||
afterPayment = (reservation)->
|
||||
$scope.paidTraining = $scope.selectedTraining
|
||||
$scope.paidTraining.backgroundColor = 'white'
|
||||
$scope.paidTraining.is_reserved = true
|
||||
$scope.paidTraining.can_modify = true
|
||||
updateTrainingSlotId($scope.paidTraining, reservation)
|
||||
$scope.paidTraining.borderColor = '#b2e774'
|
||||
$scope.paidTraining.title = $scope.paidTraining.training.name + " - " + _t('i_ve_reserved')
|
||||
|
||||
$scope.selectedTraining = null
|
||||
$scope.trainingIsValid = false
|
||||
|
||||
if $scope.selectedPlan
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
$scope.selectedPlan = null
|
||||
$scope.ctrl.member.training_credits = angular.copy(reservation.user.training_credits)
|
||||
$scope.ctrl.member.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits)
|
||||
Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be modified by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeModified = (slot)->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment(new Date())
|
||||
if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be canceled by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeCanceled = (slot) ->
|
||||
return true if $scope.currentUser.role is 'admin'
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# For booking modifications, checks that the newly selected slot is valid
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBePlaced = (slot)->
|
||||
if slot.training.id == $scope.slotToModify.training.id and !slot.is_completed
|
||||
return true
|
||||
else
|
||||
return false
|
||||
if $scope.currentUser.role is 'admin' and event.tags.length > 0
|
||||
html = ''
|
||||
for tag in event.tags
|
||||
html += "<span class='label label-success text-white' title='#{tag.name}'>#{tag.name}</span>"
|
||||
element.find('.fc-time').append(html)
|
||||
return
|
||||
|
||||
|
||||
|
||||
@ -713,11 +390,29 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
updateTrainingSlotId = (slot, reservation)->
|
||||
angular.forEach reservation.slots, (s)->
|
||||
if slot.start_at == slot.start_at
|
||||
slot.slot_id = s.id
|
||||
slot.id = s.id
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
##
|
||||
# Update the calendar's display to render the new attributes of the events
|
||||
##
|
||||
updateCalendar = ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Asynchronously fetch the events from the API and refresh the calendar's view with these new events
|
||||
##
|
||||
refetchCalendar = ->
|
||||
$timeout ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
|
||||
]
|
||||
|
569
app/assets/javascripts/directives/cart.coffee.erb
Normal file
569
app/assets/javascripts/directives/cart.coffee.erb
Normal file
@ -0,0 +1,569 @@
|
||||
Application.Directives.directive 'cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'helpers', '_t'
|
||||
, ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, helpers, _t) ->
|
||||
{
|
||||
restrict: 'E'
|
||||
scope:
|
||||
slot: '='
|
||||
slotSelectionTime: '='
|
||||
events: '='
|
||||
user: '='
|
||||
modePlans: '='
|
||||
plan: '='
|
||||
planSelectionTime: '='
|
||||
settings: '='
|
||||
onSlotAddedToCart: '='
|
||||
onSlotRemovedFromCart: '='
|
||||
onSlotStartToModify: '='
|
||||
onSlotModifyDestination: '='
|
||||
onSlotModifySuccess: '='
|
||||
onSlotModifyCancel: '='
|
||||
onSlotModifyUnselect: '='
|
||||
onSlotCancelSuccess: '='
|
||||
afterPayment: '='
|
||||
reservableId: '@'
|
||||
reservableType: '@'
|
||||
reservableName: '@'
|
||||
limitToOneSlot: '@'
|
||||
templateUrl: '<%= asset_path "shared/_cart.html" %>'
|
||||
link: ($scope, element, attributes) ->
|
||||
## will store the user's plan if he choosed to buy one
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## total amount of the bill to pay
|
||||
$scope.amountTotal = 0
|
||||
|
||||
## total amount of the elements in the cart, without considering any coupon
|
||||
$scope.totalNoCoupon = 0
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
|
||||
## Global config: is the user authorized to change his bookings slots?
|
||||
$scope.enableBookingMove = ($scope.settings.booking_move_enable == "true")
|
||||
|
||||
## Global config: delay in hours before a booking while changing the booking slot is forbidden
|
||||
$scope.moveBookingDelay = parseInt($scope.settings.booking_move_delay)
|
||||
|
||||
## Global config: is the user authorized to cancel his bookings?
|
||||
$scope.enableBookingCancel = ($scope.settings.booking_cancel_enable == "true")
|
||||
|
||||
## Global config: delay in hours before a booking while the cancellation is forbidden
|
||||
$scope.cancelBookingDelay = parseInt($scope.settings.booking_cancel_delay)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Add the provided slot to the shopping cart (state transition from free to 'about to be reserved')
|
||||
# and increment the total amount of the cart if needed.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
$scope.validateSlot = (slot)->
|
||||
slot.isValid = true
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Remove the provided slot from the shopping cart (state transition from 'about to be reserved' to free)
|
||||
# and decrement the total amount of the cart if needed.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
# @param index {number} index of the slot in the reservation array
|
||||
# @param [event] {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeSlot = (slot, index, event)->
|
||||
event.preventDefault() if event
|
||||
$scope.events.reserved.splice(index, 1)
|
||||
# if is was the last slot, we remove any plan from the cart
|
||||
if $scope.events.reserved.length == 0
|
||||
$scope.selectedPlan = null
|
||||
$scope.plan = null
|
||||
$scope.modePlans = false
|
||||
$scope.onSlotRemovedFromCart(slot) if typeof $scope.onSlotRemovedFromCart == 'function'
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks that every selected slots were added to the shopping cart. Ie. will return false if
|
||||
# any checked slot was not validated by the user.
|
||||
##
|
||||
$scope.isSlotsValid = ->
|
||||
isValid = true
|
||||
angular.forEach $scope.events.reserved, (m)->
|
||||
isValid = false if !m.isValid
|
||||
isValid
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Switch the user's view from the reservation agenda to the plan subscription
|
||||
##
|
||||
$scope.showPlans = ->
|
||||
# first, we ensure that a user was selected (admin) or logged (member)
|
||||
if Object.keys($scope.user).length > 0
|
||||
$scope.modePlans = true
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user hasn't the admin role
|
||||
growl.error(_t('cart.please_select_a_member_first'))
|
||||
|
||||
|
||||
##
|
||||
# Validates the shopping chart and redirect the user to the payment step
|
||||
##
|
||||
$scope.payCart = ->
|
||||
# first, we check that a user was selected
|
||||
if Object.keys($scope.user).length > 0
|
||||
reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan)
|
||||
|
||||
Wallet.getWalletByUser {user_id: $scope.user.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount)
|
||||
if not $scope.isAdmin() and amountToPay > 0
|
||||
payByStripe(reservation)
|
||||
else
|
||||
if $scope.isAdmin() or amountToPay is 0
|
||||
payOnSite(reservation)
|
||||
else
|
||||
# otherwise we alert, this error musn't occur when the current user is not admin
|
||||
growl.error(_t('cart.please_select_a_member_first'))
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, confirm the modification.
|
||||
##
|
||||
$scope.modifySlot = ->
|
||||
Slot.update {id: $scope.events.modifiable.id},
|
||||
slot:
|
||||
start_at: $scope.events.placable.start
|
||||
end_at: $scope.events.placable.end
|
||||
availability_id: $scope.events.placable.availability_id
|
||||
, -> # success
|
||||
# -> run the callback
|
||||
$scope.onSlotModifySuccess() if typeof $scope.onSlotModifySuccess == 'function'
|
||||
# -> set the events as successfully moved (to display a summary)
|
||||
$scope.events.moved =
|
||||
newSlot: $scope.events.placable
|
||||
oldSlot: $scope.events.modifiable
|
||||
# -> reset the 'moving' status
|
||||
$scope.events.placable = null
|
||||
$scope.events.modifiable = null
|
||||
, (err) -> # failure
|
||||
growl.error(_t('cart.unable_to_change_the_reservation'))
|
||||
console.error(err)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Cancel the current booking modification, reseting the whole process
|
||||
# @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.cancelModifySlot = (event) ->
|
||||
event.preventDefault() if event
|
||||
$scope.onSlotModifyCancel() if typeof $scope.onSlotModifyCancel == 'function'
|
||||
$scope.events.placable = null
|
||||
$scope.events.modifiable = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# When modifying an already booked reservation, cancel the choice of the new slot
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.removeSlotToPlace = (e)->
|
||||
e.preventDefault()
|
||||
$scope.onSlotModifyUnselect() if typeof $scope.onSlotModifyUnselect == 'function'
|
||||
$scope.events.placable = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks if $scope.events.modifiable and $scope.events.placable have tag incompatibilities
|
||||
# @returns {boolean} true in case of incompatibility
|
||||
##
|
||||
$scope.tagMissmatch = ->
|
||||
return false if $scope.events.placable.tag_ids.length == 0
|
||||
for tag in $scope.events.modifiable.tags
|
||||
if tag.id not in $scope.events.placable.tag_ids
|
||||
return true
|
||||
false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Check if the currently logged user has teh 'admin' role?
|
||||
# @returns {boolean}
|
||||
##
|
||||
$scope.isAdmin = ->
|
||||
$rootScope.currentUser and $rootScope.currentUser.role is 'admin'
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the directive is loaded
|
||||
##
|
||||
initialize = ->
|
||||
# What the binded slot
|
||||
$scope.$watch 'slotSelectionTime', (newValue, oldValue) ->
|
||||
if newValue != oldValue
|
||||
slotSelectionChanged()
|
||||
$scope.$watch 'user', (newValue, oldValue) ->
|
||||
if newValue != oldValue
|
||||
resetCartState()
|
||||
updateCartPrice()
|
||||
$scope.$watch 'planSelectionTime', (newValue, oldValue) ->
|
||||
if newValue != oldValue
|
||||
planSelectionChanged()
|
||||
# watch when a coupon is applied to re-compute the total price
|
||||
$scope.$watch 'coupon.applied', (newValue, oldValue) ->
|
||||
unless newValue == null and oldValue == null
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback triggered when the selected slot changed
|
||||
##
|
||||
slotSelectionChanged = ->
|
||||
if $scope.slot
|
||||
if not $scope.slot.is_reserved and not $scope.events.modifiable and not $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
|
||||
index = $scope.events.reserved.indexOf($scope.slot)
|
||||
if index == -1
|
||||
if $scope.limitToOneSlot is 'true' and $scope.events.reserved[0]
|
||||
# if we limit the number of slots in the cart to 1, and there is already
|
||||
# a slot in the cart, we remove it before adding the new one
|
||||
$scope.removeSlot($scope.events.reserved[0], 0)
|
||||
# slot is not in the cart, so we add it
|
||||
$scope.events.reserved.push $scope.slot
|
||||
$scope.onSlotAddedToCart() if typeof $scope.onSlotAddedToCart == 'function'
|
||||
else
|
||||
# slot is in the cart, remove it
|
||||
$scope.removeSlot($scope.slot, index)
|
||||
# in every cases, because a new reservation has started, we reset the cart content
|
||||
resetCartState()
|
||||
# finally, we update the prices
|
||||
updateCartPrice()
|
||||
else if !$scope.slot.is_reserved and !$scope.slot.is_completed and $scope.events.modifiable
|
||||
# slot is not reserved but we are currently modifying a slot
|
||||
# -> we request the calender to change the rendering
|
||||
$scope.onSlotModifyUnselect() if typeof $scope.onSlotModifyUnselect == 'function'
|
||||
# -> then, we re-affect the destination slot
|
||||
if !$scope.events.placable or $scope.events.placable._id != $scope.slot._id
|
||||
$scope.events.placable = $scope.slot
|
||||
else
|
||||
$scope.events.placable = null
|
||||
else if $scope.slot.is_reserved and $scope.events.modifiable and $scope.slot.is_reserved._id == $scope.events.modifiable._id
|
||||
# slot is reserved and currently modified
|
||||
# -> we cancel the modification
|
||||
$scope.cancelModifySlot()
|
||||
else if $scope.slot.is_reserved and (slotCanBeModified($scope.slot) or slotCanBeCanceled($scope.slot)) and !$scope.events.modifiable and $scope.events.reserved.length == 0
|
||||
# slot is reserved and is ok to be modified or cancelled
|
||||
# but we are not currently running a modification or having any slots in the cart
|
||||
# -> first the affect the modification/cancellation rights attributes to the current slot
|
||||
resetCartState()
|
||||
$scope.slot.movable = slotCanBeModified($scope.slot)
|
||||
$scope.slot.cancelable = slotCanBeCanceled($scope.slot)
|
||||
# -> then, we open a dialog to ask to the user to choose an action
|
||||
dialogs.confirm
|
||||
templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>'
|
||||
resolve:
|
||||
object: -> $scope.slot
|
||||
, (type) ->
|
||||
# the user has choosen an action, so we proceed
|
||||
if type == 'move'
|
||||
$scope.onSlotStartToModify() if typeof $scope.onSlotStartToModify == 'function'
|
||||
$scope.events.modifiable = $scope.slot
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('cart.confirmation_required')
|
||||
msg: _t('cart.do_you_really_want_to_cancel_this_reservation')
|
||||
, -> # cancel confirmed
|
||||
Slot.cancel {id: $scope.slot.id}, -> # successfully canceled
|
||||
growl.success _t('cart.reservation_was_cancelled_successfully')
|
||||
$scope.onSlotCancelSuccess() if typeof $scope.onSlotCancelSuccess == 'function'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cart.cancellation_failed')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Reset the parameters that may lead to a wrong price but leave the content (events added to cart)
|
||||
##
|
||||
resetCartState = ->
|
||||
$scope.selectedPlan = null
|
||||
$scope.coupon.applied = null
|
||||
$scope.events.moved = null
|
||||
$scope.events.paid = []
|
||||
$scope.events.modifiable = null
|
||||
$scope.events.placable = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be modified by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeModified = (slot)->
|
||||
return true if $scope.isAdmin()
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Determines if the provided booked slot is able to be canceled by the user.
|
||||
# @param slot {Object} fullCalendar event object
|
||||
##
|
||||
slotCanBeCanceled = (slot) ->
|
||||
return true if $scope.isAdmin()
|
||||
slotStart = moment(slot.start)
|
||||
now = moment()
|
||||
if slot.can_modify and $scope.enableBookingCancel and slotStart.diff(now, "hours") >= $scope.cancelBookingDelay
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback triggered when the selected slot changed
|
||||
##
|
||||
planSelectionChanged = ->
|
||||
if Auth.isAuthenticated()
|
||||
if $scope.selectedPlan != $scope.plan
|
||||
$scope.selectedPlan = $scope.plan
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
updateCartPrice()
|
||||
else
|
||||
$rootScope.login null, ->
|
||||
$scope.selectedPlan = $scope.plan
|
||||
updateCartPrice()
|
||||
|
||||
|
||||
##
|
||||
# Update the total price of the current selection/reservation
|
||||
##
|
||||
updateCartPrice = ->
|
||||
if Object.keys($scope.user).length > 0
|
||||
r = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan)
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
$scope.totalNoCoupon = res.price_without_coupon
|
||||
setSlotsDetails(res.details)
|
||||
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'))
|
||||
$scope.amountTotal = null
|
||||
|
||||
|
||||
setSlotsDetails = (details) ->
|
||||
angular.forEach $scope.events.reserved, (slot) ->
|
||||
angular.forEach details.slots, (s) ->
|
||||
if moment(s.start_at).isSame(slot.start)
|
||||
slot.promo = s.promo
|
||||
slot.price = s.price
|
||||
|
||||
|
||||
##
|
||||
# Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
|
||||
# @param reservation {Object} as returned by mkReservation()
|
||||
# @param coupon {Object} Coupon as returned from the API
|
||||
# @return {{reservation:Object, coupon_code:string}}
|
||||
##
|
||||
mkRequestParams = (reservation, coupon) ->
|
||||
params =
|
||||
reservation: reservation
|
||||
coupon_code: (coupon.code if coupon)
|
||||
|
||||
params
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create an hash map implementing the Reservation specs
|
||||
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
|
||||
# @param slots {Array<Object>} Array of fullCalendar events: slots selected on the calendar
|
||||
# @param [plan] {Object} Plan as retrived from the API: plan to buy with the current reservation
|
||||
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||
##
|
||||
mkReservation = (member, slots, plan = null) ->
|
||||
reservation =
|
||||
user_id: member.id
|
||||
reservable_id: $scope.reservableId
|
||||
reservable_type: $scope.reservableType
|
||||
slots_attributes: []
|
||||
plan_id: (plan.id if plan)
|
||||
angular.forEach slots, (slot, key) ->
|
||||
reservation.slots_attributes.push
|
||||
start_at: slot.start
|
||||
end_at: slot.end
|
||||
availability_id: slot.availability_id
|
||||
offered: slot.offered || false
|
||||
|
||||
reservation
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
##
|
||||
payByStripe = (reservation) ->
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
##
|
||||
# Callback to process the payment with Stripe, triggered on button click
|
||||
##
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
growl.error(response.error.message)
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
if response.status == 500
|
||||
$scope.alerts.push
|
||||
msg: response.statusText
|
||||
type: 'danger'
|
||||
else
|
||||
if response.data.card and response.data.card.join('').length > 0
|
||||
$scope.alerts.push
|
||||
msg: response.data.card.join('. ')
|
||||
type: 'danger'
|
||||
else if response.data.payment and response.data.payment.join('').length > 0
|
||||
$scope.alerts.push
|
||||
msg: response.data.payment.join('. ')
|
||||
type: 'danger'
|
||||
$scope.attempting = false
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
##
|
||||
payOnSite = (reservation) ->
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>'
|
||||
size: 'sm'
|
||||
resolve:
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
coupon: ->
|
||||
$scope.coupon.applied
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) ->
|
||||
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Global price (total of all items)
|
||||
$scope.price = price.price
|
||||
|
||||
# Price to pay (wallet deducted)
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
# Used in wallet info template to interpolate some translations
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Button label
|
||||
if $scope.amount > 0
|
||||
$scope.validButtonName = _t('cart.confirm_payment_of_html', {ROLE:$rootScope.currentUser.role, AMOUNT:$filter('currency')($scope.amount)}, "messageformat")
|
||||
else
|
||||
if price.price > 0 and $scope.walletAmount == 0
|
||||
$scope.validButtonName = _t('cart.confirm_payment_of_html', {ROLE:$rootScope.currentUser.role, AMOUNT:$filter('currency')(price.price)}, "messageformat")
|
||||
else
|
||||
$scope.validButtonName = _t('confirm')
|
||||
|
||||
##
|
||||
# Callback to process the local payment, triggered on button click
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push({msg: _t('cart.a_problem_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
afterPayment(reservation)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Actions to run after the payment was successfull
|
||||
##
|
||||
afterPayment = (reservation) ->
|
||||
# we set the cart content as 'paid' to display a summary of the transaction
|
||||
$scope.events.paid = $scope.events.reserved
|
||||
# we call the external callback if present
|
||||
$scope.afterPayment(reservation) if typeof $scope.afterPayment == 'function'
|
||||
# we reset the coupon and the cart content and we unselect the slot
|
||||
$scope.events.reserved = []
|
||||
$scope.coupon.applied = null
|
||||
$scope.slot = null
|
||||
$scope.selectedPlan = null
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the directive
|
||||
initialize()
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, growl, _t) ->
|
||||
Application.Directives.directive 'coupon', [ '$rootScope', 'Coupon', '_t', ($rootScope, Coupon, _t) ->
|
||||
{
|
||||
restrict: 'E'
|
||||
scope:
|
||||
show: '='
|
||||
coupon: '='
|
||||
total: '='
|
||||
userId: '@'
|
||||
hasSelectSlot: '='
|
||||
templateUrl: '<%= asset_path "shared/_coupon.html" %>'
|
||||
link: ($scope, element, attributes) ->
|
||||
|
||||
@ -16,32 +16,43 @@ Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, g
|
||||
# Available status are: 'pending', 'valid', 'invalid'
|
||||
$scope.status = 'pending'
|
||||
|
||||
# Binding for the code inputed
|
||||
# Binding for the code inputed (see the attached template)
|
||||
$scope.couponCode = null
|
||||
|
||||
$scope.$watch 'hasSelectSlot', (newValue) ->
|
||||
unless newValue
|
||||
$scope.coupon = null
|
||||
$scope.couponCode = null
|
||||
$scope.code.input = false
|
||||
# Code validation messages
|
||||
$scope.messages = []
|
||||
|
||||
# Re-compute if the code can be applied when the total of the cart changes
|
||||
$scope.$watch 'total', (newValue, oldValue) ->
|
||||
if newValue and newValue != oldValue and $scope.couponCode
|
||||
$scope.validateCode()
|
||||
|
||||
##
|
||||
# Callback to validate the code
|
||||
##
|
||||
$scope.validateCode = ->
|
||||
$scope.messages = []
|
||||
if $scope.couponCode == ''
|
||||
$scope.status = 'pending'
|
||||
$scope.coupon = null
|
||||
else
|
||||
Coupon.validate {code: $scope.couponCode, user_id: $scope.userId}, (res) ->
|
||||
Coupon.validate {code: $scope.couponCode, user_id: $scope.userId, amount: $scope.total}, (res) ->
|
||||
$scope.status = 'valid'
|
||||
$scope.coupon = res
|
||||
growl.success(_t('the_coupon_has_been_applied_you_get_PERCENT_discount', {PERCENT: res.percent_off}))
|
||||
if res.type == 'percent_off'
|
||||
$scope.messages.push(type: 'success', message: _t('the_coupon_has_been_applied_you_get_PERCENT_discount', {PERCENT: res.percent_off}))
|
||||
else
|
||||
$scope.messages.push(type: 'success', message: _t('the_coupon_has_been_applied_you_get_AMOUNT_CURRENCY', {AMOUNT: res.amount_off, CURRENCY: $rootScope.currencySymbol}))
|
||||
, (err) ->
|
||||
$scope.status = 'invalid'
|
||||
$scope.coupon = null
|
||||
growl.error(_t('unable_to_apply_the_coupon_because_'+err.data.status))
|
||||
$scope.messages.push(type: 'danger', message: _t('unable_to_apply_the_coupon_because_'+err.data.status))
|
||||
|
||||
##
|
||||
# Callback to remove the message at provided index from the displayed list
|
||||
##
|
||||
$scope.closeMessage = (index) ->
|
||||
$scope.messages.splice(index, 1);
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -270,6 +270,9 @@ angular.module('application.router', ['ui.router']).
|
||||
templateUrl: '<%= asset_path "projects/new.html" %>'
|
||||
controller: 'NewProjectController'
|
||||
resolve:
|
||||
allowedExtensions: ['Project', (Project)->
|
||||
Project.allowedExtensions().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.logged.projects_new', 'app.shared.project']).$promise
|
||||
]
|
||||
@ -296,6 +299,9 @@ angular.module('application.router', ['ui.router']).
|
||||
projectPromise: ['$stateParams', 'Project', ($stateParams, Project)->
|
||||
Project.get(id: $stateParams.id).$promise
|
||||
]
|
||||
allowedExtensions: ['Project', (Project)->
|
||||
Project.allowedExtensions().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.logged.projects_edit', 'app.shared.project']).$promise
|
||||
]
|
||||
@ -367,7 +373,7 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
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']).$promise
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise
|
||||
]
|
||||
.state 'app.admin.machines_edit',
|
||||
url: '/machines/:id/edit'
|
||||
@ -382,6 +388,97 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.machines_edit', 'app.shared.machine']).$promise
|
||||
]
|
||||
|
||||
# spaces
|
||||
.state 'app.public.spaces_list',
|
||||
url: '/spaces'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/index.html" %>'
|
||||
controller: 'SpacesController'
|
||||
resolve:
|
||||
spacesPromise: ['Space', (Space)->
|
||||
Space.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.spaces_list']).$promise
|
||||
]
|
||||
.state 'app.admin.space_new',
|
||||
url: '/spaces/new'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/new.html" %>'
|
||||
controller: 'NewSpaceController'
|
||||
resolve:
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.space_new', 'app.shared.space']).$promise
|
||||
]
|
||||
.state 'app.public.space_show',
|
||||
url: '/spaces/:id'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/show.html" %>'
|
||||
controller: 'ShowSpaceController'
|
||||
resolve:
|
||||
spacePromise: ['Space', '$stateParams', (Space, $stateParams)->
|
||||
Space.get(id: $stateParams.id).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.space_show']).$promise
|
||||
]
|
||||
.state 'app.admin.space_edit',
|
||||
url: '/spaces/:id/edit'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/edit.html" %>'
|
||||
controller: 'EditSpaceController'
|
||||
resolve:
|
||||
spacePromise: ['Space', '$stateParams', (Space, $stateParams)->
|
||||
Space.get(id: $stateParams.id).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.space_edit', 'app.shared.space']).$promise
|
||||
]
|
||||
.state 'app.logged.space_reserve',
|
||||
url: '/spaces/:id/reserve'
|
||||
abstract: Fablab.withoutSpaces
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "spaces/reserve.html" %>'
|
||||
controller: 'ReserveSpaceController'
|
||||
resolve:
|
||||
spacePromise: ['Space', '$stateParams', (Space, $stateParams)->
|
||||
Space.get(id: $stateParams.id).$promise
|
||||
]
|
||||
availabilitySpacesPromise: ['Availability', '$stateParams', (Availability, $stateParams)->
|
||||
Availability.spaces({spaceId: $stateParams.id}).$promise
|
||||
]
|
||||
plansPromise: ['Plan', (Plan)->
|
||||
Plan.query().$promise
|
||||
]
|
||||
groupsPromise: ['Group', (Group)->
|
||||
Group.query().$promise
|
||||
]
|
||||
settingsPromise: ['Setting', (Setting)->
|
||||
Setting.query(names: "['booking_window_start',
|
||||
'booking_window_end',
|
||||
'booking_move_enable',
|
||||
'booking_move_delay',
|
||||
'booking_cancel_enable',
|
||||
'booking_cancel_delay',
|
||||
'subscription_explications_alert',
|
||||
'space_explications_alert']").$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
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
|
||||
]
|
||||
|
||||
# trainings
|
||||
.state 'app.public.trainings_list',
|
||||
url: '/trainings'
|
||||
@ -445,7 +542,7 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
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']).$promise
|
||||
'app.shared.wallet', 'app.shared.coupon_input', 'app.shared.cart']).$promise
|
||||
]
|
||||
# notifications
|
||||
.state 'app.logged.notifications',
|
||||
@ -516,7 +613,7 @@ angular.module('application.router', ['ui.router']).
|
||||
PriceCategory.query().$promise
|
||||
]
|
||||
settingsPromise: ['Setting', (Setting)->
|
||||
Setting.query(names: "['booking_move_enable', 'booking_move_delay']").$promise
|
||||
Setting.query(names: "['booking_move_enable', 'booking_move_delay', 'event_explications_alert']").$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.events_show', 'app.shared.member_select', 'app.shared.stripe',
|
||||
@ -543,6 +640,9 @@ angular.module('application.router', ['ui.router']).
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
Machine.query().$promise
|
||||
]
|
||||
spacesPromise: ['Space', (Space) ->
|
||||
Space.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.calendar']).$promise
|
||||
]
|
||||
@ -764,6 +864,15 @@ angular.module('application.router', ['ui.router']).
|
||||
couponsPromise: ['Coupon', (Coupon) ->
|
||||
Coupon.query().$promise
|
||||
]
|
||||
spacesPromise: ['Space', (Space) ->
|
||||
Space.query().$promise
|
||||
]
|
||||
spacesPricesPromise: ['Price', (Price)->
|
||||
Price.query(priceable_type: 'Space', plan_id: 'null').$promise
|
||||
]
|
||||
spacesCreditsPromise: ['Credit', (Credit) ->
|
||||
Credit.query({creditable_type: 'Space'}).$promise
|
||||
]
|
||||
|
||||
# plans
|
||||
.state 'app.admin.plans',
|
||||
@ -772,15 +881,9 @@ angular.module('application.router', ['ui.router']).
|
||||
prices: ['Pricing', (Pricing) ->
|
||||
Pricing.query().$promise
|
||||
]
|
||||
machines: ['Machine', (Machine) ->
|
||||
Machine.query().$promise
|
||||
]
|
||||
groups: ['Group', (Group) ->
|
||||
Group.query().$promise
|
||||
]
|
||||
plans: ['Plan', (Plan) ->
|
||||
Plan.query().$promise
|
||||
]
|
||||
partners: ['User', (User) ->
|
||||
User.query({role: 'partner'}).$promise
|
||||
]
|
||||
@ -801,6 +904,15 @@ angular.module('application.router', ['ui.router']).
|
||||
templateUrl: '<%= asset_path "admin/plans/edit.html" %>'
|
||||
controller: 'EditPlanController'
|
||||
resolve:
|
||||
spaces: ['Space', (Space) ->
|
||||
Space.query().$promise
|
||||
]
|
||||
machines: ['Machine', (Machine) ->
|
||||
Machine.query().$promise
|
||||
]
|
||||
plans: ['Plan', (Plan) ->
|
||||
Plan.query().$promise
|
||||
]
|
||||
planPromise: ['Plan', '$stateParams', (Plan, $stateParams) ->
|
||||
Plan.get({id: $stateParams.id}).$promise
|
||||
]
|
||||
@ -1031,6 +1143,8 @@ angular.module('application.router', ['ui.router']).
|
||||
'training_explications_alert',
|
||||
'training_information_message',
|
||||
'subscription_explications_alert',
|
||||
'event_explications_alert',
|
||||
'space_explications_alert',
|
||||
'booking_window_start',
|
||||
'booking_window_end',
|
||||
'booking_move_enable',
|
||||
|
@ -11,4 +11,7 @@ Application.Services.factory 'AuthProvider', ["$resource", ($resource)->
|
||||
active:
|
||||
method: 'GET'
|
||||
url: '/api/auth_providers/active'
|
||||
send_code:
|
||||
method: 'POST'
|
||||
url: '/api/auth_providers/send_code'
|
||||
]
|
||||
|
@ -17,6 +17,11 @@ Application.Services.factory 'Availability', ["$resource", ($resource)->
|
||||
url: '/api/availabilities/trainings/:trainingId'
|
||||
params: {trainingId: "@trainingId"}
|
||||
isArray: true
|
||||
spaces:
|
||||
method: 'GET'
|
||||
url: '/api/availabilities/spaces/:spaceId'
|
||||
params: {spaceId: "@spaceId"}
|
||||
isArray: true
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
||||
|
@ -3,6 +3,14 @@
|
||||
Application.Services.factory 'Notification', ["$resource", ($resource)->
|
||||
$resource "/api/notifications/:id",
|
||||
{id: "@id"},
|
||||
query:
|
||||
isArray: false
|
||||
update:
|
||||
method: 'PUT'
|
||||
polling:
|
||||
url: '/api/notifications/polling'
|
||||
method: 'GET'
|
||||
last_unread:
|
||||
url: '/api/notifications/last_unread'
|
||||
method: 'GET'
|
||||
]
|
||||
|
@ -11,4 +11,8 @@ Application.Services.factory 'Project', ["$resource", ($resource)->
|
||||
method: 'GET'
|
||||
url: '/api/projects/search'
|
||||
isArray: false
|
||||
allowedExtensions:
|
||||
method: 'GET'
|
||||
url: '/api/projects/allowed_extensions'
|
||||
isArray: true
|
||||
]
|
||||
|
8
app/assets/javascripts/services/space.coffee
Normal file
8
app/assets/javascripts/services/space.coffee
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'Space', ["$resource", ($resource)->
|
||||
$resource "/api/spaces/:id",
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
@ -5,6 +5,7 @@
|
||||
//.bg-yellow { background-color: $yellow !important; }
|
||||
.bg-token { background-color: rgba(230, 208, 137, 0.49); }
|
||||
.bg-machine { background-color: $beige; }
|
||||
.bg-space { background-color: $cyan }
|
||||
.bg-formation { background-color: $violet; }
|
||||
.bg-event { background-color: $japonica; }
|
||||
.bg-atelier { background-color: $blue; }
|
||||
@ -39,4 +40,5 @@
|
||||
.text-purple { color: $violet !important; }
|
||||
.text-japonica { color: $japonica !important; }
|
||||
.text-beige { color: $beige !important; }
|
||||
.text-cyan { color: $cyan !important; }
|
||||
.text-green, .green { color: #79C84A !important; }
|
||||
|
@ -15,6 +15,20 @@
|
||||
.note-editor .note-editable {
|
||||
background-color: white;
|
||||
}
|
||||
.note-editor {
|
||||
.form-group {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.note-group-select-from-files {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Growl
|
||||
|
@ -56,6 +56,7 @@ p, .widget p {
|
||||
.block.hide{display: none;}
|
||||
.inline{display:inline-block !important;}
|
||||
.none{display: none;}
|
||||
.pull-left{float: left;}
|
||||
.pull-right-lg{float: right;}
|
||||
.pull-none{float: none;}
|
||||
.rounded{border-radius: 500px;}
|
||||
@ -102,6 +103,7 @@ p, .widget p {
|
||||
.text-italic { font-style: italic; }
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
.text-active, .active > .text, .active > .auto .text{display: none !important;}
|
||||
.active > .text-active, .active > .auto .text-active{display: inline-block !important;}
|
||||
@ -127,6 +129,7 @@ p, .widget p {
|
||||
|
||||
.width-35 { width: 35% !important; }
|
||||
.width-70 { width: 70%; }
|
||||
.width-90 { width: 90%; }
|
||||
|
||||
.b{border: 1px solid rgba(0, 0, 0, 0.05)}
|
||||
.b-a{border: 1px solid $border-color}
|
||||
@ -173,6 +176,7 @@ p, .widget p {
|
||||
.r-n { border-radius: 0 0 0 0; }
|
||||
|
||||
.p-xs { padding: 5px;}
|
||||
.p-s { padding: 10px;}
|
||||
.p-lg { padding: 30px; }
|
||||
.p-l { padding: 16px; }
|
||||
|
||||
|
@ -43,6 +43,7 @@ $blue: $brand-info;
|
||||
$green: $brand-success;
|
||||
$beige: #e4cd78;
|
||||
$violet: #bd7ae9;
|
||||
$cyan: #3fc7ff;
|
||||
$japonica: #dd7e6b;
|
||||
|
||||
$border-color: #dddddd;
|
||||
@ -767,7 +768,7 @@ $panel-footer-padding: $panel-heading-padding !default;
|
||||
$panel-border-radius: $border-radius-large !default;
|
||||
|
||||
// add sleede
|
||||
$panel-border: $border-color !default;
|
||||
$panel-border: $border-color !default;
|
||||
$panel-heading-bg: #fff !default;
|
||||
$panel-footer-bg: #fff !default;
|
||||
|
||||
|
@ -7,14 +7,15 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'calendar_management' }}</h1>
|
||||
<h1 translate>{{ '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">
|
||||
<span class="badge text-sm bg-formation m-t-sm" translate>{{ 'trainings' }}</span><br>
|
||||
<span class="badge text-sm bg-machine" translate>{{ 'machines' }}</span>
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -29,9 +30,21 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="m text-center">
|
||||
<a class="btn btn-default"
|
||||
ng-href="api/availabilities/export_index.xlsx"
|
||||
target="export-frame"
|
||||
ng-click="alertExport('index')"
|
||||
uib-popover="{{ 'admin_calendar.availabilities_notice' | translate}}"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="bottom">
|
||||
<i class="fa fa-file-excel-o"></i> {{ '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">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'ongoing_reservations' }}</h3>
|
||||
<h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled" ng-if="reservations.length > 0">
|
||||
@ -42,7 +55,7 @@
|
||||
<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-if="reservations.length == 0" translate>{{ 'no_reservations' }}</div>
|
||||
<div ng-if="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,7 +63,7 @@
|
||||
<div class="col-sm-12 col-md-12 col-lg-3" ng-if="availability.machine_ids.length > 0">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'machines' }}</h3>
|
||||
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<ul class="list-unstyled">
|
||||
|
@ -1,22 +1,41 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red">
|
||||
{{ 'DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
|
||||
{{ 'admin_calendar.DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-center font-sbold" translate>{{ 'you_can_define_a_training_on_that_slot' }}</p>
|
||||
<div>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" ng-model="available_type" ng-change="changeAvailableType()"> {{ 'link_a_training' | translate }}
|
||||
</label>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="step === 2">
|
||||
|
||||
<p class="text-center font-sbold m-t" ng-show="availability.available_type == 'machines'"><span class="underline" translate>{{ 'or_' }}</span> {{ '_select_some_machines' | translate }}</p>
|
||||
<div ng-show="availability.available_type == 'machines'">
|
||||
<p class="text-center font-sbold m-t-sm">{{ 'admin_calendar.select_some_machines' | translate }}</p>
|
||||
|
||||
<div class="checkbox" ng-show="availability.available_type == 'machines'">
|
||||
<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>
|
||||
</label>
|
||||
<div class="form-group m-l-xl">
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="availability.available_type == 'training'">
|
||||
@ -24,27 +43,42 @@
|
||||
</select>
|
||||
<div class="row m-t">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-6 control-label" translate>{{ 'number_of_tickets' }}</label>
|
||||
<label class="col-sm-6 control-label" for="nb_places_training" translate>{{ 'admin_calendar.number_of_tickets' }}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="number" class="form-control" ng-model="availability.nb_total_places">
|
||||
<input type="number" id="nb_places_training" class="form-control" ng-model="availability.nb_total_places">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="timeAdjust" class="m-t-lg">
|
||||
<p class="text-center font-sbold" translate>{{ 'adjust_the_opening_hours' }}</p>
|
||||
|
||||
<div ng-show="availability.available_type == 'space'">
|
||||
<select ng-model="selectedSpace" class="form-control m-t-sm" ng-options="t.name for t in spaces" ng-change="setNbTotalPlaces()">
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>{{ 'to_time' }}</span>
|
||||
<span class="col-md-1 m-t-xl m-l" translate>{{ '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 id="tagAssociate" class="m-t-lg">
|
||||
<p class="text-center font-sbold" translate>{{ 'restrict_this_slot_with_labels_(optional)' }}</p>
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.restrict_this_slot_with_labels_(optional)' }}</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<ui-select multiple ng-model="availability.tag_ids" class="form-control">
|
||||
@ -59,7 +93,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -21,7 +21,20 @@
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$invalid}">
|
||||
<div class="form-group">
|
||||
<label for="coupon[type]">{{ '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>
|
||||
</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>
|
||||
<div class="input-group">
|
||||
<input type="number" id="coupon[percent_off]"
|
||||
@ -31,13 +44,30 @@
|
||||
min="0"
|
||||
max="100"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
required="required"/>
|
||||
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>
|
||||
</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>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{currencySymbol}}</span>
|
||||
<input type="number" id="coupon[amount_off]"
|
||||
name="coupon[amount_off]"
|
||||
class="form-control"
|
||||
ng-model="coupon.amount_off"
|
||||
min="0"
|
||||
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>
|
||||
</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>
|
||||
<select id="coupon[validity_per_user]"
|
||||
@ -51,7 +81,7 @@
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group" ng-class="{'has-error': errors['valid_until']}">
|
||||
<label for="coupon[valid_until]" translate>{{ 'valid_until' }}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="coupon[valid_until]"
|
||||
@ -62,16 +92,16 @@
|
||||
datepicker-options="datePicker.options"
|
||||
is-open="datePicker.opened"
|
||||
min-date="datePicker.minDate"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
ng-click="toggleDatePicker($event)"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="toggleDatePicker($event)" ng-disabled="mode == 'EDIT'"><i class="fa fa-calendar"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
<span class="help-block error" ng-show="errors['valid_until']">{{ errors['valid_until'].join(' ; ') }}</span>
|
||||
|
||||
<span class="help-block text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'leave_empty_for_no_limit' | translate }}
|
||||
</span>
|
||||
<span class="text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ '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}">
|
||||
@ -84,7 +114,7 @@
|
||||
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 text-info text-xs">
|
||||
<span class="text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'leave_empty_for_no_limit' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1>{{ 'reservations' | translate }} {{event.title}}</h1>
|
||||
<h1>{{ 'the_reservations' | translate }} {{event.title}}</h1>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -157,11 +157,11 @@
|
||||
|
||||
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
|
||||
<td>{{ 'including_VAT' | translate }} {{invoice.VAT.rate}} %</td>
|
||||
<td>{{30/(invoice.VAT.rate/100+1) | currency}}</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>{{ 'including_total_excluding_taxes' }}</td>
|
||||
<td>{{30-(30/(invoice.VAT.rate/100+1)) | currency}}</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>{{ 'including_amount_payed_on_ordering' }}</td>
|
||||
|
@ -1,5 +1,8 @@
|
||||
<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" translate>{{ 'group' }}</label>
|
||||
<label for="user_group_id" class="col-sm-3 control-label">
|
||||
<span translate>{{ '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>
|
||||
|
@ -1,19 +1,19 @@
|
||||
<h2 translate>{{ 'general_informations' }}</h2>
|
||||
<h2 translate>{{ 'plan_form.general_information' }}</h2>
|
||||
<input type="hidden" name="_method" value="{{method}}">
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$invalid}">
|
||||
<label for="plan[base_name]">{{ 'name' | translate }} *</label>
|
||||
<label for="plan[base_name]">{{ 'plan_form.name' | translate }} *</label>
|
||||
<input type="text" id="plan[base_name]"
|
||||
name="plan[base_name]"
|
||||
class="form-control"
|
||||
ng-maxlength="24"
|
||||
ng-model="plan.base_name"
|
||||
required="required"/>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.required" translate>{{ 'name_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.maxlength" translate>{{ 'name_length_must_be_less_than_24_characters' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.required" translate>{{ 'plan_form.name_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.maxlength" translate>{{ 'plan_form.name_length_must_be_less_than_24_characters' }}</span>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[type]'].$dirty && planForm['plan[type]'].$invalid}">
|
||||
<label for="plan[type]">{{ 'type' | translate }} *</label>
|
||||
<label for="plan[type]">{{ 'plan_form.type' | translate }} *</label>
|
||||
<select id="plan[type]"
|
||||
name="plan[type]"
|
||||
class="form-control"
|
||||
@ -23,40 +23,40 @@
|
||||
<option value="Plan" ng-selected="plan.type == 'Plan'" translate>{{ 'standard' }}</option>
|
||||
<option value="PartnerPlan" ng-selected="plan.type == 'PartnerPlan'" translate>{{ 'partner' }}</option>
|
||||
</select>
|
||||
<span class="help-block error" ng-show="planForm['plan[type]'].$dirty && planForm['plan[type]'].$error.required" translate>{{ 'type_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="planForm['plan[type]'].$dirty && planForm['plan[type]'].$error.required" translate>{{ 'plan_form.type_is_required' }}</span>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[group_id]'].$dirty && planForm['plan[group_id]'].$invalid}">
|
||||
<label for="plan[group_id]">{{ 'group' | translate }} *</label>
|
||||
<label for="plan[group_id]">{{ 'plan_form.group' | translate }} *</label>
|
||||
<select id="plan[group_id]"
|
||||
name="plan[group_id]"
|
||||
class="form-control"
|
||||
ng-model="plan.group_id"
|
||||
required="required"
|
||||
ng-disabled="method == 'PATCH'">
|
||||
<option value="all" translate>{{ 'transversal_(all_groups)' }}</option>
|
||||
<option value="all" translate>{{ 'plan_form.transversal_(all_groups)' }}</option>
|
||||
<optgroup label="Groupes">
|
||||
<option ng-repeat="group in groups" ng-value="group.id" ng-selected="plan.group_id == group.id">{{group.name}}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<span class="help-block" ng-show="planForm['plan[group_id]'].$dirty && planForm['plan[group_id]'].$error.required" translate>{{ 'group_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[group_id]'].$dirty && planForm['plan[group_id]'].$error.required" translate>{{ 'plan_form.group_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[interval]'].$dirty && planForm['plan[interval]'].$invalid}">
|
||||
<label for="plan[interval]">{{ 'period' | translate }} *</label>
|
||||
<label for="plan[interval]">{{ 'plan_form.period' | translate }} *</label>
|
||||
<select id="plan[interval]"
|
||||
name="plan[interval]"
|
||||
class="form-control"
|
||||
ng-model="plan.interval"
|
||||
ng-disabled="method == 'PATCH'"
|
||||
required="required">
|
||||
<option value="month" ng-selected="plan.interval == 'month'" translate>{{ 'month' }}</option>
|
||||
<option value="year" ng-selected="plan.interval == 'year'" translate>{{ 'year' }}</option>
|
||||
<option value="month" ng-selected="plan.interval == 'month'" translate>{{ 'plan_form.month' }}</option>
|
||||
<option value="year" ng-selected="plan.interval == 'year'" translate>{{ 'plan_form.year' }}</option>
|
||||
</select>
|
||||
<span class="help-block" ng-show="planForm['plan[interval]'].$dirty && planForm['plan[interval]'].$error.required" translate>{{ 'period_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[interval]'].$dirty && planForm['plan[interval]'].$error.required" translate>{{ 'plan_form.period_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': planForm['plan[interval_count]'].$dirty && planForm['plan[interval_count]'].$invalid}">
|
||||
<label for="plan[interval]">{{ 'number_of_periods' | translate }} *</label>
|
||||
<label for="plan[interval]">{{ 'plan_form.number_of_periods' | translate }} *</label>
|
||||
<input id="plan[interval_count]"
|
||||
name="plan[interval_count]"
|
||||
class="form-control"
|
||||
@ -65,12 +65,12 @@
|
||||
ng-disabled="method == 'PATCH'"
|
||||
required="required"
|
||||
min="1"/>
|
||||
<span class="help-block" ng-show="planForm['plan[interval_count]'].$dirty && planForm['plan[interval_count]'].$error.required" translate>{{ 'number_of_periods_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[interval_count]'].$dirty && planForm['plan[interval_count]'].$error.required" translate>{{ 'plan_form.number_of_periods_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group" ng-class="{'has-error': planForm['plan[amount]'].$dirty && planForm['plan[amount]'].$invalid}">
|
||||
<label for="plan[amount]">{{ 'subscription_price' | translate }} *</label>
|
||||
<label for="plan[amount]">{{ 'plan_form.subscription_price' | translate }} *</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">{{currencySymbol}}</span>
|
||||
<input id="plan[amount]"
|
||||
@ -80,24 +80,24 @@
|
||||
ng-required="true"
|
||||
ng-model="plan.amount"/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="planForm['plan[amount]'].$dirty && planForm['plan[amount]'].$error.required" translate>{{ 'price_is_required' }}</span>
|
||||
<span class="help-block" ng-show="planForm['plan[amount]'].$dirty && planForm['plan[amount]'].$error.required" translate>{{ 'plan_form.price_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label translate>{{ 'visual_prominence_of_the_subscription' }}</label>
|
||||
<label translate>{{ 'plan_form.visual_prominence_of_the_subscription' }}</label>
|
||||
<input ng-model="plan.ui_weight"
|
||||
type="number"
|
||||
name="plan[ui_weight]"
|
||||
class="form-control">
|
||||
<span class="help-block">
|
||||
{{ 'on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list' | translate }}
|
||||
{{ 'an_evelated_number_means_a_higher_prominence' | translate }}
|
||||
{{ 'plan_form.on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list' | translate }}
|
||||
{{ 'plan_form.an_evelated_number_means_a_higher_prominence' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group m-t-md">
|
||||
<label for="plan[is_rolling]" class="control-label m-r-md">{{ 'rolling_subscription' | translate }} *</label>
|
||||
<label for="plan[is_rolling]" class="control-label m-r-md">{{ 'plan_form.rolling_subscription' | translate }} *</label>
|
||||
<input bs-switch
|
||||
ng-model="plan.isRolling"
|
||||
id="plan[is_rolling]"
|
||||
@ -112,8 +112,8 @@
|
||||
<span ng-if="method == 'PATCH'">{{ (plan.is_rolling ? 'yes' : 'no') | translate }}</span>
|
||||
<input type="hidden" name="plan[is_rolling]" value="{{plan.isRolling}}"/>
|
||||
<span class="help-block">
|
||||
{{ 'a_rolling_subscription_will_begin_the_day_of_the_first_training' | translate }}
|
||||
{{ 'otherwise_it_will_begin_as_soon_as_it_is_bought' | translate }}
|
||||
{{ 'plan_form.a_rolling_subscription_will_begin_the_day_of_the_first_training' | translate }}
|
||||
{{ 'plan_form.otherwise_it_will_begin_as_soon_as_it_is_bought' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -121,12 +121,12 @@
|
||||
<!-- PDF description attachement -->
|
||||
<input type="hidden" ng-model="plan.plan_file_attributes.id" name="plan[plan_file_attributes][id]" ng-value="plan.plan_file_attributes.id" />
|
||||
<input type="hidden" ng-model="plan.plan_file_attributes._destroy" name="plan[plan_file_attributes][_destroy]" ng-value="plan.plan_file_attributes._destroy"/>
|
||||
<label class="m-t-md" translate>{{ 'information_sheet' }}</label>
|
||||
<label class="m-t-md" translate>{{ 'plan_form.information_sheet' }}</label>
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(plan.plan_file_attributes)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment || plan.plan_file_attributes.attachment_identifier}}</span>
|
||||
</div>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'attach_an_information_sheet' }}</span>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'plan_form.attach_an_information_sheet' }}</span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span><input type="file"
|
||||
name="plan[plan_file_attributes][attachment]"
|
||||
accept="image/*, application/pdf"></span>
|
||||
@ -135,7 +135,7 @@
|
||||
|
||||
<div class="form-group m-t-md" ng-show="plan.type == 'PartnerPlan' && method != 'PATCH'">
|
||||
<input type="hidden" ng-model="plan.partnerId" name="plan[partner_id]" ng-value="plan.partnerId" />
|
||||
<label for="plan[partner_id]">{{ 'notified_partner' | translate }} *</label>
|
||||
<label for="plan[partner_id]">{{ 'plan_form.notified_partner' | translate }} *</label>
|
||||
<div class="input-group">
|
||||
<select class="form-control"
|
||||
ng-model="plan.partnerId"
|
||||
@ -144,10 +144,10 @@
|
||||
<option value=""></option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="openPartnerNewModal()"><i class="fa fa-user-plus"></i> {{ 'new_user' | translate }}</button>
|
||||
<button class="btn btn-default" type="button" ng-click="openPartnerNewModal()"><i class="fa fa-user-plus"></i> {{ 'plan_form.new_user' | translate }}</button>
|
||||
</span>
|
||||
</div>
|
||||
<span class="help-block" translate>{{ 'as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user' }}</span>
|
||||
<span class="help-block" translate>{{ 'plan_form.as_part_of_a_partner_subscription_some_notifications_may_be_sent_to_this_user' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="plan.partners">
|
||||
@ -155,4 +155,4 @@
|
||||
<span ng-repeat="partner in plan.partners">
|
||||
<input type="text" class="form-control" disabled value="{{ partner.first_name}} {{partner.last_name }}">
|
||||
</span>
|
||||
</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>{{ 'subscription_plan' | translate }} {{ plan.base_name }}</h1>
|
||||
<h1>{{ 'edit_plan.subscription_plan' | translate }} {{ plan.base_name }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -30,23 +30,23 @@
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/plans/_form.html' %>'"></ng-include>
|
||||
|
||||
<h2 class="m-t-xl" translate>{{ 'prices' }}</h2>
|
||||
<h2 class="m-t-xl" translate>{{ 'edit_plan.prices' }}</h2>
|
||||
<div class="form-group col-md-6 col-lg-offset-6">
|
||||
<input type="hidden" ng-model="plan.parent" name="plan[parent_id]" ng-value="plan.parent"/>
|
||||
<label for="parentPlan" translate>{{ 'copy_prices_from' }}</label>
|
||||
<label for="parentPlan" translate>{{ 'edit_plan.copy_prices_from' }}</label>
|
||||
<select id="parentPlan" ng-options="plan.id as humanReadablePlanName(plan, groups) for plan in plans" ng-model="plan.parent" ng-change="copyPricesFromPlan()" class="form-control">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
<h3 translate>{{ 'machines' }}</h3>
|
||||
<h3 translate>{{ 'edit_plan.machines' }}</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th translate>{{ 'machine' }}</th>
|
||||
<th translate>{{ 'hourly_rate' }}</th>
|
||||
<th translate>{{ 'edit_plan.machine' }}</th>
|
||||
<th translate>{{ 'edit_plan.hourly_rate' }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr ng-repeat="price in plan.prices">
|
||||
<tr ng-repeat="price in plan.prices" ng-if="price.priceable_type === 'Machine'">
|
||||
<td style="width: 60%;">{{ getMachineName(price.priceable_id) }} (id {{ price.priceable_id }}) *</td>
|
||||
<td>
|
||||
<div class="input-group" ng-class="{'has-error': planForm['plan[prices_attributes][][amount]'].$dirty && planForm['plan[prices_attributes][][amount]'].$invalid}">
|
||||
@ -59,6 +59,27 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 ng-hide="fablabWithoutSpaces" translate>{{ 'edit_plan.spaces' }}</h3>
|
||||
<table class="table" ng-hide="fablabWithoutSpaces">
|
||||
<thead>
|
||||
<th translate>{{ 'edit_plan.space' }}</th>
|
||||
<th translate>{{ 'edit_plan.hourly_rate' }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr ng-repeat="price in plan.prices" ng-if="price.priceable_type === 'Space'">
|
||||
<td style="width: 60%;">{{ getSpaceName(price.priceable_id) }} *</td>
|
||||
<td>
|
||||
<div class="input-group" ng-class="{'has-error': planForm['plan[prices_attributes][][amount]'].$dirty && planForm['plan[prices_attributes][][amount]'].$invalid}">
|
||||
<span class="input-group-addon">{{currencySymbol}}</span>
|
||||
<input type="number" class="form-control" name="plan[prices_attributes][][amount]" ng-value="price.amount" required="required"/>
|
||||
<input type="hidden" class="form-control" name="plan[prices_attributes][][id]" ng-value="price.id"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="planForm.$invalid"/>
|
||||
</div>
|
||||
|
@ -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_a_subscription_plan' }}</h1>
|
||||
<h1 translate>{{ 'new_plan.add_a_subscription_plan' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
@ -1,22 +1,25 @@
|
||||
<h2 translate>{{ 'list_of_the_coupons' }}</h2>
|
||||
<h2 translate>{{ 'pricing.list_of_the_coupons' }}</h2>
|
||||
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.coupons_new" translate>{{ 'add_a_new_coupon' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.coupons_new" translate>{{ 'pricing.add_a_new_coupon' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'name' }}</th>
|
||||
<th translate>{{ 'percentage_off' }}</th>
|
||||
<th translate>{{ 'nb_of_usages' }}</th>
|
||||
<th translate>{{ 'status' }}</th>
|
||||
<th translate>{{ 'pricing.name' }}</th>
|
||||
<th translate>{{ 'pricing.discount' }}</th>
|
||||
<th translate>{{ 'pricing.nb_of_usages' }}</th>
|
||||
<th translate>{{ 'pricing.status' }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="coupon in coupons">
|
||||
<td>{{coupon.name}}</td>
|
||||
<td>{{coupon.percent_off}} %</td>
|
||||
<td>
|
||||
<span ng-show="coupon.type == 'percent_off'">{{coupon.percent_off}} %</span>
|
||||
<span ng-show="coupon.type == 'amount_off'">{{coupon.amount_off}} {{currencySymbol}}</span>
|
||||
</td>
|
||||
<td>{{coupon.usages}}</td>
|
||||
<td translate>{{coupon.status}}</td>
|
||||
<td translate>{{'pricing.'+coupon.status}}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-default" ng-click="sendCouponToUser(coupon)"><i class="fa fa-send-o"></i> </button>
|
||||
<button type="button" class="btn btn-default" ui-sref="app.admin.coupons_edit({id:coupon.id})"><i class="fa fa-pencil-square-o"></i></button>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<h2 class="m-t-lg" translate>{{ 'trainings' }}</h2>
|
||||
<h2 class="m-t-lg" translate>{{ 'pricing.trainings' }}</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'subscription' }}</th>
|
||||
<th style="width:10%" translate>{{ 'credits' }}</th>
|
||||
<th style="width:50%" translate>{{ 'related_trainings' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.subscription' }}</th>
|
||||
<th style="width:10%" translate>{{ 'pricing.credits' }}</th>
|
||||
<th style="width:50%" translate>{{ 'pricing.related_trainings' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -43,17 +43,17 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="m-t-lg" translate>{{ 'machines' }}</h2>
|
||||
<h2 class="m-t-lg" translate>{{ 'pricing.machines' }}</h2>
|
||||
<div class="btn-group m-t-md m-b-md">
|
||||
<button type="button" class="btn btn-warning" ng-click="addMachineCredit($event)" translate>{{ 'add_a_machine_credit' }}</button>
|
||||
<button type="button" class="btn btn-warning" ng-click="addMachineCredit($event)" translate>{{ 'pricing.add_a_machine_credit' }}</button>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'machine' }}</th>
|
||||
<th style="width:10%" translate>{{ 'hours' }}</th>
|
||||
<th style="width:50%" translate>{{ 'related_subscriptions' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.machine' }}</th>
|
||||
<th style="width:10%" translate>{{ 'pricing.hours' }}</th>
|
||||
<th style="width:50%" translate>{{ 'pricing.related_subscriptions' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -94,4 +94,56 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="m-t-lg" translate>{{ 'pricing.spaces' }}</h2>
|
||||
<div class="btn-group m-t-md m-b-md">
|
||||
<button type="button" class="btn btn-warning" ng-click="addSpaceCredit($event)" translate>{{ 'pricing.add_a_space_credit' }}</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'pricing.space' }}</th>
|
||||
<th style="width:10%" translate>{{ 'pricing.hours' }}</th>
|
||||
<th style="width:50%" translate>{{ 'pricing.related_subscriptions' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="sc in spaceCredits">
|
||||
<td>
|
||||
<span editable-select="sc.creditable_id" e-name="creditable_id" e-form="rowform" e-ng-options="s.id as s.name for s in spaces" e-required>
|
||||
{{ showCreditableName(sc) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-number="sc.hours" e-name="hours" e-form="rowform" e-required>
|
||||
{{ sc.hours }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-select="sc.plan_id" e-ng-options="p.id as humanReadablePlanName(p, groups, 'short') for p in plans" e-name="plan_id" e-form="rowform">
|
||||
{{ getPlanFromId(sc.plan_id) | humanReadablePlanName: groups: 'short' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form editable-form name="rowform" onbeforesave="saveSpaceCredit($data, sc.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == sc">
|
||||
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelSpaceCredit(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeSpaceCredit($index)">
|
||||
<i class="fa fa-trash-o"></i> {{ 'delete' | translate }} (!)
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'pricing_management' }}</h1>
|
||||
<h1 translate>{{ 'pricing.pricing_management' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -21,23 +21,27 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'subscriptions' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.subscriptions' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/subscriptions.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'trainings' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.trainings' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/trainings.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'machine_hours' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.machine_hours' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/machine_hours.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'credits' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.spaces' | translate }}" ng-hide="fablabWithoutSpaces">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/spaces.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'pricing.credits' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/credits.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'coupons' | translate }}">
|
||||
<uib-tab heading="{{ 'pricing.coupons' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/coupons.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<div class="alert alert-warning m-t">
|
||||
{{ 'these_prices_match_machine_hours_rates_' | translate }} <span class="font-bold" translate>{{ '_without_subscriptions' }}</span>.
|
||||
{{ 'pricing.these_prices_match_machine_hours_rates_' | translate }} <span class="font-bold" translate>{{ 'pricing._without_subscriptions' }}</span>.
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'machines' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.machines' }}</th>
|
||||
<th style="width:20%" ng-repeat="group in groups">
|
||||
<span class="text-u-c text-sm">{{group.name}}</span>
|
||||
</th>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red" translate>{{ 'send_a_coupon' }}</h3>
|
||||
<h3 class="text-center red" translate>{{ 'pricing.send_a_coupon' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<select-member></select-member>
|
||||
<div class="widget panel b-a m">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 class="panel-title" translate>{{ 'coupon' }}</h3>
|
||||
<h3 class="panel-title" translate>{{ 'pricing.coupon' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<table>
|
||||
@ -13,12 +13,12 @@
|
||||
<tr><th style="width:60%"></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td translate>{{'code'}}</td><td>{{coupon.code}}</td></tr>
|
||||
<tr><td translate>{{'percent_off'}}</td><td>{{coupon.percent_off}} %</td></tr>
|
||||
<tr><td translate>{{'validity_per_user'}}</td><td translate>{{coupon.validity_per_user}}</td></tr>
|
||||
<tr><td translate>{{'valid_until'}}</td><td>{{coupon.valid_until | amDateFormat:'L'}}</td></tr>
|
||||
<tr><td translate>{{'usages'}}</td><td>{{coupon.usages}} / {{coupon.max_usages | maxCount}}</td></tr>
|
||||
<tr><td translate>{{'enabled'}}</td><td>{{coupon.active | booleanFormat}}</td></tr>
|
||||
<tr><td translate>{{'pricing.code'}}</td> <td>{{coupon.code}}</td></tr>
|
||||
<tr><td translate>{{'pricing.discount'}}</td> <td><span ng-show="coupon.type == 'percent_off'">{{coupon.percent_off}} %</span><span ng-show="coupon.type == 'amount_off'">{{coupon.amount_off}} {{currencySymbol}}</span></td></tr>
|
||||
<tr><td translate>{{'pricing.validity_per_user'}}</td> <td translate>{{'pricing.'+coupon.validity_per_user}}</td></tr>
|
||||
<tr><td translate>{{'pricing.valid_until'}}</td> <td>{{coupon.valid_until | amDateFormat:'L'}}</td></tr>
|
||||
<tr><td translate>{{'pricing.usages'}}</td> <td>{{coupon.usages}} / {{coupon.max_usages | maxCount}}</td></tr>
|
||||
<tr><td translate>{{'pricing.enabled'}}</td> <td>{{coupon.active | booleanFormat}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
26
app/assets/templates/admin/pricing/spaces.html.erb
Normal file
26
app/assets/templates/admin/pricing/spaces.html.erb
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="alert alert-warning m-t">
|
||||
{{ 'pricing.these_prices_match_space_hours_rates_' | translate }} <span class="font-bold" translate>{{ 'pricing._without_subscriptions' }}</span>.
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'pricing.spaces' }}</th>
|
||||
<th style="width:20%" ng-repeat="group in groups">
|
||||
<span class="text-u-c text-sm">{{group.name}}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="space in spaces">
|
||||
<td>
|
||||
{{ space.name }}
|
||||
</td>
|
||||
<td ng-repeat="group in groups">
|
||||
<span editable-number="findPriceBy(spacesPrices, space.id, group.id).amount"
|
||||
onbeforesave="updatePrice($data, findPriceBy(spacesPrices, space.id, group.id))">
|
||||
{{ findPriceBy(spacesPrices, space.id, group.id).amount | currency}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -1,21 +1,21 @@
|
||||
<h2 translate>{{ 'list_of_the_subscription_plans' }}</h2>
|
||||
<h2 translate>{{ 'pricing.list_of_the_subscription_plans' }}</h2>
|
||||
|
||||
<div ng-show="fablabWithoutPlans" class="alert alert-warning m-t">
|
||||
{{ 'beware_the_subscriptions_are_disabled_on_this_application' | translate }}
|
||||
{{ 'you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }}
|
||||
<br>{{ 'for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }}
|
||||
{{ 'pricing.beware_the_subscriptions_are_disabled_on_this_application' | translate }}
|
||||
{{ 'pricing.you_can_create_some_but_they_wont_be_available_until_the_project_is_redeployed_by_the_server_manager' | translate }}
|
||||
<br>{{ 'pricing.for_safety_reasons_please_dont_create_subscriptions_if_you_dont_want_intend_to_use_them_later' | translate }}
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.plans.new" translate>{{ 'add_a_new_subscription_plan' }}</button>
|
||||
<button type="button" class="btn btn-warning m-t-lg m-b" ui-sref="app.admin.plans.new" translate>{{ 'pricing.add_a_new_subscription_plan' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="" ng-click="setOrderPlans('type')">{{ 'type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='type', 'fa fa-sort-alpha-desc': orderPlans=='-type', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('name')">{{ 'name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='name', 'fa fa-sort-alpha-desc': orderPlans=='-name', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('interval')">{{ 'duration' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-amount-asc': orderPlans=='interval', 'fa fa-sort-amount-desc': orderPlans=='-interval', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('group_id')">{{ 'group' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='group_id', 'fa fa-sort-alpha-desc': orderPlans=='-group_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th class="hidden-xs"><a href="" ng-click="setOrderPlans('ui_weight')">{{ 'prominence' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='ui_weight', 'fa fa-sort-numeric-desc': orderPlans=='-ui_weight', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('amount')">{{ 'price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='amount', 'fa fa-sort-numeric-desc': orderPlans=='-amount', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('type')">{{ 'pricing.type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='type', 'fa fa-sort-alpha-desc': orderPlans=='-type', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('name')">{{ 'pricing.name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='name', 'fa fa-sort-alpha-desc': orderPlans=='-name', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('interval')">{{ 'pricing.duration' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-amount-asc': orderPlans=='interval', 'fa fa-sort-amount-desc': orderPlans=='-interval', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('group_id')">{{ 'pricing.group' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPlans=='group_id', 'fa fa-sort-alpha-desc': orderPlans=='-group_id', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th class="hidden-xs"><a href="" ng-click="setOrderPlans('pricing.ui_weight')">{{ 'pricing.prominence' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='ui_weight', 'fa fa-sort-numeric-desc': orderPlans=='-ui_weight', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th><a href="" ng-click="setOrderPlans('amount')">{{ 'pricing.price' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPlans=='amount', 'fa fa-sort-numeric-desc': orderPlans=='-amount', 'fa fa-arrows-v': orderPlans }"></i></a></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'trainings' }}</th>
|
||||
<th style="width:20%" translate>{{ 'pricing.trainings' }}</th>
|
||||
<th style="width:20%" ng-repeat="group in groups">
|
||||
<span class="text-u-c text-sm">{{group.name}}</span>
|
||||
</th>
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
<div class="row m-t-lg m-b-lg">
|
||||
<div class="col-sm-offset-4 col-sm-4">
|
||||
<h1 ng-model="aboutTitleSetting.value" medium-editor options='{"placeholder": "{{ "title_of_the_about_page" | translate }}", "disableToolbar": true, "disableReturn": false}' class="text-u-c"></h1>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<h1 ng-model="aboutTitleSetting.value" medium-editor options='{"placeholder": "{{ "settings.title_of_the_about_page" | translate }}", "disableToolbar": true, "disableReturn": false}' class="text-u-c"></h1>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutTitleSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<div class="text-justify" ng-model="aboutBodySetting.value" medium-editor options='{"placeholder": "{{ "input_the_main_content" | translate }}",
|
||||
<div class="text-justify" ng-model="aboutBodySetting.value" medium-editor options='{"placeholder": "{{ "settings.input_the_main_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -20,12 +20,12 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutBodySetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-2">
|
||||
<div ng-model="aboutContactsSetting.value" medium-editor options='{"placeholder": "{{ "input_the_fablab_contacts" | translate }}",
|
||||
<div ng-model="aboutContactsSetting.value" medium-editor options='{"placeholder": "{{ "settings.input_the_fablab_contacts" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.shift_enter_to_force_carriage_return' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutContactsSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
<div class="panel panel-default m-t-md">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'title' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.title' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row m-t-lg">
|
||||
<div class="col-md-4">
|
||||
<form role="form" novalidate>
|
||||
<label for="fablabName" class="control-label m-r" translate>{{ 'fablab_title' }}</label>
|
||||
<label for="fablabName" class="control-label m-r" translate>{{ 'settings.fablab_title' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><i class="fa fa-font"></i></div>
|
||||
<input type="text" id="fablabName" ng-model="fablabName.value" class="form-control" placeholder="{{ 'fablab_name' | translate }}"/>
|
||||
<input type="text" id="fablabName" ng-model="fablabName.value" class="form-control" placeholder="{{ 'settings.fablab_name' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(fablabName)" translate>{{ 'save' }}</button>
|
||||
@ -19,13 +19,13 @@
|
||||
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<form role="form" novalidate>
|
||||
<h4 class="control-label m-r" translate>{{ 'title_concordance' }}</h4>
|
||||
<h4 class="control-label m-r" translate>{{ 'settings.title_concordance' }}</h4>
|
||||
<div class="form-group">
|
||||
<input type="radio" name="nameGenre" id="nameGenreMale" ng-model="nameGenre.value" ng-value="'male'" />
|
||||
<label for="nameGenreMale">{{ 'male' | translate }} <span style="font-weight: normal">{{ 'eg' | translate }} <cite>{{ 'about' | translate }} <strong translate>{{ 'male_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
<label for="nameGenreMale">{{ 'settings.male' | translate }} <span style="font-weight: normal">{{ 'settings.eg' | translate }} <cite>{{ 'settings.about' | translate }} <strong translate>{{ 'settings.male_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
<br/>
|
||||
<input type="radio" name="nameGenre" id="nameGenreFemale" ng-model="nameGenre.value" ng-value="'female'" />
|
||||
<label for="nameGenreFemale">{{ 'female' | translate }} <span style="font-weight: normal">{{ 'eg' | translate }} <cite>{{ 'about' | translate }} <strong translate>{{ 'female_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
<label for="nameGenreFemale">{{ 'settings.female' | translate }} <span style="font-weight: normal">{{ 'settings.eg' | translate }} <cite>{{ 'settings.about' | translate }} <strong translate>{{ 'settings.female_preposition' }}</strong> {{fablabName.value}}</cite></span></label>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(nameGenre)" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
@ -36,15 +36,15 @@
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'customize_information_messages' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.customize_information_messages' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'message_of_the_machine_booking_page' }}</h4>
|
||||
<div ng-model="machineExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.message_of_the_machine_booking_page' }}</h4>
|
||||
<div ng-model="machineExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -52,8 +52,8 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(machineExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'warning_message_of_the_training_booking_page'}}</h4>
|
||||
<div ng-model="trainingExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.warning_message_of_the_training_booking_page'}}</h4>
|
||||
<div ng-model="trainingExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -61,8 +61,8 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(trainingExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'information_message_of_the_training_reservation_page'}}</h4>
|
||||
<div ng-model="trainingInformationMessage.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.information_message_of_the_training_reservation_page'}}</h4>
|
||||
<div ng-model="trainingInformationMessage.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
@ -70,30 +70,46 @@
|
||||
<button name="button" class="btn btn-warning" ng-click="save(trainingInformationMessage)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'message_of_the_subscriptions_page' }}</h4>
|
||||
<div ng-model="subscriptionExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
<h4 translate>{{ 'settings.message_of_the_subscriptions_page' }}</h4>
|
||||
<div ng-model="subscriptionExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(subscriptionExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'settings.message_of_the_events_page' }}</h4>
|
||||
<div ng-model="eventExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(eventExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-3" ng-hide="fablabWithoutSpaces">
|
||||
<h4 translate>{{ 'settings.message_of_the_spaces_page' }}</h4>
|
||||
<div ng-model="spaceExplicationsAlert.value" medium-editor options='{"placeholder": "{{ "settings.type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(spaceExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'legal_documents'}}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.legal_documents'}}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-warning m-t" translate>
|
||||
{{ 'if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user' }}
|
||||
{{ 'settings.if_these_documents_are_not_filled_no_consent_about_them_will_be_asked_to_the_user' }}
|
||||
</div>
|
||||
<div class="row">
|
||||
<form class="col-md-6" method="post" action="{{actionUrl.cgv}}" novalidate name="cgvForm" ng-upload="submited(content)" ng-submit="addLoader('cgv')" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="cgv-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.cgv">
|
||||
<label for="tnc_file" class="control-label m-r" translate>{{ 'general_terms_and_conditions_(T&C)' }}</label>
|
||||
<label for="tnc_file" class="control-label m-r" translate>{{ 'settings.general_terms_and_conditions_(T&C)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(cgvFile.custom_asset_file_attributes.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
@ -117,7 +133,7 @@
|
||||
<form class="col-md-6" method="post" action="{{actionUrl.cgu}}" novalidate name="cguForm" ng-upload="submited(content)" ng-submit="addLoader('cgu')" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="cgu-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.cgu">
|
||||
<label for="tos_file" class="control-label m-r" translate>{{ 'terms_of_service_(TOS)' }}</label>
|
||||
<label for="tos_file" class="control-label m-r" translate>{{ 'settings.terms_of_service_(TOS)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(cguFile.custom_asset_file_attributes.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
@ -142,21 +158,21 @@
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'customize_the_graphics' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.customize_the_graphics' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-warning m-t">
|
||||
<span translate>{{ 'for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height' }}</span><br/>
|
||||
<span translate>{{ 'concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels' }}</span><br/>
|
||||
<span translate>{{ 'settings.for_an_optimal_rendering_the_logo_image_must_be_at_the_PNG_format_with_a_transparent_background_and_with_an_aspect_ratio_3.5_times_wider_than_the_height' }}</span><br/>
|
||||
<span translate>{{ 'settings.concerning_the_favicon_it_must_be_at_ICO_format_with_a_size_of_16x16_pixels' }}</span><br/>
|
||||
<br/>
|
||||
<span translate>{{ 'remember_to_refresh_the_page_for_the_changes_to_take_effect' }}</span>
|
||||
<span translate>{{ 'settings.remember_to_refresh_the_page_for_the_changes_to_take_effect' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form class="custom-logo-container" method="post" action="{{actionUrl.logo}}" novalidate name="logoForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="logo-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.logo">
|
||||
<h3 class="m-l" translate>{{ 'logo_(white_background)' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_(white_background)' }}</h3>
|
||||
<div class="custom-logo" style="background-image: url({{customLogo}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!customLogo" class="img-responsive">
|
||||
<img base-sixty-four-image="customLogo" ng-show="customLogo && customLogo.base64">
|
||||
@ -164,7 +180,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
@ -182,7 +198,7 @@
|
||||
<form class="custom-logo-container" method="post" action="{{actionUrl.logoBlack}}" novalidate name="logoBlackForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="logo-black-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.logoBlack">
|
||||
<h3 class="m-l" translate>{{ 'logo_(black_background)' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.logo_(black_background)' }}</h3>
|
||||
<div class="custom-logo bg-dark" style="background-image: url({{customLogoBlack}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-black-xs" bs-holder ng-show="!customLogoBlack" class="img-responsive">
|
||||
<img base-sixty-four-image="customLogoBlack" ng-show="customLogoBlack && customLogoBlack.base64">
|
||||
@ -190,7 +206,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
@ -208,7 +224,7 @@
|
||||
<form class="custom-favicon-container" method="post" action="{{actionUrl.favicon}}" novalidate name="faviconForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="favicon-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.favicon">
|
||||
<h3 class="m-l" translate>{{ 'favicon' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.favicon' }}</h3>
|
||||
<div class="custom-favicon" style="background-image: url({{customFavicon}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/32x32/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!customFavicon" class="img-responsive">
|
||||
<img base-sixty-four-image="customFavicon" ng-show="customFavicon && customFavicon.base64">
|
||||
@ -216,7 +232,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_favicon' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_favicon' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png,image/x-icon,image/ico,image/vnd.microsoft.icon"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
@ -233,14 +249,14 @@
|
||||
</div>
|
||||
<div class="row m-t m-l-xs">
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ 'main_colour' }}</h4>
|
||||
<h4 translate>{{ 'settings.main_colour' }}</h4>
|
||||
<form role="form" class="form-inline" name="mainColorForm" novalidate>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</div>
|
||||
<input type="text" minicolors ng-model="mainColorSetting.value" class="form-control" placeholder="{{ 'primary' | translate}}"/>
|
||||
<input type="text" minicolors ng-model="mainColorSetting.value" class="form-control" placeholder="{{ 'settings.primary' | translate}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -249,14 +265,14 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ 'secondary_colour' }}</h4>
|
||||
<h4 translate>{{ 'settings.secondary_colour' }}</h4>
|
||||
<form role="form" class="form-inline" name="secondColorForm" novalidate>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</div>
|
||||
<input type="text" minicolors ng-model="secondColorSetting.value" class="form-control" placeholder="{{ 'secondary' | translate}}"/>
|
||||
<input type="text" minicolors ng-model="secondColorSetting.value" class="form-control" placeholder="{{ 'settings.secondary' | translate}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -270,7 +286,7 @@
|
||||
<form class="custom-profile-image-container" method="post" action="{{actionUrl.profileImage}}" novalidate name="profileImageForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="profile-image-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.profileImage">
|
||||
<h3 class="m-l" translate>{{ 'background_picture_of_the_profile_banner' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.background_picture_of_the_profile_banner' }}</h3>
|
||||
<div class="custom-profile-image" style="background-image: url({{profileImage}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!profileImage" class="img-responsive">
|
||||
<img base-sixty-four-image="profileImage" ng-show="profileImage && profileImage.base64">
|
||||
@ -278,7 +294,7 @@
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_profile_banner' | translate }}
|
||||
<i class="fa fa-edit"></i> {{ 'settings.change_the_profile_banner' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
|
@ -2,21 +2,21 @@
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4 translate>{{ 'news_of_the_home_page' }}</h4>
|
||||
<div ng-model="homeBlogpostSetting.value" class="well" medium-editor options='{"placeholder": "{{ "type_your_news_here" | translate }}",
|
||||
<h4 translate>{{ 'settings.news_of_the_home_page' }}</h4>
|
||||
<div ng-model="homeBlogpostSetting.value" class="well" medium-editor options='{"placeholder": "{{ "settings.type_your_news_here" | translate }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]}'></div>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'leave_it_empty_to_not_bring_up_any_news_on_the_home_page' | translate }}</span>
|
||||
<span class="help-block text-info text-xs"><i class="fa fa-lightbulb-o"></i> {{ 'settings.leave_it_empty_to_not_bring_up_any_news_on_the_home_page' | translate }}</span>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(homeBlogpostSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4 translate>{{ 'twitter_stream' }}</h4>
|
||||
<h4 translate>{{ 'settings.twitter_stream' }}</h4>
|
||||
<form role="form" class="form-inline" name="twitterForm" novalidate>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-twitter"></i>
|
||||
</div>
|
||||
<input type="text" ng-model="twitterSetting.value" class="form-control" placeholder="{{ 'name_of_the_twitter_account' | translate }}"/>
|
||||
<input type="text" ng-model="twitterSetting.value" class="form-control" placeholder="{{ 'settings.name_of_the_twitter_account' | translate }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'customize_the_application' }}</h1>
|
||||
<h1 translate>{{ 'settings.customize_the_application' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -20,18 +20,18 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'general' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.general' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/general.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'home_page' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.home_page' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/home_page.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'about' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.about' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/about.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
<uib-tab heading="{{ 'reservations' | translate }}">
|
||||
<uib-tab heading="{{ 'settings.reservations' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/reservations.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -1,20 +1,20 @@
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'reservations_parameters' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.reservations_parameters' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'confine_the_booking_agenda' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.confine_the_booking_agenda' }}</h3>
|
||||
<div class="col-md-2">
|
||||
<h4 translate>{{ 'opening_time' }}</h4>
|
||||
<h4 translate>{{ 'settings.opening_time' }}</h4>
|
||||
<uib-timepicker ng-model="windowStart.value" hour-step="timepicker.hstep" minute-step="timepicker.mstep" show-meridian="false"></uib-timepicker>
|
||||
</div>
|
||||
<div class="col-md-4 m-t">
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(windowStart)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<h4 translate>{{ 'closing_time' }}</h4>
|
||||
<h4 translate>{{ 'settings.closing_time' }}</h4>
|
||||
<uib-timepicker ng-model="windowEnd.value" hour-step="timepicker.hstep" minute-step="timepicker.mstep" show-meridian="false"></uib-timepicker>
|
||||
</div>
|
||||
<div class="col-md-4 m-t">
|
||||
@ -22,23 +22,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'ability_for_the_users_to_move_their_reservations' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.ability_for_the_users_to_move_their_reservations' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableMove" class="control-label m-r" translate>{{ 'reservations_shifting' }}</label>
|
||||
<label for="enableMove" class="control-label m-r" translate>{{ 'settings.reservations_shifting' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="enableMove.value"
|
||||
id="enableMove"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'enabled' | translate }}"
|
||||
switch-off-text="{{ 'disabled' | translate }}"
|
||||
switch-on-text="{{ 'settings.enabled' | translate }}"
|
||||
switch-off-text="{{ 'settings.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(enableMove)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="enableMove.value">
|
||||
<form class="col-md-4" name="moveDelayForm">
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -51,23 +51,23 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'ability_for_the_users_to_cancel_their_reservations' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.ability_for_the_users_to_cancel_their_reservations' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableCancel" class="control-label m-r" translate>{{ 'reservations_cancelling' }}</label>
|
||||
<label for="enableCancel" class="control-label m-r" translate>{{ 'settings.reservations_cancelling' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="enableCancel.value"
|
||||
id="enableCancel"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'enabled' | translate }}"
|
||||
switch-off-text="{{ 'disabled' | translate }}"
|
||||
switch-on-text="{{ 'settings.enabled' | translate }}"
|
||||
switch-off-text="{{ 'settings.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(enableCancel)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="enableCancel.value">
|
||||
<form class="col-md-4" name="cancelDelayForm">
|
||||
<label for="cancelDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<label for="cancelDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -85,27 +85,27 @@
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'reservations_reminders' }}</span>
|
||||
<span class="font-sbold" translate>{{ 'settings.reservations_reminders' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'notification_sending_before_the_reservation_occurs' }}</h3>
|
||||
<h3 class="m-l" translate>{{ 'settings.notification_sending_before_the_reservation_occurs' }}</h3>
|
||||
<div class="form-group m-l">
|
||||
<label for="enableReminder" class="control-label m-r" translate>{{ 'reservations_reminders' }}</label>
|
||||
<label for="enableReminder" class="control-label m-r" translate>{{ 'settings.reservations_reminders' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="enableReminder.value"
|
||||
id="enableReminder"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'enabled' | translate }}"
|
||||
switch-off-text="{{ 'disabled' | translate }}"
|
||||
switch-on-text="{{ 'settings.enabled' | translate }}"
|
||||
switch-off-text="{{ 'settings.disabled' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(enableReminder)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="enableReminder.value">
|
||||
<form class="col-md-4" name="reminderDelayForm">
|
||||
<label for="reminderDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<label for="reminderDelay" class="control-label m-r" translate>{{ 'settings.prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
@ -114,7 +114,7 @@
|
||||
<input type="number" class="form-control" id="reminderDelay" ng-model="reminderDelay.value" min="0">
|
||||
</div>
|
||||
<span class="help-block text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'default_value_is_24_hours' | translate }}
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'settings.default_value_is_24_hours' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(reminderDelay)" ng-disabled="reminderDelayForm.$invalid" translate>{{ 'save' }}</button>
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
<uib-tab ng-repeat="stat in statistics" heading="{{stat.label}}" select="setActiveTab(stat)" ng-if="stat.table && !(stat.es_type_key == 'subscription' && fablabWithoutPlans)">
|
||||
<uib-tab ng-repeat="stat in statistics" heading="{{stat.label}}" select="setActiveTab(stat)" ng-hide="hiddenTab(stat)">
|
||||
<form id="filters_form" name="filters_form" class="form-inline m-t-md m-b-lg" novalidate="novalidate">
|
||||
<div id="agePickerPane" class="form-group datepicker-container" style="z-index:102;">
|
||||
<button id="agePickerExpand" class="btn btn-default" type="button" ng-click="agePicker.show = !agePicker.show">
|
||||
|
@ -7,14 +7,14 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md hide-b-r-lg">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'calendar' }}</h1>
|
||||
<h1 translate>{{ 'calendar.calendar' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md hidden-lg">
|
||||
<div class="heading-actions wrapper">
|
||||
<button type="button" class="btn btn-default m-t m-b" ng-click="openFilterAside()">
|
||||
<span class="fa fa-filter"></span> {{ 'filter-calendar' | translate }}
|
||||
<span class="fa fa-filter"></span> {{ 'calendar.filter_calendar' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,7 +38,7 @@
|
||||
<div class="col-lg-3 hidden-md hidden-sm hidden-xs">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'filter-calendar' }}</h3>
|
||||
<h3 translate>{{ 'calendar.filter_calendar' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper calendar-filter">
|
||||
<ng-include src="'<%= asset_path 'calendar/filter.html' %>'"></ng-include>
|
||||
@ -53,7 +53,7 @@
|
||||
<div class="widget">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close($event)"><span>×</span></button>
|
||||
<h1 class="modal-title" translate>{{ 'filter-calendar' }}</h1>
|
||||
<h1 class="modal-title" translate>{{ 'calendar.filter_calendar' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body widget-content calendar-filter calendar-filter-aside">
|
||||
<ng-include src="'<%= asset_path 'calendar/filter.html' %>'"></ng-include>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div>
|
||||
<div class="row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-purple" translate>{{ 'trainings' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-purple" translate>{{ 'calendar.trainings' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.trainings" ng-change="toggleFilter('trainings', filter)">
|
||||
</div>
|
||||
<div ng-repeat="t in trainings" class="row">
|
||||
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="m-t">
|
||||
<div class="row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-beige" translate>{{ 'machines' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-beige" translate>{{ 'calendar.machines' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.machines" ng-change="toggleFilter('machines', filter)">
|
||||
</div>
|
||||
<div ng-repeat="m in machines" class="row">
|
||||
@ -18,11 +18,21 @@
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="m.checked" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-t">
|
||||
<div class="row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-cyan" translate>{{ 'calendar.spaces' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.spaces" ng-change="toggleFilter('spaces', filter)">
|
||||
</div>
|
||||
<div ng-repeat="s in spaces" class="row">
|
||||
<span class="col-md-11 col-sm-11 col-xs-11">{{::s.name}}</span>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="s.checked" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-t row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-japonica" translate>{{ 'events' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-japonica" translate>{{ 'calendar.events' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.evt" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
<div class="m-t row">
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-black" translate>{{ 'show_no_disponible' }}</h3>
|
||||
<h3 class="col-md-11 col-sm-11 col-xs-11 text-black" translate>{{ 'calendar.show_unavailables' }}</h3>
|
||||
<input class="col-md-1 col-sm-1 col-xs-1" type="checkbox" ng-model="filter.dispo" ng-change="filterAvailabilities(filter)">
|
||||
</div>
|
||||
|
@ -40,7 +40,16 @@
|
||||
<div class="form-group" ng-class="{'has-error': eventForm['event[description]'].$dirty && eventForm['event[description]'].$invalid}">
|
||||
<label for="description" class="col-sm-3 control-label">{{ 'description' | translate }} *</label>
|
||||
<div class="col-sm-9">
|
||||
<textarea ng-model="event.description" rows="16" class="form-control" id="event_description" placeholder="" name="event[description]" required></textarea>
|
||||
<input type="hidden"
|
||||
name="event[description]"
|
||||
ng-value="event.description" />
|
||||
<summernote ng-model="event.description"
|
||||
id="event_description"
|
||||
placeholder=""
|
||||
config="summernoteOpts"
|
||||
name="event[description]"
|
||||
required>
|
||||
</summernote>
|
||||
<span class="help-block" ng-show="eventForm['event[description]'].$dirty && eventForm['event[description]'].$error.required" translate>{{ 'description_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -237,7 +246,7 @@
|
||||
<span class="help-block" translate>{{ '0_=_free' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-repeat="price in event.prices">
|
||||
<div class="form-group" ng-repeat="price in event.prices" ng-show="!price._destroy">
|
||||
<div class="col-sm-5">
|
||||
<input type="hidden" name="event[event_price_categories_attributes][][id]" ng-value="price.id">
|
||||
<select class="form-control"
|
||||
@ -255,6 +264,10 @@
|
||||
<div class="input-group-addon">{{currencySymbol}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<input type="hidden" name="event[event_price_categories_attributes][][_destroy]" ng-value="price._destroy">
|
||||
<a class="btn" ng-click="removePrice(price, $event)" href="#"><i class="fa fa-times text-danger"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="link-icon m-b" ng-hide="event.prices.length == priceCategories.length">
|
||||
<div class="col-sm-offset-5">
|
||||
|
@ -36,7 +36,7 @@
|
||||
</div>
|
||||
|
||||
<h3 translate>{{ 'event_description' }}</h3>
|
||||
<p ng-bind-html="event.description | breakFilter"></p>
|
||||
<p ng-bind-html="event.description | toTrusted"></p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@
|
||||
|
||||
<section class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'informations_and_booking' }}</h3>
|
||||
<h3 translate>{{ 'information_and_booking' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="panel-content wrapper">
|
||||
@ -164,7 +164,7 @@
|
||||
|
||||
<button class="btn btn-warning-full rounded btn-block text-sm" ng-click="reserveEvent()" ng-show="event.nb_free_places > 0 && !reserve.toReserve">{{ 'book' | translate }}</button>
|
||||
|
||||
<coupon show="reserve.totalSeats > 0 && ctrl.member" coupon="coupon.applied" user-id="{{ctrl.member.id}}"></coupon>
|
||||
<coupon show="reserve.totalSeats > 0 && ctrl.member" coupon="coupon.applied" total="reserve.totalNoCoupon" user-id="{{ctrl.member.id}}"></coupon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -178,6 +178,13 @@
|
||||
|
||||
</section>
|
||||
|
||||
<uib-alert type="warning m" ng-if="eventExplicationsAlert.length > 0">
|
||||
<p class="text-sm pull-left">
|
||||
<i class="fa fa-warning"></i>
|
||||
<div class="m-l-lg" ng-bind-html="eventExplicationsAlert"></div>
|
||||
</p>
|
||||
</uib-alert>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -100,7 +100,7 @@
|
||||
<span class="v-middle badge text-xs" ng-class="'bg-{{event.category.name | lowercase}}'">{{event.category.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>{{event.description | humanize : 500 }}</p>
|
||||
<p ng-bind-html="event.description | simpleText | humanize : 500 | breakFilter"></p>
|
||||
|
||||
<hr/>
|
||||
<div class="row">
|
||||
|
@ -29,178 +29,25 @@
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-show="!ctrl.member && currentUser.role == 'admin' && eventsReserved.length == 0 && (!paidMachineSlots || paidMachineSlots.length == 0) && !slotToModify && !modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_one_or_more_slots_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="ctrl.member && !slotToModify && !modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="eventsReserved.length == 0 && (!paidMachineSlots || paidMachineSlots.length == 0)">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_one_or_more_slots_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="eventsReserved.length > 0">
|
||||
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'you_ve_just_selected_the_slot' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light" ng-repeat="machineSlot in eventsReserved">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(machineSlot.start | amDateFormat:'LLLL'), END_TIME:(machineSlot.end | amDateFormat:'LT') } }}</div>
|
||||
<div class="text-base">{{ 'cost_of_a_machine_hour' | translate }} <span ng-class="{'text-blue': !machineSlot.promo, 'red': machineSlot.promo}">{{machineSlot.price | currency}}</span></div>
|
||||
<div ng-show="currentUser.role == 'admin'" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'offer_this_slot' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="machineSlot.offered"
|
||||
id="offerSlot"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate}}"
|
||||
switch-off-text="{{ 'no' | translate}}"
|
||||
switch-animate="true"
|
||||
switch-readonly="{{machineSlot.isValid}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validMachineSlot(machineSlot)" ng-if="!machineSlot.isValid" translate>{{ 'confirm_this_slot' }}</button>
|
||||
</div>
|
||||
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeMachineSlot(machineSlot, $event)" ng-if="machineSlot.isValid" translate>{{ 'remove_this_slot' }}</a></div>
|
||||
</div>
|
||||
|
||||
<coupon show="machineSlotsValid() && (!plansAreShown || selectedPlan)" coupon="coupon.applied" has-select-slot="machineSlotsValid()" user-id="{{ctrl.member.id}}"></coupon>
|
||||
|
||||
<span ng-hide="fablabWithoutPlans">
|
||||
<div ng-if="machineSlotsValid() && !ctrl.member.subscribed_plan" ng-show="!plansAreShown">
|
||||
<p class="font-sbold text-base l-h-2x" translate>{{ 'to_benefit_from_attractive_prices' }}</p>
|
||||
<div><button class="btn btn-warning-full rounded btn-block text-xs" ng-click="showPlans()" translate>{{ 'view_our_subscriptions' }}</button></div>
|
||||
<p class="font-bold text-base text-u-c text-center m-b-xs" translate>{{ 'or' }}</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_ve_just_selected_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body m-b-md">
|
||||
<div class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</div>
|
||||
<div class="text-base">{{ 'cost_of_the_subscription' | translate }} <span class="text-blue">{{selectedPlan.amount | currency}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="eventsReserved.length > 0">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="payMachine()" ng-if="machineSlotsValid() && (!plansAreShown || selectedPlan)">{{ 'confirm_and_pay' | translate }} {{amountTotal | currency}}</button>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="paidMachineSlots">
|
||||
{{ 'you_have_settled_the_following_machine_hours' | translate }} <strong>{{machine.name}}</strong>:
|
||||
|
||||
<div class="well well-warning m-t-sm" ng-repeat="paidSlot in paidMachineSlots">
|
||||
<i class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(paidSlot.start | amDateFormat:'LLLL'), END_TIME:(paidSlot.end | amDateFormat:'LT') } }}</i>
|
||||
<div class="font-sbold">{{ 'cost_of_a_machine_hour' | translate }} {{paidSlot.machine.amount() | currency}}</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_have_settled_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</i>
|
||||
<div class="font-sbold">{{ 'cost_of_the_subscription' | translate }} {{selectedPlan.amount | currency}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t-md font-sbold">{{ 'total_' | translate }} {{amountTotal | currency}}</div>
|
||||
|
||||
<div class="alert alert-success" ng-if="ctrl.member.subscribed_plan">{{ 'thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
{{ 'your_invoice_will_be_available_soon_from_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'dashboard' }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="slotToModify || modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="slotToModify">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'i_want_to_change_the_following_reservation' }}</div>
|
||||
|
||||
<div class="panel panel-warning bg-yellow">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToModify.start | amDateFormat:'LLLL'), END_TIME:(slotToModify.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToModify($event)" translate>{{ 'cancel_my_modification' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_a_new_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info bg-info text-white" ng-if="slotToPlace">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToPlace.start | amDateFormat:'LLLL'), END_TIME:(slotToPlace.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToPlace($event)" translate>{{ 'cancel_my_selection' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="slotToPlace && slotToModify.tags.length > 0 && slotToPlace.tags.length > 0" ng-class="{'panel panel-danger bg-red': tagMissmatch()}">
|
||||
<div class="panel-body">
|
||||
<div id="fromTags">
|
||||
{{ 'tags_of_the_original_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in slotToModify.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
</div><br/>
|
||||
<div id="toTags">
|
||||
{{ 'tags_of_the_destination_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in slotToPlace.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="slotToModify && slotToPlace">
|
||||
<button class="btn btn-invalid btn-default btn-block p-l btn-lg text-u-c r-n text-base" ng-click="cancelModifyMachineSlot()" translate>{{ 'cancel' }}</button>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="modifyMachineSlot()" translate>{{ 'confirm_my_modification' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="modifiedSlots">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'your_booking_slot_was_successfully_moved_from_' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.oldReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.oldReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center font-bold m-b-sm text-u-c" translate>{{ 'to_date' }}</p>
|
||||
|
||||
<div class="panel panel-success bg-success bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.newReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.newReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<cart slot="selectedEvent"
|
||||
slot-selection-time="selectionTime"
|
||||
events="events"
|
||||
user="ctrl.member"
|
||||
mode-plans="plansAreShown"
|
||||
plan="selectedPlan"
|
||||
plan-selection-time="planSelectionTime"
|
||||
settings="settings"
|
||||
on-slot-added-to-cart="markSlotAsAdded"
|
||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||
on-slot-start-to-modify="markSlotAsModifying"
|
||||
on-slot-modify-success="modifyMachineSlot"
|
||||
on-slot-modify-cancel="cancelModifyMachineSlot"
|
||||
on-slot-modify-unselect="changeModifyMachineSlot"
|
||||
on-slot-cancel-success="slotCancelled"
|
||||
after-payment="afterPayment"
|
||||
reservable-id="{{machine.id}}"
|
||||
reservable-type="Machine"
|
||||
reservable-name="{{machine.name}}"></cart>
|
||||
|
||||
<uib-alert type="warning m">
|
||||
<p class="text-sm">
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t-sm m-b" ng-click="markAllAsRead()" ng-disabled="notifications.length == 0">{{ 'mark_all_as_read' | translate }} ({{notifications.length}})</button>
|
||||
<button type="button" class="btn btn-warning m-t-sm m-b" ng-click="markAllAsRead()" ng-disabled="totalUnread == 0">{{ 'mark_all_as_read' | translate }} ({{totalUnread}})</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
@ -31,7 +31,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="notification in notifications" ng-if="notifications.length > 0">
|
||||
<tr ng-repeat="notification in notificationsUnread" ng-if="notificationsUnread.length > 0">
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" ng-click="markAsRead(notification, $event)">
|
||||
<i class="fa fa-check"></i>
|
||||
@ -41,45 +41,47 @@
|
||||
<td ng-bind-html="notification.message.description"></td>
|
||||
|
||||
</tr>
|
||||
<tr ng-if="notifications.length == 0">
|
||||
<tr ng-if="notificationsUnread.length == 0">
|
||||
<td colspan="3" translate>{{ 'no_new_notifications' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h5 translate>{{ 'archives' }}</h5>
|
||||
<div ng-hide="notificationsRead.length == 0 && notificationsUnread.length < total">
|
||||
<h5 translate>{{ 'archives' }}</h5>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:10%"></th>
|
||||
<th style="width:20%"></th>
|
||||
<th style="width:70%"></th>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:10%"></th>
|
||||
<th style="width:20%"></th>
|
||||
<th style="width:70%"></th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
|
||||
|
||||
<tr class="read" ng-repeat="n in notificationsRead | orderBy:'created_at':true" ng-if="notificationsRead.length > 0">
|
||||
<td>
|
||||
</td>
|
||||
<td>{{ n.created_at | amDateFormat:'LLL' }}</td>
|
||||
<td ng-bind-html="n.message.description"></td>
|
||||
<tr class="read" ng-repeat="n in notificationsRead | orderBy:'created_at':true" ng-if="notificationsRead.length > 0">
|
||||
<td>
|
||||
</td>
|
||||
<td>{{ n.created_at | amDateFormat:'LLL' }}</td>
|
||||
<td ng-bind-html="n.message.description"></td>
|
||||
|
||||
</tr>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr ng-if="notificationsRead.length == 0">
|
||||
<td colspan="3" translate>{{ 'no_archived_notifications' }}</td>
|
||||
</tr>
|
||||
<tr ng-if="notificationsRead.length == 0">
|
||||
<td colspan="3" translate>{{ 'no_archived_notifications' }}</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-default" ng-click="addMoreNotificationsReaded()" ng-if="paginateActive" translate>{{ 'load_the_next_notifications' }}</a>
|
||||
<a class="btn btn-default" ng-click="addMoreNotifications()" ng-if="paginateActive" translate>{{ 'load_the_next_notifications' }}</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -61,7 +61,7 @@
|
||||
</div>
|
||||
|
||||
<br ng-show="!plan.plan_file_url"> <!-- TODO Refacto with CSS -->
|
||||
<a ng-href="{{ plan.plan_file_url }}" ng-show="plan.plan_file_url" target="_blank" translate>{{ 'more_informations' }}</a>
|
||||
<a ng-href="{{ plan.plan_file_url }}" ng-show="plan.plan_file_url" target="_blank" translate>{{ 'more_information' }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -141,7 +141,7 @@
|
||||
<div class="font-sbold">{{ 'subscription_price' | translate }} {{selectedPlan.amount | currency}}</div>
|
||||
</div>
|
||||
|
||||
<coupon show="!ctrl.member.subscribed_plan" coupon="coupon.applied" user-id="{{ctrl.member.id}}"></coupon>
|
||||
<coupon show="!ctrl.member.subscribed_plan" coupon="coupon.applied" total="selectedPlan.amount" user-id="{{ctrl.member.id}}"></coupon>
|
||||
</div>
|
||||
|
||||
<div class="widget-footer">
|
||||
|
@ -3,6 +3,7 @@
|
||||
<h3 translate>{{ 'do_you_already_have_an_account' }}</h3>
|
||||
<p ng-hide="hasDuplicate()" translate>{{ 'do_not_fill_the_form_beside_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access' }}</p>
|
||||
<p ng-show="hasDuplicate()" translate>{{ 'just_specify_code_here_to_recover_access' }}</p>
|
||||
<p class="pull-right"><a href="#" ng-click="resendCode($event)" translate>{{ 'i_did_not_receive_the_code' }}</a></p>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-lg-offset-1 hidden-md col-sm-3 col-sm-offset-1"></div>
|
||||
<div class="col-lg-offset-1 col-lg-6 col-md-12 col-sm-offset-1 col-sm-6">
|
||||
|
@ -28,7 +28,7 @@
|
||||
{{ 'you_ve_just_created_a_new_account_on_the_fablab_by_logging_from' | translate:{ GENDER: nameGenre, NAME: fablabName }:"messageformat" }}<br/>
|
||||
<img class="m-l v-middle" height="16" width="16" src='https://www.google.com/s2/favicons?domain={{activeProvider.domain}}' />
|
||||
<strong class="v-middle">{{activeProvider.name}} <span ng-if="ssoEmail()">({{ssoEmail()}})</span></strong><br/>
|
||||
<p class="m-t-md" ng-hide="hasDuplicate()" translate>{{ 'before_letting_you_use_the_application_we_need_some_more_details' }}.</p>
|
||||
<p class="m-t-md" ng-hide="hasDuplicate()" translate>{{ 'we_need_some_more_details' }}.</p>
|
||||
<p class="m-t-md" ng-show="hasDuplicate()" translate>{{ 'your_email_is_already_used_by_another_account_on_the_platform' }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
26
app/assets/templates/profile/resend_code_modal.html
Normal file
26
app/assets/templates/profile/resend_code_modal.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>{{ 'send_code_again' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<form name="emailForm">
|
||||
<label for="email" class="beforeAmount" translate>{{ 'email_address_associated_with_your_account' }}</label>
|
||||
<div class="input-group" ng-class="{'has-error': emailForm.email.$dirty && emailForm.email.$invalid }">
|
||||
<span class="input-group-addon"><i class="fa fa-envelope"></i> </span>
|
||||
<input class="form-control"
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
ng-model="object.email"
|
||||
required>
|
||||
</div>
|
||||
<span class="help-block error" ng-show="emailForm['email'].$dirty && emailForm['email'].$error.required" translate>{{'email_is_required'}}</span>
|
||||
<span class="help-block error" ng-show="emailForm['email'].$dirty && emailForm['email'].$error.email" translate>{{'email_format_is_incorrect'}}</span>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok(object.email)" ng-disabled="emailForm.$invalid" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
@ -42,18 +42,22 @@
|
||||
<label class="col-sm-2 control-label" translate>{{ 'CAD_file' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div ng-repeat="file in project.project_caos_attributes" ng-show="!file._destroy">
|
||||
<input type="hidden" name="project[project_caos_attributes][][id]" ng-value="file.id" />
|
||||
<input type="hidden" name="project[project_caos_attributes][][_destroy]" ng-value="file._destroy" />
|
||||
<div class="col-md-11 m-l-n">
|
||||
<input type="hidden" name="project[project_caos_attributes][][id]" ng-value="file.id" />
|
||||
<input type="hidden" name="project[project_caos_attributes][][_destroy]" ng-value="file._destroy" />
|
||||
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(file.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(file.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
|
||||
</div>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'browse' }}</span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span><input type="file" name="project[project_caos_attributes][][attachment]"></span>
|
||||
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
|
||||
</div>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'browse' }}</span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span><input type="file" name="project[project_caos_attributes][][attachment]"></span>
|
||||
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 m-t-xs">
|
||||
<i class="fa fa-info-circle" aria-hidden="true" uib-tooltip="{{ 'allowed_extensions' | translate }} {{allowedExtensions.join(', ')}}" tooltip-placement="bottom" tooltip-class="media-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-default" ng-click="addFile()" role="button">{{ 'add_a_new_file' | translate }} <i class="fa fa-file-o fa-fw"></i></a>
|
||||
</div>
|
||||
@ -96,15 +100,32 @@
|
||||
<input type="hidden" name="project[project_steps_attributes][][description]" ng-value="step.description" />
|
||||
<summernote ng-model="step.description" placeholder="" config="summernoteOpts" name=project[project_steps_attributes][][description]></summernote>
|
||||
|
||||
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(step.project_step_image)">
|
||||
<span class="btn btn-default btn-file"><span class="fileinput-new">{{ 'add_a_picture' | translate }} <i class="fa fa-file-image-o m-l-sm" aria-hidden="true"></i></span><span class="fileinput-exists" translate>{{ 'change_the_picture' }}</span>
|
||||
<input type="file"
|
||||
name="project[project_steps_attributes][][project_step_image_attributes][attachment]"></span>
|
||||
<span class="fileinput-filename">{{step.project_step_image}}</span>
|
||||
<a class="close fileinput-exists" data-dismiss="fileinput" style="float: none"><i class="fa fa-trash-o"></i></a>
|
||||
<div class="row">
|
||||
<div ng-repeat-start="image in step.project_step_images_attributes" class="clearfix" ng-if="$index % 3 == 0"></div>
|
||||
<div class="col-md-4" ng-repeat-end ng-show="!image._destroy">
|
||||
<input type="hidden" name="project[project_steps_attributes][][project_step_images_attributes][][id]" ng-value="image.id" />
|
||||
<input type="hidden" name="project[project_steps_attributes][][project_step_images_attributes][][_destroy]" ng-value="image._destroy" />
|
||||
|
||||
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(image.attachment)" style="width: 100%;">
|
||||
<div class="fileinput-new thumbnail" style="width: 100%; height: 200px;">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!image.attachment">
|
||||
</div>
|
||||
<div class="fileinput-preview fileinput-exists thumbnail" data-trigger="fileinput" style="max-width: 334px;">
|
||||
<img ng-src="{{ image.attachment_url }}" alt="{{image.attachment}}" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="btn btn-default btn-file"><span class="fileinput-new">{{ 'browse' | translate }} <i class="fa fa-upload fa-fw"></i></span><span class="fileinput-exists" translate>{{ 'change' }}</span>
|
||||
<input type="file" name="project[project_steps_attributes][][project_step_images_attributes][][attachment]"></span>
|
||||
<a class="btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteProjectStepImage(step, image)" translate>{{ 'delete' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn btn-default" ng-click="addProjectStepImage(step)" role="button">{{ 'add_a_picture' | translate }} <i class="fa fa-file-o fa-fw"></i></a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="m-t">
|
||||
<a class="btn btn-sm btn-danger" ng-click="deleteStep(step)" role="button"><i class="fa fa-trash-o m-r-xs"></i> {{ 'delete_the_step' | translate }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,7 +90,7 @@
|
||||
<div class="card-header-bg" style="background-image: url({{project.project_image}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!project.project_image">
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-block">
|
||||
|
@ -42,10 +42,11 @@
|
||||
<div class="col-md-12 m-b-xs">
|
||||
<h3 class="well well-simple step-title">{{ 'step_N' | translate:{INDEX:step.step_nb} }} : {{step.title}}</h3>
|
||||
</div>
|
||||
<div class="col-md-4" ng-if="step.project_step_image">
|
||||
<a href="{{step.project_step_full_image_url}}" target="_blank"><img class="img-responsive m-b" ng-src="{{step.project_step_image_url}}" alt="{{step.title}}" ></a>
|
||||
<div ng-repeat-start="image in step.project_step_images_attributes" class="clearfix" ng-if="$index % 3 == 0"></div>
|
||||
<div class="col-md-4" ng-repeat-end>
|
||||
<a href="{{image.attachment_full_url}}" target="_blank"><img class="img-responsive m-b" ng-src="{{image.attachment_url}}" alt="{{image.attachment}}" ></a>
|
||||
</div>
|
||||
<div class="col-md-8" ng-class="{'col-md-12' : step.project_step_image == undefined}">
|
||||
<div class="col-md-8" ng-class="{'col-md-12' : step.project_step_images_attributes.length > 1 || step.project_step_images_attributes.length == 0}">
|
||||
|
||||
<p ng-bind-html="step.description | toTrusted"></p>
|
||||
</div>
|
||||
|
167
app/assets/templates/shared/_cart.html.erb
Normal file
167
app/assets/templates/shared/_cart.html.erb
Normal file
@ -0,0 +1,167 @@
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="user && !events.modifiable && !events.moved">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'cart.summary' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="events.reserved.length == 0 && (!events.paid || events.paid.length == 0)">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag('fleche-left.png', class: 'fleche-left visible-lg') %>
|
||||
{{ 'cart.select_one_or_more_slots_in_the_calendar' | translate:{SINGLE:limitToOneSlot}:"messageformat" }}</p>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="events.reserved.length > 0">
|
||||
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'cart.you_ve_just_selected_the_slot' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light" ng-repeat="slot in events.reserved">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(slot.start | amDateFormat:'LLLL'), END_TIME:(slot.end | amDateFormat:'LT') } }}</div>
|
||||
<div class="text-base">{{ 'cart.cost_of_TYPE' | translate:{TYPE:reservableType}:"messageformat" }} <span ng-class="{'text-blue': !slot.promo, 'red': slot.promo}">{{slot.price | currency}}</span></div>
|
||||
<div ng-show="isAdmin()" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'cart.offer_this_slot' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="slot.offered"
|
||||
id="offerSlot"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate}}"
|
||||
switch-off-text="{{ 'no' | translate}}"
|
||||
switch-animate="true"
|
||||
switch-readonly="{{slot.isValid}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validateSlot(slot)" ng-if="!slot.isValid" translate>{{ 'cart.confirm_this_slot' }}</button>
|
||||
</div>
|
||||
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlot(slot, $index, $event)" ng-if="slot.isValid" translate>{{ 'cart.remove_this_slot' }}</a></div>
|
||||
</div>
|
||||
|
||||
<coupon show="isSlotsValid() && (!modePlans || selectedPlan)" coupon="coupon.applied" total="totalNoCoupon" user-id="{{user.id}}"></coupon>
|
||||
|
||||
<div ng-hide="fablabWithoutPlans">
|
||||
<div ng-if="isSlotsValid() && !user.subscribed_plan" ng-show="!modePlans">
|
||||
<p class="font-sbold text-base l-h-2x" translate>{{ 'cart.to_benefit_from_attractive_prices' }}</p>
|
||||
<div><button class="btn btn-warning-full rounded btn-block text-xs" ng-click="showPlans()" translate>{{ 'cart.view_our_subscriptions' }}</button></div>
|
||||
<p class="font-bold text-base text-u-c text-center m-b-xs" translate>{{ 'cart.or' }}</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'cart.you_ve_just_selected_a_' | translate }} <br> <span class="font-sbold" translate>{{ 'cart._subscription' }}</span> :</div>
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body m-b-md">
|
||||
<div class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</div>
|
||||
<div class="text-base">{{ 'cart.cost_of_the_subscription' | translate }} <span class="text-blue">{{selectedPlan.amount | currency}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="events.reserved.length > 0">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="payCart()" ng-if="isSlotsValid() && (!modePlans || selectedPlan)">{{ 'cart.confirm_and_pay' | translate }} {{amountTotal | currency}}</button>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="events.paid && events.paid.length > 0">
|
||||
{{ 'cart.you_have_settled_the_following_TYPE' | translate:{TYPE:reservableType}:"messageformat" }} <strong>{{reservableName}}</strong>:
|
||||
|
||||
<div class="well well-warning m-t-sm" ng-repeat="paidSlot in events.paid">
|
||||
<i class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(paidSlot.start | amDateFormat:'LLLL'), END_TIME:(paidSlot.end | amDateFormat:'LT') } }}</i>
|
||||
<div class="font-sbold">{{ 'cart.cost_of_TYPE' | translate:{TYPE:reservableType}:"messageformat" }} {{paidSlot.price | currency}}</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'cart.you_have_settled_a_' | translate }} <br> <span class="font-sbold" translate>{{ 'cart._subscription' }}</span> :</div>
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</i>
|
||||
<div class="font-sbold">{{ 'cart.cost_of_the_subscription' | translate }} {{selectedPlan.amount | currency}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t-md font-sbold">{{ 'cart.total_' | translate }} {{amountTotal | currency}}</div>
|
||||
|
||||
<div class="alert alert-success" ng-if="user.subscribed_plan">{{ 'cart.thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
{{ 'cart.your_invoice_will_be_available_soon_from_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'cart.dashboard' }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="events.modifiable || events.moved">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'cart.summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="events.modifiable">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'cart.i_want_to_change_the_following_reservation' }}</div>
|
||||
|
||||
<div class="panel panel-warning bg-yellow">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.modifiable.start | amDateFormat:'LLLL'), END_TIME:(events.modifiable.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="cancelModifySlot($event)" translate>{{ 'cart.cancel_my_modification' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag('fleche-left.png', class: 'fleche-left visible-lg') %>
|
||||
{{ 'cart.select_a_new_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info bg-info text-white" ng-if="events.placable">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.placable.start | amDateFormat:'LLLL'), END_TIME:(events.placable.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToPlace($event)" translate>{{ 'cart.cancel_my_selection' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="events.placable && (events.modifiable.tags.length > 0 || events.placable.tags.length > 0)" ng-class="{'panel panel-danger bg-red': tagMissmatch()}">
|
||||
<div class="panel-body">
|
||||
<div id="fromTags">
|
||||
{{ 'cart.tags_of_the_original_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in events.modifiable.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
<span ng-show="events.modifiable.tags.length == 0">
|
||||
<span class='label label-warning text-white' title="{{ 'cart.none' | translate }}" translate>{{ 'cart.none' }}</span>
|
||||
</span>
|
||||
</div><br/>
|
||||
<div id="toTags">
|
||||
{{ 'cart.tags_of_the_destination_slot' | translate }}<br/>
|
||||
<span ng-repeat="tag in events.placable.tags">
|
||||
<span class='label label-success text-white' title="{{tag.name}}">{{tag.name}}</span>
|
||||
</span>
|
||||
<span ng-show="events.placable.tags.length == 0">
|
||||
<span class='label label-warning text-white' title="{{ 'cart.none' | translate }}" translate>{{ 'cart.none' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="events.modifiable && events.placable">
|
||||
<button class="btn btn-invalid btn-default btn-block p-l btn-lg text-u-c r-n text-base" ng-click="cancelModifySlot()" translate>{{ 'cancel' }}</button>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="modifySlot()" translate>{{ 'cart.confirm_my_modification' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="events.moved">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'cart.your_booking_slot_was_successfully_moved_from_' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.moved.oldSlot.start | amDateFormat:'LLLL'), END_TIME:(events.moved.oldSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center font-bold m-b-sm text-u-c" translate>{{ 'cart.to_date' }}</p>
|
||||
|
||||
<div class="panel panel-success bg-success bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'cart.datetime_to_time' | translate:{START_DATETIME:(events.moved.newSlot.start | amDateFormat:'LLLL'), END_TIME:(events.moved.newSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -3,7 +3,7 @@
|
||||
|
||||
<div ng-show="code.input">
|
||||
<label for="coupon_code" translate>{{ 'code_' }}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group m-b">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="coupon_code"
|
||||
@ -17,5 +17,7 @@
|
||||
<i class="fa fa-check" ng-show="status == 'valid'"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<uib-alert ng-repeat="msg in messages" type="{{msg.type}}" close="closeMessage($index)">{{msg.message}}</uib-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,13 +55,16 @@
|
||||
ng-disabled="preventField['profile.gender'] && user.profile.gender && !userForm['user[profile_attributes][gender]'].$dirty"/>
|
||||
<i class="fa fa-female m-l-sm"></i> {{ 'woman' | translate }}
|
||||
</label>
|
||||
<span class="help-block" ng-show="userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$error.required" translate>{{ 'gender_is_required' }}</span>
|
||||
<span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
|
||||
<span class="help-block" ng-show="userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$error.required" translate>{{ 'gender_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[username]'].$dirty && userForm['user[username]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i></span>
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
<input type="text"
|
||||
name="user[username]"
|
||||
ng-model="user.username"
|
||||
@ -78,7 +81,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][last_name]'].$dirty && userForm['user[profile_attributes][last_name]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i></span>
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][last_name]"
|
||||
ng-model="user.profile.last_name"
|
||||
@ -93,7 +96,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][first_name]'].$dirty && userForm['user[profile_attributes][first_name]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i></span>
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][first_name]"
|
||||
ng-model="user.profile.first_name"
|
||||
@ -108,7 +111,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[email]'].$dirty && userForm['user[email]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-envelope"></i> </span>
|
||||
<span class="input-group-addon"><i class="fa fa-envelope"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="email"
|
||||
name="user[email]"
|
||||
ng-model="user.email"
|
||||
@ -130,7 +133,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[password]'].$dirty && userForm['user[password]'].$invalid}" ng-if="password.change">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-key"></i> </span>
|
||||
<span class="input-group-addon"><i class="fa fa-key"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="password"
|
||||
name="user[password]"
|
||||
ng-model="user.password"
|
||||
@ -146,7 +149,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[password_confirmation]'].$dirty && userForm['user[password_confirmation]'].$invalid}" ng-if="password.change">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-key"></i> </span>
|
||||
<span class="input-group-addon"><i class="fa fa-key"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="password"
|
||||
name="user[password_confirmation]"
|
||||
ng-model="user.password_confirmation"
|
||||
@ -164,7 +167,7 @@
|
||||
|
||||
<div class="form-group" ng-if="user.profile.organization" ng-class="{'has-error': userForm['user[profile_attributes][organization_attributes][name]'].$dirty && userForm['user[profile_attributes][organization_attributes][name]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-building-o"></i></span>
|
||||
<span class="input-group-addon"><i class="fa fa-building-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="hidden"
|
||||
name="user[profile_attributes][organization_attributes][id]"
|
||||
ng-value="user.profile.organization.id" />
|
||||
@ -181,7 +184,7 @@
|
||||
|
||||
<div class="form-group" ng-if="user.profile.organization" ng-class="{'has-error': userForm['user[profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[profile_attributes][organization_attributes][address_attributes][address]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-map-marker"></i></span>
|
||||
<span class="input-group-addon"><i class="fa fa-map-marker"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="hidden"
|
||||
name="user[profile_attributes][organization_attributes][address_attributes][id]"
|
||||
ng-value="user.profile.organization.address.id" />
|
||||
@ -198,7 +201,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][birthday]'].$dirty && userForm['user[profile_attributes][birthday]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar-o"></i> </span>
|
||||
<span class="input-group-addon"><i class="fa fa-calendar-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
id="user_birthday"
|
||||
class="form-control"
|
||||
@ -235,7 +238,7 @@
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][phone]'].$dirty && userForm['user[profile_attributes][phone]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-phone"></i> </span>
|
||||
<span class="input-group-addon"><i class="fa fa-phone"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
|
||||
<input type="text"
|
||||
name="user[profile_attributes][phone]"
|
||||
ng-model="user.profile.phone"
|
||||
@ -313,7 +316,7 @@
|
||||
|
||||
<!-- allow receive newsletter -->
|
||||
<div class="form-group">
|
||||
<label for="allowNewsletter" translate>{{ 'i_accept_to_receive_informations_from_the_fablab' }}</label>
|
||||
<label for="allowNewsletter" translate>{{ 'i_accept_to_receive_information_from_the_fablab' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="user.is_allow_newsletter"
|
||||
id="allowNewsletter"
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h1>{{object.title}}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{object.msg}}</p>
|
||||
<p ng-bind-html="object.msg"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
|
@ -23,7 +23,7 @@
|
||||
<!-- Top Nav -->
|
||||
<ul class="nav navbar-nav navbar-right m-n hidden-xs nav-user user">
|
||||
<li class="notification-open" ng-if="isAuthenticated()">
|
||||
<a ui-sref="app.logged.notifications"><i class="fa fa-bell fa-2x black"></i> <span class="badge" ng-class="{'bg-red': notifications.length > 0}">{{notifications.length}}</span></a>
|
||||
<a ui-sref="app.logged.notifications"><i class="fa fa-bell fa-2x black"></i> <span class="badge" ng-class="{'bg-red': notifications.unread > 0}">{{notifications.unread}}</span></a>
|
||||
</li>
|
||||
<li class="dropdown user-profile-nav" ng-if="isAuthenticated()" uib-dropdown>
|
||||
<a href="#" class="dropdown-toggle" uib-dropdown-toggle>
|
||||
|
@ -217,7 +217,7 @@
|
||||
id="is_allow_newsletter"
|
||||
ng-model="user.is_allow_newsletter"
|
||||
value="true"/>
|
||||
<label for="is_allow_newsletter" translate>{{ 'i_accept_to_receive_informations_from_the_fablab' }}</label>
|
||||
<label for="is_allow_newsletter" translate>{{ 'i_accept_to_receive_information_from_the_fablab' }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
109
app/assets/templates/spaces/_form.html
Normal file
109
app/assets/templates/spaces/_form.html
Normal file
@ -0,0 +1,109 @@
|
||||
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
|
||||
|
||||
<div class="form-group m-b-lg" ng-class="{'has-error': spaceForm['space[name]'].$dirty && spaceForm['space[name]'].$invalid}">
|
||||
<label for="space_name" class="col-sm-2 control-label">{{ 'space.name' | translate }} *</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="space.name"
|
||||
type="text"
|
||||
name="space[name]"
|
||||
class="form-control"
|
||||
id="space_name"
|
||||
placeholder="{{'space.name' | translate}}"
|
||||
required>
|
||||
<span class="help-block" ng-show="spaceForm['space[name]'].$dirty && spaceForm['space[name]'].$error.required" translate>{{ 'space.name_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-lg">
|
||||
<label for="space_image" class="col-sm-2 control-label">{{ 'space.illustration' | translate }} *</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(space.space_image)">
|
||||
<div class="fileinput-new thumbnail" style="width: 334px; height: 250px;">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!space.space_image">
|
||||
</div>
|
||||
<div class="fileinput-preview fileinput-exists thumbnail" style="max-width: 334px;">
|
||||
<img ng-src="{{ space.space_image }}" alt="" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="btn btn-default btn-file">
|
||||
<span class="fileinput-new">{{ 'space.add_an_illustration' | translate }} <i class="fa fa-upload fa-fw"></i></span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span>
|
||||
<input type="file"
|
||||
id="space_image"
|
||||
ng-model="space.space_image"
|
||||
name="space[space_image_attributes][attachment]"
|
||||
accept="image/*"
|
||||
required
|
||||
bs-jasny-fileinput>
|
||||
</span>
|
||||
<a href="#" class="btn btn-danger fileinput-exists" data-dismiss="fileinput" translate>{{ 'delete' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group m-b-xl" ng-class="{'has-error': spaceForm['space[default_places]'].$dirty && spaceForm['space[default_places]'].$invalid}">
|
||||
<label for="default_places" class="col-sm-2 control-label">{{ 'space.default_places' | translate }} *</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number"
|
||||
name="space[default_places]"
|
||||
ng-model="space.default_places"
|
||||
id="default_places"
|
||||
class="form-control"
|
||||
required>
|
||||
<span class="help-block" ng-show="spaceForm['space[default_places]'].$dirty && spaceForm['space[default_places]'].$error.required" translate>{{ 'space.default_places_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label for="space_description" class="col-sm-2 control-label" translate>{{ 'space.description' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden"
|
||||
name="space[description]"
|
||||
ng-value="space.description" />
|
||||
<summernote ng-model="space.description"
|
||||
id="space_description"
|
||||
placeholder=""
|
||||
config="summernoteOpts"
|
||||
name="space[description]">
|
||||
</summernote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label for="space_characteristics" class="col-sm-2 control-label" translate>{{ 'space.characteristics' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden"
|
||||
name="space[characteristics]"
|
||||
ng-value="space.characteristics" />
|
||||
<summernote ng-model="space.characteristics"
|
||||
id="space_characteristics"
|
||||
placeholder=""
|
||||
config="summernoteOpts"
|
||||
name="space[characteristics]">
|
||||
</summernote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-xl">
|
||||
<label class="col-sm-2 control-label" translate>{{ 'space.attached_files_(pdf)' }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div ng-repeat="file in space.space_files_attributes" ng-show="!file._destroy">
|
||||
<input type="hidden" ng-model="file.id" name="space[space_files_attributes][][id]" ng-value="file.id" />
|
||||
<input type="hidden" ng-model="file._destroy" name="space[space_files_attributes][][_destroy]" ng-value="file._destroy"/>
|
||||
|
||||
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass(file.attachment)">
|
||||
<div class="form-control" data-trigger="fileinput">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{file.attachment}}</span>
|
||||
</div>
|
||||
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new" translate>{{ 'space.attach_a_file' }}</span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span><input type="file" name="space[space_files_attributes][][attachment]" accept=".pdf"></span>
|
||||
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<a class="btn btn-default" ng-click="addFile()" role="button"> {{ 'space.add_an_attachment' | translate }} <i class="fa fa-file-o fa-fw"></i></a>
|
||||
</div>
|
||||
</div>
|
50
app/assets/templates/spaces/edit.html.erb
Normal file
50
app/assets/templates/spaces/edit.html.erb
Normal file
@ -0,0 +1,50 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-md-1 hidden-xs">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1 translate translate-values="{NAME: space.name}">{{ 'space_edit.edit_the_space_NAME' }}</h1>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter" >
|
||||
|
||||
<div class="col-md-9 b-r nopadding">
|
||||
<form role="form"
|
||||
name="spaceForm"
|
||||
class="form-horizontal"
|
||||
action="{{ actionUrl }}"
|
||||
ng-upload="submited(content)"
|
||||
upload-options-enable-rails-csrf="true"
|
||||
unsaved-warning-form
|
||||
novalidate>
|
||||
|
||||
<input name="_method" type="hidden" ng-value="method">
|
||||
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'spaces/_form.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit"
|
||||
value="{{ 'space_edit.validate_the_changes' | translate }}"
|
||||
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
|
||||
ng-disabled="spaceForm.$invalid"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3"/>
|
||||
</div>
|
63
app/assets/templates/spaces/index.html.erb
Normal file
63
app/assets/templates/spaces/index.html.erb
Normal file
@ -0,0 +1,63 @@
|
||||
<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 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">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'the_spaces' }}</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-xs" ui-sref="app.admin.space_new" role="button" translate>{{ 'add_a_space' }}</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="m-lg">
|
||||
|
||||
<div class="row" ng-repeat="space in (spaces.length/3 | array)">
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="space in spaces.slice(3*$index, 3*$index + 3)">
|
||||
|
||||
|
||||
<div class="widget panel panel-default">
|
||||
<div class="panel-heading picture" ng-if="!space.space_image" ng-click="showMachine(space)">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder class="img-responsive">
|
||||
</div>
|
||||
<div class="panel-heading picture" style="background-image:url({{space.space_image}})" ng-if="space.space_image" ng-click="showMachine(space)">
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h1 class="text-center m-b">{{space.name}}</h1>
|
||||
</div>
|
||||
<div class="panel-footer no-padder">
|
||||
|
||||
<div class="text-center clearfix">
|
||||
<div class="col-sm-6 b-r no-padder">
|
||||
<div class="btn btn-default btn-block no-b padder-v red" ng-click="reserveSpace(space, $event)">
|
||||
<i class="fa fa-bookmark"></i> {{ 'book' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 no-padder">
|
||||
<div class="btn btn-default btn-block padder-v no-b red" ng-click="showSpace(space)">
|
||||
<i class="fa fa-eye"></i> {{ 'consult' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</section>
|
57
app/assets/templates/spaces/new.html.erb
Normal file
57
app/assets/templates/spaces/new.html.erb
Normal file
@ -0,0 +1,57 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-md-1 hidden-xs">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'space_new.add_a_new_space' }}</h1>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter" >
|
||||
|
||||
<div class="col-md-9 b-r nopadding">
|
||||
|
||||
<div class="m-lg alert alert-warning" role="alert">
|
||||
{{ 'space_new.watch_out_when_creating_a_new_space_its_prices_are_initialized_at_0_for_all_subscriptions' | translate}}
|
||||
{{ 'space_new.consider_changing_its_prices_before_creating_any_reservation_slot' | translate }}
|
||||
</div>
|
||||
|
||||
<form role="form"
|
||||
name="spaceForm"
|
||||
class="form-horizontal"
|
||||
action="{{ actionUrl }}"
|
||||
ng-upload="submited(content)"
|
||||
upload-options-enable-rails-csrf="true"
|
||||
unsaved-warning-form
|
||||
novalidate>
|
||||
|
||||
<input name="_method" type="hidden" ng-value="method">
|
||||
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
<ng-include src="'<%= asset_path 'spaces/_form.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit"
|
||||
value="{{ 'space_new.add_this_space' | translate }}"
|
||||
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
|
||||
ng-disabled="spaceForm.$invalid"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-3"/>
|
||||
</div>
|
59
app/assets/templates/spaces/reserve.html.erb
Normal file
59
app/assets/templates/spaces/reserve.html.erb
Normal file
@ -0,0 +1,59 @@
|
||||
<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 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">
|
||||
<section class="heading-title">
|
||||
<h1 translate translate-values="{NAME:space.name}">{{ 'space_reserve.planning_of_space_NAME' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter training-reserve">
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="calendar" class="wrapper-lg" ng-show="!plansAreShown"></div>
|
||||
<ng-include ng-if="!fablabWithoutPlans" src="'<%= asset_path 'plans/_plan.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
<cart slot="selectedEvent"
|
||||
slot-selection-time="selectionTime"
|
||||
events="events"
|
||||
user="ctrl.member"
|
||||
mode-plans="plansAreShown"
|
||||
plan="selectedPlan"
|
||||
plan-selection-time="planSelectionTime"
|
||||
settings="settings"
|
||||
on-slot-added-to-cart="markSlotAsAdded"
|
||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||
on-slot-start-to-modify="markSlotAsModifying"
|
||||
on-slot-modify-success="modifyTrainingSlot"
|
||||
on-slot-modify-cancel="cancelModifyTrainingSlot"
|
||||
on-slot-modify-unselect="changeModifyTrainingSlot"
|
||||
on-slot-cancel-success="slotCancelled"
|
||||
after-payment="afterPayment"
|
||||
reservable-id="{{space.id}}"
|
||||
reservable-type="Space"
|
||||
reservable-name="{{space.name}}"></cart>
|
||||
|
||||
|
||||
<uib-alert type="warning m" ng-show="spaceExplicationsAlert">
|
||||
<p class="text-sm pull-left">
|
||||
<i class="fa fa-warning"></i>
|
||||
<div class="m-l-lg" ng-bind-html="spaceExplicationsAlert"></div>
|
||||
</p>
|
||||
</uib-alert>
|
||||
|
||||
</div>
|
||||
</div>
|
85
app/assets/templates/spaces/show.html
Normal file
85
app/assets/templates/spaces/show.html
Normal file
@ -0,0 +1,85 @@
|
||||
<div>
|
||||
|
||||
<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 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-7 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1>{{ space.name }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-4 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper">
|
||||
<a ng-click="reserveSpace($event)" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs" ng-if="!isAuthorized('admin')" translate>{{ 'space_show.book_this_space' }}</a>
|
||||
|
||||
<a ui-sref="app.admin.space_edit({id:space.slug})" ng-if="isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs"><i class="fa fa-edit"></i> {{ 'edit' | translate }}</a>
|
||||
<a ng-click="deleteSpace($event)" ng-if="isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row no-gutter">
|
||||
<div class="col-sm-12 col-md-12 col-lg-8 b-r-lg">
|
||||
|
||||
<div class="article wrapper-lg">
|
||||
|
||||
<div class="article-thumbnail" ng-if="space.space_image">
|
||||
<img ng-src="{{space.space_image}}" alt="{{space.name}}" class="img-responsive">
|
||||
</div>
|
||||
|
||||
<p class="intro" ng-bind-html="space.description | toTrusted"></p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-4">
|
||||
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'space_show.characteristics' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg wrapper">
|
||||
<h3></h3>
|
||||
<p ng-bind-html="space.characteristics | toTrusted"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="widget panel b-a m" ng-if="space.space_files_attributes">
|
||||
<div class="panel-heading b-b">
|
||||
<span class="badge bg-warning pull-right">{{space.space_files_attributes.length}}</span>
|
||||
<h3 translate>{{ 'space_show.files_to_download' }}</h3>
|
||||
</div>
|
||||
|
||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||
<li ng-repeat="file in space.space_files_attributes" class="list-group-item no-b clearfix">
|
||||
<a target="_blank" ng-href="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="widget panel b-a m" ng-if="space.space_projects">
|
||||
<div class="panel-heading b-b">
|
||||
<h3 translate>{{ 'space_show.projects_using_the_space' }}</h3>
|
||||
</div>
|
||||
|
||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||
<li ng-repeat="project in space.space_projects" class="list-group-item no-b clearfix">
|
||||
<a ui-sref="app.public.projects_show({id:project.slug})"><i class="fa"> </i> {{project.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
@ -7,15 +7,15 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 ng-hide="training" translate>{{ 'trainings_planning' }}</h1>
|
||||
<h1 ng-show="training"><span translate>{{ 'planning_of' }}</span> {{training.name}}</h1>
|
||||
<h1 ng-show="mode == 'all'" translate>{{ 'trainings_planning' }}</h1>
|
||||
<h1 ng-hide="mode == 'all'"><span translate>{{ 'planning_of' }}</span> {{training.name}}</h1>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs"
|
||||
ui-sref="app.logged.trainings_reserve({id:'all'})"
|
||||
ng-show="training"
|
||||
ng-hide="mode == 'all'"
|
||||
role="button"
|
||||
translate>{{ 'all_trainings' }}</a>
|
||||
</section>
|
||||
@ -33,165 +33,34 @@
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div class="text-center m-t">
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="ctrl.member && !slotToModify && !modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-show="!selectedTraining && !paidTraining">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_a_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="selectedTraining">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'you_ve_just_selected_the_slot' }}</div>
|
||||
<cart slot="selectedEvent"
|
||||
slot-selection-time="selectionTime"
|
||||
events="events"
|
||||
user="ctrl.member"
|
||||
mode-plans="plansAreShown"
|
||||
plan="selectedPlan"
|
||||
plan-selection-time="planSelectionTime"
|
||||
settings="settings"
|
||||
on-slot-added-to-cart="markSlotAsAdded"
|
||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||
on-slot-start-to-modify="markSlotAsModifying"
|
||||
on-slot-modify-success="modifyTrainingSlot"
|
||||
on-slot-modify-cancel="cancelModifyTrainingSlot"
|
||||
on-slot-modify-unselect="changeModifyTrainingSlot"
|
||||
on-slot-cancel-success="slotCancelled"
|
||||
after-payment="afterPayment"
|
||||
reservable-id="{{training.id}}"
|
||||
reservable-type="Training"
|
||||
reservable-name="{{training.name}}"
|
||||
limit-to-one-slot="true"></cart>
|
||||
|
||||
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(selectedTraining.start | amDateFormat:'LLLL'), END_TIME:(selectedTraining.end | amDateFormat:'LT')} }}</div>
|
||||
<div class="text-base">{{ 'training_cost_' | translate }} <span ng-class="{'text-blue': selectedTraining.training.amount == selectedTrainingAmount, 'red': selectedTraining.training.amount != selectedTrainingAmount}">{{selectedTrainingAmount | currency}}</span></div>
|
||||
<div ng-show="currentUser.role == 'admin'" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'offer_this_training' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="selectedTraining.offered"
|
||||
id="offerSlot"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"
|
||||
switch-readonly="{{trainingIsValid}}"
|
||||
ng-change="updatePrices()"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer no-padder">
|
||||
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validTraining()" ng-if="!trainingIsValid" translate>{{ 'confirm_this_slot' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clear">
|
||||
<a class="pull-right m-t-xs text-u-l" href="#" ng-click="removeTraining($event)" ng-if="trainingIsValid" translate>{{ 'remove_this_slot' }}</a>
|
||||
</div>
|
||||
|
||||
<coupon show="trainingIsValid && (!plansIsShow || selectedPlan)" coupon="coupon.applied" has-select-slot="trainingIsValid" user-id="{{ctrl.member.id}}"></coupon>
|
||||
|
||||
<span ng-hide="fablabWithoutPlans">
|
||||
<div ng-if="trainingIsValid && !ctrl.member.subscribed_plan" ng-show="!plansIsShow">
|
||||
<p class="font-sbold text-base l-h-2x" translate>{{ 'to_benefit_from_attractive_prices_and_a_free_training' }}</p>
|
||||
<div><button class="btn btn-warning-full rounded btn-block text-xs" ng-click="showPlans()" translate>{{ 'view_our_subscriptions' }}</button></div>
|
||||
<p class="font-bold text-base text-u-c text-center m-b-xs">ou</p>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_ve_just_selected_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
<div class="panel panel-default bg-light m-n">
|
||||
<div class="panel-body m-b-md">
|
||||
<div class="font-sbold text-u-c">{{ selectedPlan | humanReadablePlanName }}</div>
|
||||
<div class="text-base">{{ 'subscription_cost' | translate }} <span class="text-blue">{{selectedPlan.amount | currency}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="selectedTraining">
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="payTraining()" ng-if="trainingIsValid && (!plansIsShow || selectedPlan)">{{ 'confirm_and_pay' | translate }} {{amountTotal | currency}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="paidTraining">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_have_settled_the_training' | translate }} <br> <span class="font-sbold">{{paidTraining.training.name}}</span> :
|
||||
</div>
|
||||
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(paidTraining.start | amDateFormat:'LLLL'), END_TIME:(paidTraining.end | amDateFormat:'LT') } }} </i>
|
||||
<div class="font-sbold">{{ 'training_cost_' | translate }} {{paidTraining.training.amount | currency}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="selectedPlan">
|
||||
<div class="m-t-md m-b-sm text-base">{{ 'you_have_settled_a_' | translate }} <br> <span class="font-sbold" translate>{{ '_subscription' }}</span> :</div>
|
||||
|
||||
<div class="well well-warning m-t-sm">
|
||||
<i class="font-sbold text-u-c">{{selectedPlan | humanReadablePlanName }}</i>
|
||||
<div class="font-sbold">{{ 'subscription_cost' | translate }} {{selectedPlan.amount | currency}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t-md font-sbold">{{ 'total_' | translate }} {{amountTotal | currency}}</div>
|
||||
<div class="alert alert-success" ng-if="ctrl.member.subscribed_plan">{{ 'thank_you_your_payment_has_been_successfully_registered' | translate }}<br>
|
||||
{{ 'your_invoice_will_be_available_soon_from_your_' | translate }} <a ui-sref="app.logged.dashboard.invoices" translate>{{ 'dashboard' }}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m m-t-lg" ng-if="slotToModify || modifiedSlots">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'summary' }}</h3>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="slotToModify">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'i_want_to_change_the_following_reservation' }}</div>
|
||||
|
||||
<div class="panel panel-warning bg-yellow">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToModify.start | amDateFormat:'LLLL'), END_TIME:(slotToModify.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToModify($event)" translate>{{ 'cancel_my_modification' }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg">
|
||||
<p class="font-felt fleche-left text-lg"><%= image_tag("fleche-left.png", class: 'fleche-left visible-lg') %>
|
||||
{{ 'select_a_new_slot_in_the_calendar' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info bg-info text-white" ng-if="slotToPlace">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(slotToPlace.start | amDateFormat:'LLLL'), END_TIME:(slotToPlace.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
<div class="clear"><a class="pull-right m-b-sm text-u-l ng-scope m-r-sm" href="#" ng-click="removeSlotToPlace($event)" translate>{{ 'cancel_my_selection' }}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer no-padder" ng-if="slotToModify && slotToPlace">
|
||||
<button class="btn btn-invalid btn-default btn-block p-l btn-lg text-u-c r-n text-base" ng-click="cancelModifyMachineSlot()" translate>{{ 'cancel' }}</button>
|
||||
<div>
|
||||
<button class="btn btn-valid btn-info btn-block p-l btn-lg text-u-c r-b text-base" ng-click="modifyTrainingSlot()" translate>{{ 'confirm_my_modification' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-content no-bg auto wrapper" ng-if="modifiedSlots">
|
||||
<div class="font-sbold m-b-sm " translate>{{ 'your_booking_slot_was_successfully_moved_from_' }}</div>
|
||||
|
||||
<div class="panel panel-default bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.oldReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.oldReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center font-bold m-b-sm text-u-c" translate>{{ 'to_date' }}</p>
|
||||
|
||||
<div class="panel panel-success bg-success bg-light">
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'datetime_to_time' | translate:{START_DATETIME:(modifiedSlots.newReservedSlot.start | amDateFormat:'LLLL'), END_TIME:(modifiedSlots.newReservedSlot.end | amDateFormat:'LT') } }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<uib-alert type="info m">
|
||||
<p class="text-sm font-bold">
|
||||
<i class="fa fa-lightbulb-o"></i>
|
||||
|
@ -18,6 +18,7 @@
|
||||
<a ng-click="reserveTraining(training, $event)" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs" ng-if="!isAuthorized('admin')" translate>{{ 'book_this_training' }}</a>
|
||||
|
||||
<a ui-sref="app.admin.trainings_edit({id: training.id})" ng-if="isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs"><i class="fa fa-edit"></i> {{ 'edit' | translate }}</a>
|
||||
<a ng-click="delete(training)" ng-if="isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,13 +3,86 @@
|
||||
<h1 translate>{{ 'credit_title' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="alert alert-warning m-b-md m-b-sm">
|
||||
<i class="fa fa-warning m-sm inline" aria-hidden="true"></i>
|
||||
<div class="inline pull-right width-90 m-t-n-xs" translate>{{ 'warning_uneditable_credit' }}</div>
|
||||
</div>
|
||||
|
||||
<form name="walletForm" ng-class="{'has-error': walletForm.amount.$dirty && walletForm.amount.$invalid}">
|
||||
<div class="text-center amountGroup">
|
||||
<span class="beforeAmount" translate>{{ 'credit_label' }}</span>
|
||||
<input class="form-control" type="number" name="amount" ng-model="amount" required min="1" step="any">
|
||||
<div class="text-right amountGroup m-r-md">
|
||||
<label for="amount" class="beforeAmount" translate>{{ 'credit_label' }}</label>
|
||||
<input class="form-control m-l"
|
||||
type="number"
|
||||
id="amount"
|
||||
name="amount"
|
||||
ng-model="amount"
|
||||
required min="1"
|
||||
step="any">
|
||||
<span class="afterAmount">{{currencySymbol}}</span>
|
||||
<span class="help-block" ng-show="walletForm.amount.$dirty && walletForm.amount.$error.required" translate>{{'amount_is_required'}}</span>
|
||||
<span class="help-block" ng-show="walletForm.amount.$dirty && walletForm.amount.$error.min">{{ 'amount_minimum_1' | translate }}{{currencySymbol}}</span>
|
||||
<span class="help-block" ng-show="walletForm.amount.$dirty && walletForm.amount.$error.min">{{ 'amount_minimum_1' | translate }} {{currencySymbol}}.</span>
|
||||
</div>
|
||||
<div class="text-right amountGroup m-t m-r-md" ng-class="{'has-error': walletForm.amount_confirm.$dirty && walletForm.amount_confirm.$invalid }">
|
||||
<label for="amount_confirm" class="beforeAmount" translate>{{ 'confirm_credit_label' }}</label>
|
||||
<input class="form-control m-l"
|
||||
type="number"
|
||||
id="amount_confirm"
|
||||
name="amount_confirm"
|
||||
ng-model="amount_confirm"
|
||||
required
|
||||
min="1"
|
||||
step="any"
|
||||
ng-pattern="amount.toString()">
|
||||
<span class="afterAmount">{{currencySymbol}}</span>
|
||||
<span class="help-block" ng-show="walletForm.amount_confirm.$dirty && walletForm.amount_confirm.$error.required" translate>{{'amount_confirm_is_required'}}</span>
|
||||
<span class="help-block" ng-show="walletForm.amount_confirm.$dirty && walletForm.amount_confirm.$error.pattern">{{ 'amount_confirm_does_not_match' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<div class="text-right m-t">
|
||||
<label for="generate_avoir" translate>{{ 'generate_a_refund_invoice' }}</label>
|
||||
<div class="inline m-l">
|
||||
<input bs-switch
|
||||
ng-model="generate_avoir"
|
||||
id="generate_avoir"
|
||||
name="generate_avoir"
|
||||
type="checkbox"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="generate_avoir">
|
||||
<div class="m-t" ng-class="{'has-error': walletForm.avoir_date.$dirty && walletForm.avoir_date.$invalid }">
|
||||
<label for="avoir_date" translate>{{ '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"
|
||||
class="form-control"
|
||||
id="avoir_date"
|
||||
name="avoir_date"
|
||||
ng-model="avoir_date"
|
||||
uib-datepicker-popup="{{datePicker.format}}"
|
||||
datepicker-options="datePicker.options"
|
||||
is-open="datePicker.opened"
|
||||
placeholder="{{datePicker.format}}"
|
||||
ng-click="toggleDatePicker($event)"
|
||||
ng-required="generate_avoir"/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="walletForm.avoir_date.$dirty && walletForm.avoir_date.$error.required" translate>{{ 'creation_date_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<label for="description" translate>{{ 'description_(optional)' }}</label>
|
||||
<p translate>{{ 'will_appear_on_the_refund_invoice' }}</p>
|
||||
<textarea class="form-control m-t-sm"
|
||||
id="description"
|
||||
name="description"
|
||||
ng-model="description">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
class API::AuthProvidersController < API::ApiController
|
||||
|
||||
before_action :set_provider, only: [:show, :update, :destroy]
|
||||
|
||||
def index
|
||||
@providers = policy_scope(AuthProvider)
|
||||
end
|
||||
@ -48,6 +47,25 @@ class API::AuthProvidersController < API::ApiController
|
||||
@provider = AuthProvider.active
|
||||
end
|
||||
|
||||
|
||||
def send_code
|
||||
authorize AuthProvider
|
||||
user = User.find_by(email: params[:email])
|
||||
|
||||
if user&.auth_token
|
||||
if AuthProvider.active.providable_type != DatabaseProvider.name
|
||||
NotificationCenter.call type: 'notify_user_auth_migration',
|
||||
receiver: user,
|
||||
attached_object: user
|
||||
render json: {status: 'processing'}, status: :ok
|
||||
else
|
||||
render json: {status: 'error', error: I18n.t('members.current_authentication_method_no_code')}, status: :bad_request
|
||||
end
|
||||
else
|
||||
render json: {status: 'error', error: I18n.t('members.requested_account_does_not_exists')}, status: :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_provider
|
||||
|
@ -1,48 +1,78 @@
|
||||
class API::AvailabilitiesController < API::ApiController
|
||||
include FablabConfiguration
|
||||
|
||||
before_action :authenticate_user!, except: [:public]
|
||||
before_action :set_availability, only: [:show, :update, :destroy, :reservations]
|
||||
respond_to :json
|
||||
|
||||
## machine availabilities are divided in multiple slots of 60 minutes
|
||||
SLOT_DURATION = 60
|
||||
|
||||
def index
|
||||
authorize Availability
|
||||
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
|
||||
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
|
||||
@availabilities = Availability.includes(:machines,:tags,:trainings).where.not(available_type: 'event')
|
||||
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces).where.not(available_type: 'event')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
|
||||
if fablab_spaces_deactivated?
|
||||
@availabilities = @availabilities.where.not(available_type: 'space')
|
||||
end
|
||||
end
|
||||
|
||||
def public
|
||||
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
|
||||
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
|
||||
@reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
|
||||
|
||||
# request for 1 single day
|
||||
if in_same_day(start_date, end_date)
|
||||
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots).where(available_type: ['training', 'event'])
|
||||
# trainings, events
|
||||
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots).where(available_type: %w(training event))
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
# machines
|
||||
@machine_availabilities = Availability.includes(:tags, :machines).where(available_type: 'machines')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
@machine_slots = []
|
||||
@machine_availabilities.each do |a|
|
||||
a.machines.each do |machine|
|
||||
if params[:m] and params[:m].include?(machine.id.to_s)
|
||||
((a.end_at - a.start_at)/SLOT_DURATION.minutes).to_i.times do |i|
|
||||
slot = Slot.new(start_at: a.start_at + (i*SLOT_DURATION).minutes, end_at: a.start_at + (i*SLOT_DURATION).minutes + SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: machine, title: machine.name)
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: machine, title: machine.name)
|
||||
slot = verify_machine_is_reserved(slot, @reservations, current_user, '')
|
||||
@machine_slots << slot
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@availabilities = [].concat(@training_and_event_availabilities).concat(@machine_slots)
|
||||
else
|
||||
|
||||
@availabilities = Availability.includes(:tags, :machines, :trainings, :event, :slots)
|
||||
# spaces
|
||||
@space_availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space')
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
|
||||
if params[:s]
|
||||
@space_availabilities.where(available_id: params[:s])
|
||||
end
|
||||
|
||||
@space_slots = []
|
||||
@space_availabilities.each do |a|
|
||||
space = a.spaces.first
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: space, title: space.name)
|
||||
slot = verify_space_is_reserved(slot, @reservations, current_user, '')
|
||||
@space_slots << slot
|
||||
end
|
||||
end
|
||||
end
|
||||
@availabilities = [].concat(@training_and_event_availabilities).concat(@machine_slots).concat(@space_slots)
|
||||
|
||||
# request for many days (week or month)
|
||||
else
|
||||
@availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
|
||||
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
|
||||
@availabilities.each do |a|
|
||||
if a.available_type != 'machines'
|
||||
a = verify_training_event_is_reserved(a, @reservations)
|
||||
if a.available_type == 'training' or a.available_type == 'event'
|
||||
a = verify_training_event_is_reserved(a, @reservations, current_user)
|
||||
elsif a.available_type == 'space'
|
||||
a.is_reserved = is_reserved_availability(a, current_user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -90,7 +120,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
@user = current_user
|
||||
end
|
||||
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
|
||||
@machine = Machine.find(params[:machine_id])
|
||||
@machine = Machine.friendly.find(params[:machine_id])
|
||||
@slots = []
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
|
||||
if @user.is_admin?
|
||||
@ -101,9 +131,9 @@ class API::AvailabilitiesController < API::ApiController
|
||||
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end
|
||||
@availabilities.each do |a|
|
||||
((a.end_at - a.start_at)/SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*SLOT_DURATION).minutes, end_at: a.start_at + (i*SLOT_DURATION).minutes + SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: @machine, title: '')
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: @machine, title: '')
|
||||
slot = verify_machine_is_reserved(slot, @reservations, current_user, @current_user_role)
|
||||
@slots << slot
|
||||
end
|
||||
@ -126,8 +156,8 @@ class API::AvailabilitiesController < API::ApiController
|
||||
|
||||
# what is requested?
|
||||
# 1) a single training
|
||||
if params[:training_id].is_number?
|
||||
@availabilities = Training.find(params[:training_id]).availabilities
|
||||
if params[:training_id].is_number? or (params[:training_id].length > 0 and params[:training_id] != 'all')
|
||||
@availabilities = Training.friendly.find(params[:training_id]).availabilities
|
||||
# 2) all trainings
|
||||
else
|
||||
@availabilities = Availability.trainings
|
||||
@ -135,7 +165,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
|
||||
# who made the request?
|
||||
# 1) an admin (he can see all future availabilities)
|
||||
if @user.is_admin?
|
||||
if current_user.is_admin?
|
||||
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines]).where('availabilities.start_at > ?', Time.now)
|
||||
# 2) an user (he cannot see availabilities further than 1 (or 3) months)
|
||||
else
|
||||
@ -146,13 +176,62 @@ class API::AvailabilitiesController < API::ApiController
|
||||
|
||||
# finally, we merge the availabilities with the reservations
|
||||
@availabilities.each do |a|
|
||||
a = verify_training_event_is_reserved(a, @reservations)
|
||||
a = verify_training_event_is_reserved(a, @reservations, @user)
|
||||
end
|
||||
end
|
||||
|
||||
def spaces
|
||||
if params[:member_id]
|
||||
@user = User.find(params[:member_id])
|
||||
else
|
||||
@user = current_user
|
||||
end
|
||||
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
|
||||
@space = Space.friendly.find(params[:space_id])
|
||||
@slots = []
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
|
||||
if @user.is_admin?
|
||||
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND available_type = 'space'", Time.now)
|
||||
else
|
||||
end_at = 1.month.since
|
||||
end_at = 3.months.since if is_subscription_year(@user)
|
||||
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end
|
||||
@availabilities.each do |a|
|
||||
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: @space, title: '')
|
||||
slot = verify_space_is_reserved(slot, @reservations, @user, @current_user_role)
|
||||
@slots << slot
|
||||
end
|
||||
end
|
||||
end
|
||||
@slots.each do |s|
|
||||
if s.is_complete? and not s.is_reserved
|
||||
s.title = t('availabilities.not_available')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def reservations
|
||||
authorize Availability
|
||||
@reservation_slots = @availability.slots.includes(reservation: [user: [:profile]]).order('slots.start_at ASC')
|
||||
@reservation_slots = @availability.slots.includes(reservations: [user: [:profile]]).order('slots.start_at ASC')
|
||||
end
|
||||
|
||||
def export_availabilities
|
||||
authorize :export
|
||||
|
||||
export = Export.where({category:'availabilities', export_type: 'index'}).where('created_at > ?', Availability.maximum('updated_at')).last
|
||||
if export.nil? || !FileTest.exist?(export.file)
|
||||
@export = Export.new({category:'availabilities', export_type: 'index', user: current_user})
|
||||
if @export.save
|
||||
render json: {export_id: @export.id}, status: :ok
|
||||
else
|
||||
render json: @export.errors, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
send_file File.join(Rails.root, export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@ -161,10 +240,20 @@ class API::AvailabilitiesController < API::ApiController
|
||||
end
|
||||
|
||||
def availability_params
|
||||
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, machine_ids: [], training_ids: [], tag_ids: [],
|
||||
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, machine_ids: [], training_ids: [], space_ids: [], tag_ids: [],
|
||||
:machines_attributes => [:id, :_destroy])
|
||||
end
|
||||
|
||||
def is_reserved_availability(availability, user_id)
|
||||
reserved_slots = []
|
||||
availability.slots.each do |s|
|
||||
if s.canceled_at.nil?
|
||||
reserved_slots << s
|
||||
end
|
||||
end
|
||||
reserved_slots.map(&:reservations).flatten.map(&:user_id).include? user_id
|
||||
end
|
||||
|
||||
def is_reserved(start_at, reservations)
|
||||
is_reserved = false
|
||||
reservations.each do |r|
|
||||
@ -184,7 +273,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
slot.is_reserved = true
|
||||
slot.title = "#{slot.machine.name} - #{t('availabilities.not_available')}"
|
||||
slot.can_modify = true if user_role === 'admin'
|
||||
slot.reservation = r
|
||||
slot.reservations.push r
|
||||
end
|
||||
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
|
||||
slot.title = "#{slot.machine.name} - #{t('availabilities.i_ve_reserved')}"
|
||||
@ -197,8 +286,27 @@ class API::AvailabilitiesController < API::ApiController
|
||||
slot
|
||||
end
|
||||
|
||||
def verify_training_event_is_reserved(availability, reservations)
|
||||
user = current_user
|
||||
def verify_space_is_reserved(slot, reservations, user, user_role)
|
||||
reservations.each do |r|
|
||||
r.slots.each do |s|
|
||||
if slot.space.id == r.reservable_id
|
||||
if s.start_at == slot.start_at and s.canceled_at == nil
|
||||
slot.can_modify = true if user_role === 'admin'
|
||||
slot.reservations.push r
|
||||
end
|
||||
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
|
||||
slot.id = s.id
|
||||
slot.title = t('availabilities.i_ve_reserved')
|
||||
slot.can_modify = true
|
||||
slot.is_reserved = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
slot
|
||||
end
|
||||
|
||||
def verify_training_event_is_reserved(availability, reservations, user)
|
||||
reservations.each do |r|
|
||||
r.slots.each do |s|
|
||||
if ((availability.available_type == 'training' and availability.trainings.first.id == r.reservable_id) or (availability.available_type == 'event' and availability.event.id == r.reservable_id)) and s.start_at == availability.start_at and s.canceled_at == nil
|
||||
@ -239,6 +347,12 @@ class API::AvailabilitiesController < API::ApiController
|
||||
availabilities_filtered << a
|
||||
end
|
||||
end
|
||||
# space
|
||||
if params[:s] and a.available_type == 'space'
|
||||
if params[:s].include?(a.spaces.first.id.to_s)
|
||||
availabilities_filtered << a
|
||||
end
|
||||
end
|
||||
# machines
|
||||
if params[:m] and a.available_type == 'machines'
|
||||
if (params[:m].map(&:to_i) & a.machine_ids).any?
|
||||
|
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