mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-29 10:24:20 +01:00
Merge branch 'dev'
This commit is contained in:
commit
dff155831a
1
.fabmanager-version
Normal file
1
.fabmanager-version
Normal file
@ -0,0 +1 @@
|
||||
2.4.0
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -30,9 +30,14 @@
|
||||
# PDF invoices
|
||||
/invoices/*
|
||||
|
||||
# XLSX exports
|
||||
/exports/*
|
||||
|
||||
|
||||
.DS_Store
|
||||
|
||||
.vagrant
|
||||
.docker
|
||||
|
||||
# Plugins are versioned is their own repository
|
||||
/plugins/*
|
||||
|
70
CHANGELOG.md
70
CHANGELOG.md
@ -1,5 +1,73 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## 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
|
||||
- Load translation locales from subdirectories
|
||||
- Add wallet to user, client can pay total/partial reservation or subscription by wallet
|
||||
- Public calendar for show all trainings/machines/events
|
||||
- Display 'draft' badge on drafts in project galleries
|
||||
- Add a 'new project' button in dashboard/my projects
|
||||
- Open Projects: show the platform of origin even for local projects
|
||||
- Ability to use HTML in machine specs and description
|
||||
- Ability to manage project steps order
|
||||
- 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"
|
||||
- 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
|
||||
- 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
|
||||
- More fields in members exports
|
||||
- Unified members, subscriptions and reservations exports with the new statistics exports
|
||||
- Excel exports are now asynchronously generated and cached on the server for future identical requests
|
||||
- Users have the ability to create an organizational profile when creating an account
|
||||
- Organization informations will be used in invoices generation, if present
|
||||
- Admins can create and enable/disable coupons. They can also notify an user about details of a coupon
|
||||
- Users and admins can apply coupons's discounts to their shopping cart
|
||||
- Send an email reminder and system notification some hours before a reservation happens
|
||||
- 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 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: 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`, `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
|
||||
|
||||
- Fix a bug: group cache filename too long
|
||||
@ -19,7 +87,7 @@
|
||||
## v2.2.2 2016 June 23
|
||||
- Fix some bugs: users with uncompleted account (sso imported) won't appear in statistics, in listings and in searches. Moreover, they won't block statistics generation
|
||||
- Fix a bug: unable to display next results in statistics tables
|
||||
- Admin: Category is mandatory when creating a course/workshop (event)
|
||||
- Admin: Category is mandatory when creating an event
|
||||
|
||||
## v2.2.1 2016 June 22
|
||||
- Fix a bug: field User.merged_at should not be allowed to be mapped in SSO
|
||||
|
24
Dockerfile
24
Dockerfile
@ -1,18 +1,11 @@
|
||||
FROM ruby:2.3
|
||||
MAINTAINER peng@sleede.com
|
||||
|
||||
# cf: nginx Dockerfile : https://github.com/nginxinc/docker-nginx
|
||||
RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62
|
||||
RUN echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list
|
||||
|
||||
ENV NGINX_VERSION 1.9.7-1~jessie
|
||||
|
||||
# Install apt based dependencies required to run Rails as
|
||||
# well as RubyGems. As the Ruby image itself is based on a
|
||||
# Debian image, we use apt-get to install those.
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
nginx=${NGINX_VERSION} \
|
||||
nodejs \
|
||||
supervisor
|
||||
|
||||
@ -28,20 +21,11 @@ RUN bundle install --binstubs
|
||||
# Clean up APT when done.
|
||||
#RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
|
||||
# Nginx
|
||||
# Remove the default site
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
|
||||
# forward request and error logs to docker log collector
|
||||
RUN ln -sf /dev/stdout /var/log/nginx/access.log
|
||||
RUN ln -sf /dev/stderr /var/log/nginx/error.log
|
||||
|
||||
|
||||
# Web app
|
||||
RUN mkdir -p /usr/src/app
|
||||
RUN mkdir -p /usr/src/app/config
|
||||
RUN mkdir -p /usr/src/app/invoices
|
||||
RUN mkdir -p /usr/src/app/exports
|
||||
RUN mkdir -p /usr/src/app/log
|
||||
RUN mkdir -p /usr/src/app/public/uploads
|
||||
RUN mkdir -p /usr/src/app/public/assets
|
||||
@ -56,13 +40,15 @@ COPY . /usr/src/app
|
||||
|
||||
# Volumes
|
||||
VOLUME /usr/src/app/invoices
|
||||
VOLUME /usr/src/app/exports
|
||||
VOLUME /usr/src/app/public
|
||||
VOLUME /usr/src/app/public/uploads
|
||||
VOLUME /usr/src/app/public/assets
|
||||
VOLUME /var/log/supervisor
|
||||
|
||||
# Expose port 80 and ssl 443 to the Docker host, so we can access it
|
||||
# Expose port 3000 to the Docker host, so we can access it
|
||||
# from the outside.
|
||||
EXPOSE 80 443
|
||||
EXPOSE 3000
|
||||
|
||||
# The main command to run when the container starts. Also
|
||||
# tell the Rails dev server to bind to all interfaces by
|
||||
|
5
Gemfile
5
Gemfile
@ -144,3 +144,8 @@ gem 'openlab_ruby'
|
||||
gem 'api-pagination'
|
||||
gem 'has_secure_token'
|
||||
gem 'apipie-rails'
|
||||
|
||||
# XLS files generation
|
||||
gem 'rubyzip', '~> 1.1.0'
|
||||
gem 'axlsx', '2.1.0.pre'
|
||||
gem 'axlsx_rails'
|
||||
|
34
Gemfile.lock
34
Gemfile.lock
@ -54,6 +54,13 @@ GEM
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
axlsx (2.1.0.pre)
|
||||
htmlentities (~> 4.3.1)
|
||||
nokogiri (>= 1.4.1)
|
||||
rubyzip (~> 1.1.7)
|
||||
axlsx_rails (0.4.0)
|
||||
axlsx (>= 2.0.1)
|
||||
rails (>= 3.1)
|
||||
bcrypt (3.1.10)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
@ -87,13 +94,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)
|
||||
@ -150,7 +157,7 @@ GEM
|
||||
multi_json
|
||||
equalizer (0.0.11)
|
||||
erubis (2.7.0)
|
||||
execjs (2.4.0)
|
||||
execjs (2.7.0)
|
||||
faker (1.4.3)
|
||||
i18n (~> 0.5)
|
||||
faraday (0.9.1)
|
||||
@ -174,6 +181,7 @@ GEM
|
||||
highline (1.7.1)
|
||||
hike (1.2.3)
|
||||
hitimes (1.2.2)
|
||||
htmlentities (4.3.4)
|
||||
http (0.6.4)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http-cookie (1.0.2)
|
||||
@ -214,8 +222,8 @@ GEM
|
||||
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
|
||||
@ -233,8 +241,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)
|
||||
@ -257,6 +266,7 @@ GEM
|
||||
orm_adapter (0.5.0)
|
||||
pdf-core (0.5.1)
|
||||
pg (0.18.1)
|
||||
pkg-config (1.1.7)
|
||||
prawn (2.0.1)
|
||||
pdf-core (~> 0.5.1)
|
||||
ttfunk (~> 1.4.0)
|
||||
@ -305,7 +315,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)
|
||||
@ -325,6 +335,7 @@ GEM
|
||||
netrc (~> 0.7)
|
||||
rolify (4.0.0)
|
||||
ruby-progressbar (1.7.5)
|
||||
rubyzip (1.1.7)
|
||||
rufus-scheduler (3.0.9)
|
||||
tzinfo
|
||||
rvm-capistrano (1.5.6)
|
||||
@ -440,6 +451,8 @@ DEPENDENCIES
|
||||
api-pagination
|
||||
apipie-rails
|
||||
awesome_print
|
||||
axlsx (= 2.1.0.pre)
|
||||
axlsx_rails
|
||||
bootstrap-sass
|
||||
byebug
|
||||
capistrano
|
||||
@ -488,6 +501,7 @@ DEPENDENCIES
|
||||
recurrence
|
||||
responders (~> 2.0)
|
||||
rolify
|
||||
rubyzip (~> 1.1.0)
|
||||
rvm-capistrano
|
||||
sass-rails (= 5.0.1)
|
||||
sdoc (~> 0.4.0)
|
||||
|
166
README.md
166
README.md
@ -6,14 +6,15 @@ FabManager is the FabLab management solution. It is web-based, open-source and t
|
||||
##### Table of Contents
|
||||
1. [Software stack](#software-stack)
|
||||
2. [Contributing](#contributing)
|
||||
3. [Setup a production environment with Docker and CoreOS](#setup-a-production-environment)
|
||||
3. [Setup a production environment](#setup-a-production-environment)
|
||||
4. [Setup a development environment](#setup-a-development-environment)<br/>
|
||||
4.1. [General Guidelines](#general-guidelines)<br/>
|
||||
4.2. [Environment Configuration](#environment-configuration)
|
||||
5. [PostgreSQL](#postgresql)<br/>
|
||||
5.1. [Install PostgreSQL 9.4 on Ubuntu/Debian](#postgresql-on-debian)<br/>
|
||||
5.2. [Install and launch PostgreSQL on MacOS X](#postgresql-on-macosx)<br/>
|
||||
5.3. [Setup the FabManager database in PostgreSQL](#setup-fabmanager-in-postgresql)
|
||||
5.3. [Setup the FabManager database in PostgreSQL](#setup-fabmanager-in-postgresql)<br/>
|
||||
5.4. [PostgreSQL Limitations](#postgresql-limitations)
|
||||
6. [ElasticSearch](#elasticsearch)<br/>
|
||||
6.1. [Install ElasticSearch on Ubuntu/Debian](#elasticsearch-on-debian)<br/>
|
||||
6.2. [Install ElasticSearch on MacOS X](#elasticsearch-on-macosx)<br/>
|
||||
@ -28,8 +29,9 @@ FabManager is the FabLab management solution. It is web-based, open-source and t
|
||||
7.2.2. [Applying changes](#i18n-apply)
|
||||
8. [Open Projects](#open-projects)
|
||||
9. [Plugins](#plugins)
|
||||
10. [Known issues](#known-issues)
|
||||
11. [Related Documentation](#related-documentation)
|
||||
10. [Single Sign-On](#sso)
|
||||
11. [Known issues](#known-issues)
|
||||
12. [Related Documentation](#related-documentation)
|
||||
|
||||
|
||||
|
||||
@ -38,7 +40,7 @@ FabManager is the FabLab management solution. It is web-based, open-source and t
|
||||
|
||||
FabManager is a Ruby on Rails / AngularJS web application that runs on the following software:
|
||||
|
||||
- Ubuntu/Debian
|
||||
- Ubuntu LTS 14.04+ / Debian 8+
|
||||
- Ruby 2.3
|
||||
- Git 1.9.1+
|
||||
- Redis 2.8.4+
|
||||
@ -54,13 +56,16 @@ Contributions are welcome. Please read [the contribution guidelines](CONTRIBUTIN
|
||||
**IMPORTANT**: **do not** update Arshaw/fullCalendar.js as it contains a hack for the remove-event cross.
|
||||
|
||||
<a name="setup-a-production-environment"></a>
|
||||
## Setup a production environment with Docker and CoreOS
|
||||
## Setup a production environment
|
||||
|
||||
[Docker Readme](docker/README.md)
|
||||
To run fab-manager as a production application, this is highly recommended to use [Docker](https://www.docker.com/).
|
||||
The procedure to follow is described in the [docker readme](docker/README.md).
|
||||
|
||||
<a name="setup-a-development-environment"></a>
|
||||
## Setup a development environment
|
||||
|
||||
In you only intend to run fab-manager on your local machine for testing purposes or to contribute to the project development, you can set it up with the following procedure.
|
||||
|
||||
<a name="general-guidelines"></a>
|
||||
### General Guidelines
|
||||
|
||||
@ -74,15 +79,17 @@ Contributions are welcome. Please read [the contribution guidelines](CONTRIBUTIN
|
||||
```
|
||||
|
||||
3. Install the software dependencies.
|
||||
First install [PostgreSQL](#postgresql) and [ElasticSearch](#elasticsearch) as specified in their respective documentations.
|
||||
Then install the other dependencies:
|
||||
- For Ubuntu/Debian:
|
||||
|
||||
```bash
|
||||
sudo apt-get install libpq-dev postgresql-9.4 redis-server imagemagick
|
||||
sudo apt-get install libpq-dev redis-server imagemagick
|
||||
```
|
||||
- For MacOS X:
|
||||
|
||||
```bash
|
||||
brew install postgresql redis imagemagick
|
||||
brew install redis imagemagick
|
||||
```
|
||||
|
||||
4. Init the RVM instance and check it was correctly configured
|
||||
@ -114,7 +121,7 @@ Contributions are welcome. Please read [the contribution guidelines](CONTRIBUTIN
|
||||
# or use your favorite text editor instead of vi (nano, ne...)
|
||||
```
|
||||
|
||||
8. Build the database. You may have to follow the steps described in [the PostgreSQL installation chapter](#postgresql) before, if you don't already have a working installation of PostgreSQL.
|
||||
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.
|
||||
|
||||
```bash
|
||||
rake db:setup
|
||||
@ -147,11 +154,13 @@ If you are in a development environment, your can keep the default values, other
|
||||
POSTGRES_HOST
|
||||
|
||||
DNS name or IP address of the server hosting the PostgreSQL database of the application (see [PostgreSQL](#postgresql)).
|
||||
This value is only used when deploying with Docker, otherwise this is configured in `config/database.yml`.
|
||||
|
||||
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.
|
||||
This value is only used when deploying with Docker, otherwise this is configured in `config/database.yml`.
|
||||
|
||||
REDIS_HOST
|
||||
|
||||
@ -217,13 +226,42 @@ See https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname-
|
||||
|
||||
TWITTER_NAME
|
||||
|
||||
Identifier of the Twitter account, for witch the last tweet will be displayed on the home page.
|
||||
Identifier of the Twitter account, from witch the last tweet will be fetched and displayed on the home page.
|
||||
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
|
||||
|
||||
Keys and secrets to access the twitter API.
|
||||
Retrieve them from https://apps.twitter.com
|
||||
|
||||
FACEBOOK_APP_ID
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
@ -237,6 +275,7 @@ See the [Settings](#i18n-settings) section of the [Internationalization (i18n)](
|
||||
|
||||
1. Create the file `/etc/apt/sources.list.d/pgdg.list`, and append it one the following lines:
|
||||
- `deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main` (Ubuntu 14.04 Trusty)
|
||||
- `deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main` (Ubuntu 16.04 Xenial)
|
||||
- `deb http://apt.postgresql.org/pub/repos/apt/ jessie-pgdg main` (Debian 8 Jessie)
|
||||
|
||||
|
||||
@ -264,7 +303,7 @@ Otherwise, please follow the official instructions on the project's website.
|
||||
|
||||
```bash
|
||||
brew update
|
||||
brew install postgres
|
||||
brew install homebrew/versions/postgresql94
|
||||
```
|
||||
|
||||
2. Launch PostgreSQL
|
||||
@ -282,42 +321,73 @@ Otherwise, please follow the official instructions on the project's website.
|
||||
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.
|
||||
To create it, please follow these instructions:
|
||||
|
||||
1. Login as the postgres user
|
||||
1. Run the PostgreSQL administration command line interface, logged as the postgres user
|
||||
- For Ubuntu/Debian:
|
||||
|
||||
```bash
|
||||
sudo -i -u postgres
|
||||
```
|
||||
|
||||
2. Run the PostgreSQL administration command line interface
|
||||
|
||||
```bash
|
||||
psql
|
||||
```
|
||||
- For MacOS X:
|
||||
|
||||
3. Create a new user in PostgreSQL (in this example, the user will be named `sleede`)
|
||||
```bash
|
||||
sudo psql -U $(whoami) postgres
|
||||
```
|
||||
|
||||
If you get an error running this command, please check your [pg_hba.conf](https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html) file.
|
||||
|
||||
2. Create a new user in PostgreSQL (in this example, the user will be named `sleede`)
|
||||
|
||||
```sql
|
||||
CREATE USER sleede;
|
||||
```
|
||||
|
||||
4. Grant him the right to create databases
|
||||
3. Grant him the right to create databases
|
||||
|
||||
```sql
|
||||
ALTER ROLE sleede WITH CREATEDB;
|
||||
```
|
||||
|
||||
5. Then, create the fablab_development and fablab_test databases
|
||||
4. Then, create the fabmanager_development and fabmanager_test databases
|
||||
|
||||
```sql
|
||||
CREATE DATABASE fablab_development OWNER sleede;
|
||||
CREATE DATABASE fablab_test OWNER sleede;
|
||||
CREATE DATABASE fabmanager_development OWNER sleede;
|
||||
CREATE DATABASE fabmanager_test OWNER sleede;
|
||||
```
|
||||
|
||||
6. To finish, attribute a password to this user
|
||||
5. To finish, 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.
|
||||
|
||||
<a name="postgresql-limitations"></a>
|
||||
### PostgreSQL Limitations
|
||||
|
||||
- While setting up the database, we'll need to activate two PostgreSQL extensions: [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html).
|
||||
This can only be achieved if the user, configured in `config/database.yml`, was granted the _SUPERUSER_ role **OR** if these extensions were white-listed.
|
||||
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).
|
||||
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.
|
||||
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()`
|
||||
- `db/migrate/20160613093842_create_unaccent_function.rb` is using [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html) modules and defines a PL/pgSQL function (`f_unaccent()`)
|
||||
- `app/controllers/api/members_controllers.rb@search` is using `f_unaccent()` (see above) and `regexp_replace()`
|
||||
- `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.
|
||||
|
||||
<a name="elasticsearch"></a>
|
||||
## ElasticSearch
|
||||
@ -354,7 +424,7 @@ For a more detailed guide concerning the ElasticSearch installation, please chec
|
||||
sudo apt-get install elasticsearch
|
||||
```
|
||||
|
||||
4. To automatically start ElasticSearch during bootup, then, depending if your system is compatible with SysV (eg. Ubuntu 14.04) or uses systemd (eg. Debian 8), you will need to run:
|
||||
4. To automatically start ElasticSearch during bootup, then, depending if your system is compatible with SysV (eg. Ubuntu 14.04) or uses systemd (eg. Debian 8/Ubuntu 16.04), you will need to run:
|
||||
|
||||
```bash
|
||||
# System V
|
||||
@ -364,6 +434,12 @@ For a more detailed guide concerning the ElasticSearch installation, please chec
|
||||
sudo /bin/systemctl enable elasticsearch.service
|
||||
```
|
||||
|
||||
5. Restart the host operating system to complete the installation
|
||||
|
||||
```bash
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
<a name="elasticsearch-on-macosx"></a>
|
||||
### Install ElasticSearch on MacOS X
|
||||
|
||||
@ -387,17 +463,11 @@ brew install homebrew/versions/elasticsearch17
|
||||
|
||||
2. Every nights, the statistics for the day that just ended are built automatically at 01:00 (AM).
|
||||
See [schedule.yml](config/schedule.yml) to modify this behavior.
|
||||
If the scheduled task wasn't executed for any reason (eg. you are in a dev environment and your computer was turned off at 1 AM), you can force the statistics data generation in ElasticSearch, running the following commands in a rails console.
|
||||
If the scheduled task wasn't executed for any reason (eg. you are in a dev environment and your computer was turned off at 1 AM), you can force the statistics data generation in ElasticSearch, running the following command.
|
||||
|
||||
```bash
|
||||
rails c
|
||||
```
|
||||
|
||||
```ruby
|
||||
# Here for the 200 last days
|
||||
200.times.each do |i|
|
||||
StatisticService.new.generate_statistic({start_date: i.day.ago.beginning_of_day,end_date: i.day.ago.end_of_day})
|
||||
end
|
||||
# Here for the 50 last days
|
||||
rake fablab:generate_stats[50]
|
||||
```
|
||||
|
||||
<a name="backup-and-restore-elasticsearch"></a>
|
||||
@ -531,6 +601,11 @@ See https://angular-ui.github.io/bootstrap/#uibdateparser-s-format-codes for a l
|
||||
|
||||
**BEWARE**: years format with less than 4 digits will result in problems because the system won't be able to distinct dates with the same less significant digits, eg. 50 could mean 1950 or 2050.
|
||||
|
||||
EXCEL_DATE_FORMAT
|
||||
|
||||
Date format for dates shown in exported Excel files (eg. statistics)
|
||||
See https://support.microsoft.com/en-us/kb/264372 for a list a available formats.
|
||||
|
||||
<a name="i18n-apply"></a>
|
||||
#### Applying changes
|
||||
|
||||
@ -570,6 +645,15 @@ To install a plugin, you just have to copy the plugin folder which contains its
|
||||
|
||||
You can see an example on the [repo of navinum gamification plugin](https://github.com/LaCasemate/navinum-gamification)
|
||||
|
||||
<a name="sso"></a>
|
||||
## Single Sign-On
|
||||
|
||||
Fab-manager can be connected to a [Single Sign-On](https://en.wikipedia.org/wiki/Single_sign-on) server which will provide its own authentication for the platform's users.
|
||||
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).
|
||||
|
||||
<a name="known-issues"></a>
|
||||
## Known issues
|
||||
|
||||
@ -606,13 +690,15 @@ You can see an example on the [repo of navinum gamification plugin](https://gith
|
||||
|
||||
ALTER ROLE sleede WITH SUPERUSER;
|
||||
|
||||
DO NOT do this in a production environment, as this would lead to a serious security issue.
|
||||
DO NOT do this in a production environment, unless you know what you're doing: this could lead to a serious security issue.
|
||||
|
||||
- Using another DBMS than PostgreSQL is not supported, because of some PostgreSQL specific instructions:
|
||||
- `app/controllers/api/members_controllers.rb@list` is using `ILIKE`
|
||||
- `app/controllers/api/invoices_controllers.rb@list` is using `ILIKE` and `date_trunc()`
|
||||
- `db/migrate/20160613093842_create_unaccent_function.rb` is using [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html) modules and defines a PL/pgSQL function (`f_unaccent()`)
|
||||
- `app/controllers/api/members_controllers.rb@search` is using `f_unaccent()` (see above) and `regexp_replace()`
|
||||
- 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
|
||||
```
|
||||
|
||||
<a name="related-documentation"></a>
|
||||
## Related Documentation
|
||||
|
@ -20,7 +20,7 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ngCooki
|
||||
'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'DeviseModal', 'angular-growl', 'xeditable',
|
||||
'checklist-model', 'unsavedChanges', 'angular-loading-bar', 'ngTouch', 'angular-google-analytics',
|
||||
'angularUtils.directives.dirDisqus', 'summernote', 'elasticsearch', 'angular-medium-editor', 'naif.base64',
|
||||
'minicolors', 'pascalprecht.translate', 'ngFitText']).
|
||||
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside']).
|
||||
config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfigProvider", "AnalyticsProvider", "uibDatepickerPopupConfig", "$provide", "$translateProvider",
|
||||
function($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider) {
|
||||
|
||||
@ -125,6 +125,24 @@ config(['$httpProvider', 'AuthProvider', "growlProvider", "unsavedWarningsConfig
|
||||
// see https://github.com/revolunet/angular-google-analytics#automatic-page-view-tracking
|
||||
Analytics.pageView();
|
||||
|
||||
|
||||
/**
|
||||
* This helper method builds and return an array contaning every integers between
|
||||
* the provided start and end.
|
||||
* @param start {number}
|
||||
* @param end {number}
|
||||
* @return {Array} [start .. end]
|
||||
*/
|
||||
$rootScope.intArray = function(start, end) {
|
||||
var arr = [];
|
||||
for (var i = start; i < end; i++) { arr.push(i); }
|
||||
return arr;
|
||||
};
|
||||
|
||||
}]).constant('angularMomentConfig', {
|
||||
timezone: Fablab.timezone
|
||||
});
|
||||
|
||||
angular.isUndefinedOrNull = function(val) {
|
||||
return angular.isUndefined(val) || val === null
|
||||
};
|
||||
|
@ -67,6 +67,7 @@
|
||||
//= require messageformat/messageformat
|
||||
//= require angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat
|
||||
//= require ngFitText/dist/ng-FitText.min
|
||||
//= require angular-aside/dist/js/angular-aside
|
||||
//= require_tree ./controllers
|
||||
//= require_tree ./services
|
||||
//= require_tree ./directives
|
||||
|
@ -34,6 +34,102 @@ check_oauth2_id_is_mapped = (mappings) ->
|
||||
return false
|
||||
|
||||
|
||||
##
|
||||
# Provides a set of common callback methods and data to the $scope parameter. These methods are used
|
||||
# in the various authentication providers' controllers.
|
||||
#
|
||||
# Provides :
|
||||
# - $scope.authMethods
|
||||
# - $scope.mappingFields
|
||||
# - $scope.cancel()
|
||||
# - $scope.defineDataMapping(mapping)
|
||||
#
|
||||
# Requires :
|
||||
# - mappingFieldsPromise: retrieved by AuthProvider.mapping_fields()
|
||||
# - $state (Ui-Router) [ 'app.admin.members' ]
|
||||
##
|
||||
class AuthenticationController
|
||||
constructor: ($scope, $state, $uibModal, mappingFieldsPromise)->
|
||||
## list of supported authentication methods
|
||||
$scope.authMethods = METHODS
|
||||
|
||||
## list of fields that can be mapped through the SSO
|
||||
$scope.mappingFields = mappingFieldsPromise
|
||||
|
||||
##
|
||||
# Changes the admin's view to the members list page
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.admin.members')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal allowing to specify the data mapping for the given field
|
||||
##
|
||||
$scope.defineDataMapping = (mapping) ->
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "admin/authentications/_data_mapping.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
field: -> mapping
|
||||
datatype: ->
|
||||
for field in $scope.mappingFields[mapping.local_model]
|
||||
if field[0] == mapping.local_field
|
||||
return field[1]
|
||||
|
||||
controller: ['$scope', '$uibModalInstance', 'field', 'datatype', ($scope, $uibModalInstance, field, datatype) ->
|
||||
## parent field
|
||||
$scope.field = field
|
||||
## expected data type
|
||||
$scope.datatype = datatype
|
||||
## data transformation rules
|
||||
$scope.transformation =
|
||||
rules: field.transformation || {type: datatype}
|
||||
## available transformation formats
|
||||
$scope.formats =
|
||||
date: [
|
||||
{
|
||||
label: 'ISO 8601',
|
||||
value: 'iso8601'
|
||||
},
|
||||
{
|
||||
label: 'RFC 2822',
|
||||
value: 'rfc2822'
|
||||
},
|
||||
{
|
||||
label: 'RFC 3339',
|
||||
value: 'rfc3339'
|
||||
},
|
||||
{
|
||||
label: 'Timestamp (s)'
|
||||
value: 'timestamp-s'
|
||||
},
|
||||
{
|
||||
label: 'Timestamp (ms)',
|
||||
value: 'timestamp-ms'
|
||||
}
|
||||
]
|
||||
|
||||
## Create a new mapping between anything and an expected integer
|
||||
$scope.addIntegerMapping = ->
|
||||
unless angular.isArray $scope.transformation.rules.mapping
|
||||
$scope.transformation.rules.mapping = []
|
||||
$scope.transformation.rules.mapping.push({from:'', to:0})
|
||||
|
||||
## close and save the modifications
|
||||
$scope.ok = ->
|
||||
$uibModalInstance.close($scope.transformation.rules)
|
||||
|
||||
## do not save the modifications
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss()
|
||||
]
|
||||
.result['finally'](null).then (transfo_rules) ->
|
||||
mapping.transformation = transfo_rules
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Page listing all authentication providers
|
||||
@ -103,15 +199,12 @@ Application.Controllers.controller "AuthentificationController", ["$scope", "$st
|
||||
##
|
||||
# Page to add a new authentication provider
|
||||
##
|
||||
Application.Controllers.controller "NewAuthenticationController", ["$scope", "$state", "$rootScope", "dialogs", "growl", "mappingFieldsPromise", "authProvidersPromise", "AuthProvider", '_t'
|
||||
, ($scope, $state, $rootScope, dialogs, growl, mappingFieldsPromise, authProvidersPromise, AuthProvider, _t) ->
|
||||
|
||||
$scope.authMethods = METHODS
|
||||
|
||||
$scope.mappingFields = mappingFieldsPromise
|
||||
Application.Controllers.controller "NewAuthenticationController", ["$scope", "$state", "$rootScope", "$uibModal", "dialogs", "growl", "mappingFieldsPromise", "authProvidersPromise", "AuthProvider", '_t'
|
||||
, ($scope, $state, $rootScope, $uibModal, dialogs, growl, mappingFieldsPromise, authProvidersPromise, AuthProvider, _t) ->
|
||||
|
||||
$scope.mode = 'creation'
|
||||
|
||||
## default parameters for the new authentication provider
|
||||
$scope.provider = {
|
||||
name: '',
|
||||
providable_type: '',
|
||||
@ -172,12 +265,8 @@ Application.Controllers.controller "NewAuthenticationController", ["$scope", "$s
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Changes the admin's view to the members list page
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.admin.members')
|
||||
|
||||
## Using the AuthenticationController
|
||||
new AuthenticationController($scope, $state, $uibModal, mappingFieldsPromise)
|
||||
]
|
||||
|
||||
|
||||
@ -185,17 +274,14 @@ Application.Controllers.controller "NewAuthenticationController", ["$scope", "$s
|
||||
##
|
||||
# Page to edit an already added authentication provider
|
||||
##
|
||||
Application.Controllers.controller "EditAuthenticationController", ["$scope", "$state", "$stateParams", "$rootScope", "dialogs", "growl", 'providerPromise', 'mappingFieldsPromise', 'AuthProvider', '_t'
|
||||
, ($scope, $state, $stateParams, $rootScope, dialogs, growl, providerPromise, mappingFieldsPromise, AuthProvider, _t) ->
|
||||
Application.Controllers.controller "EditAuthenticationController", ["$scope", "$state", "$stateParams", "$rootScope", "$uibModal", "dialogs", "growl", 'providerPromise', 'mappingFieldsPromise', 'AuthProvider', '_t'
|
||||
, ($scope, $state, $stateParams, $rootScope, $uibModal, dialogs, growl, providerPromise, mappingFieldsPromise, AuthProvider, _t) ->
|
||||
|
||||
## parameters of the currently edited authentication provider
|
||||
$scope.provider = providerPromise
|
||||
|
||||
$scope.authMethods = METHODS
|
||||
|
||||
$scope.mode = 'edition'
|
||||
|
||||
$scope.mappingFields = mappingFieldsPromise
|
||||
|
||||
##
|
||||
# Update the current provider with the new inputs
|
||||
##
|
||||
@ -210,10 +296,8 @@ Application.Controllers.controller "EditAuthenticationController", ["$scope", "$
|
||||
, ->
|
||||
growl.error(_t('an_error_occurred_unable_to_update_the_provider'))
|
||||
|
||||
##
|
||||
# Changes the admin's view to the members list page
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.admin.members')
|
||||
|
||||
|
||||
## Using the AuthenticationController
|
||||
new AuthenticationController($scope, $state, $uibModal, mappingFieldsPromise)
|
||||
]
|
@ -4,8 +4,8 @@
|
||||
# Controller used in the calendar management page
|
||||
##
|
||||
|
||||
Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'availabilitiesPromise', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t'
|
||||
($scope, $state, $uibModal, moment, Availability, Slot, Setting, growl, dialogs, availabilitiesPromise, bookingWindowStart, bookingWindowEnd, machinesPromise, _t) ->
|
||||
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) ->
|
||||
|
||||
|
||||
|
||||
@ -17,9 +17,6 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
# The bookings can be positioned every half hours
|
||||
BOOKING_SNAP = '00:30:00'
|
||||
|
||||
# The calendar will be initialized positioned under 9:00 AM
|
||||
DEFAULT_CALENDAR_POSITION = '09:00:00'
|
||||
|
||||
# We do not allow the creation of slots that are not a multiple of 60 minutes
|
||||
SLOT_MULTIPLE = 60
|
||||
|
||||
@ -36,40 +33,17 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
## bind the availabilities slots with full-Calendar events
|
||||
$scope.eventSources = []
|
||||
$scope.eventSources.push
|
||||
events: availabilitiesPromise
|
||||
url: '/api/availabilities'
|
||||
textColor: 'black'
|
||||
|
||||
## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar()
|
||||
$scope.calendar = null
|
||||
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig =
|
||||
timezone: Fablab.timezone
|
||||
lang: Fablab.fullcalendar_locale
|
||||
header:
|
||||
left: 'month agendaWeek'
|
||||
center: 'title'
|
||||
right: 'today prev,next'
|
||||
firstDay: 1 # Week start on monday (France)
|
||||
scrollTime: DEFAULT_CALENDAR_POSITION
|
||||
$scope.calendarConfig = CalendarConfig
|
||||
slotDuration: BASE_SLOT
|
||||
snapDuration: BOOKING_SNAP
|
||||
allDayDefault: false
|
||||
minTime: "00:00:00"
|
||||
maxTime: "24:00:00"
|
||||
height: 'auto'
|
||||
buttonIcons:
|
||||
prev: 'left-single-arrow'
|
||||
next: 'right-single-arrow'
|
||||
timeFormat:
|
||||
agenda:'H:mm'
|
||||
month: 'H(:mm)'
|
||||
axisFormat: 'H:mm'
|
||||
|
||||
allDaySlot: false
|
||||
defaultView: 'agendaWeek'
|
||||
selectable: true
|
||||
selecHelper: true
|
||||
minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss'))
|
||||
maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss'))
|
||||
select: (start, end, jsEvent, view) ->
|
||||
calendarSelectCb(start, end, jsEvent, view)
|
||||
eventClick: (event, jsEvent, view)->
|
||||
@ -77,10 +51,6 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element)
|
||||
|
||||
## fullCalendar time bounds (up & down)
|
||||
$scope.calendarConfig.minTime = moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss'))
|
||||
$scope.calendarConfig.maxTime = moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -141,7 +111,7 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
# update the machine_ids attribute
|
||||
$scope.availability.machine_ids = data.machine_ids
|
||||
$scope.availability.title = data.title
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
# notify the admin
|
||||
growl.success(_t('the_machine_was_successfully_removed_from_the_slot'))
|
||||
, (data, status) -> # failed
|
||||
@ -180,7 +150,7 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
end: -> end
|
||||
# when the modal is closed, we send the slot to the server for saving
|
||||
modalInstance.result.then (availability) ->
|
||||
$scope.calendar.fullCalendar 'renderEvent',
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'renderEvent',
|
||||
id: availability.id
|
||||
title: availability.title,
|
||||
start: availability.start_at
|
||||
@ -189,12 +159,13 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
backgroundColor: availability.backgroundColor
|
||||
borderColor: availability.borderColor
|
||||
tag_ids: availability.tag_ids
|
||||
tags: availability.tags
|
||||
machine_ids: availability.machine_ids
|
||||
, true
|
||||
, ->
|
||||
$scope.calendar.fullCalendar('unselect')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('unselect')
|
||||
|
||||
$scope.calendar.fullCalendar('unselect')
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('unselect')
|
||||
|
||||
|
||||
|
||||
@ -209,7 +180,7 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
# if the user has clicked on the delete event button, delete the event
|
||||
if ($(jsEvent.target).hasClass('remove-event'))
|
||||
Availability.delete id: event.id, ->
|
||||
$scope.calendar.fullCalendar 'removeEvents', 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)
|
||||
@ -231,12 +202,12 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
|
||||
# @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
##
|
||||
eventRenderCb = (event, element) ->
|
||||
if event.tag_ids.length > 0
|
||||
Availability.get {id: event.id}, (avail) ->
|
||||
if event.tags.length > 0
|
||||
html = ''
|
||||
for tag in avail.tags
|
||||
for tag in event.tags
|
||||
html += "<span class='label label-success text-white'>#{tag.name}</span> "
|
||||
element.find('.fc-title').append("<br/>"+html)
|
||||
return
|
||||
|
||||
]
|
||||
|
||||
|
122
app/assets/javascripts/controllers/admin/coupons.coffee
Normal file
122
app/assets/javascripts/controllers/admin/coupons.coffee
Normal file
@ -0,0 +1,122 @@
|
||||
### COMMON CODE ###
|
||||
|
||||
# The validity per user defines how many time a user may ba able to use the same coupon
|
||||
# Here are the various options for this parameter
|
||||
userValidities = ['once', 'forever']
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the coupon creation page
|
||||
##
|
||||
Application.Controllers.controller "NewCouponController", ["$scope", "$state",'Coupon', 'growl', '_t'
|
||||
, ($scope, $state, Coupon, growl, _t) ->
|
||||
|
||||
## Values for the coupon currently created
|
||||
$scope.coupon =
|
||||
active: true
|
||||
|
||||
## Options for the validity per user
|
||||
$scope.validities = userValidities
|
||||
|
||||
## Default parameters for AngularUI-Bootstrap datepicker (used for coupon validity limit selection)
|
||||
$scope.datePicker =
|
||||
format: Fablab.uibDateFormat
|
||||
opened: false # default: datePicker is not shown
|
||||
minDate: moment().toDate()
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Shows/hides the validity limit datepicker
|
||||
# @param $event {Object} jQuery event object
|
||||
##
|
||||
$scope.toggleDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.opened = !$scope.datePicker.opened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to save the new coupon in $scope.coupon and redirect the user to the listing page
|
||||
##
|
||||
$scope.saveCoupon = ->
|
||||
Coupon.save coupon: $scope.coupon, (coupon) ->
|
||||
$state.go('app.admin.pricing')
|
||||
, (err)->
|
||||
growl.error(_t('unable_to_create_the_coupon_check_code_already_used'))
|
||||
console.error(err)
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the coupon edition page
|
||||
##
|
||||
Application.Controllers.controller "EditCouponController", ["$scope", "$state", 'Coupon', 'couponPromise', '_t'
|
||||
, ($scope, $state, Coupon, couponPromise, _t) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
|
||||
## Used in the form to freeze unmodifiable fields
|
||||
$scope.mode = 'EDIT'
|
||||
|
||||
## Coupon to edit
|
||||
$scope.coupon = couponPromise
|
||||
|
||||
## Options for the validity per user
|
||||
$scope.validities = userValidities
|
||||
|
||||
## Default parameters for AngularUI-Bootstrap datepicker (used for coupon validity limit selection)
|
||||
$scope.datePicker =
|
||||
format: Fablab.uibDateFormat
|
||||
opened: false # default: datePicker is not shown
|
||||
minDate: moment().toDate()
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Shows/hides the validity limit datepicker
|
||||
# @param $event {Object} jQuery event object
|
||||
##
|
||||
$scope.toggleDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.opened = !$scope.datePicker.opened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to save the coupon's changes to the API
|
||||
##
|
||||
$scope.updateCoupon = ->
|
||||
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)
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
# parse the date if any
|
||||
if (couponPromise.valid_until)
|
||||
$scope.coupon.valid_until = moment(couponPromise.valid_until).toDate()
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
@ -1,296 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
### COMMON CODE ###
|
||||
|
||||
##
|
||||
# Provides a set of common properties and methods to the $scope parameter. They are used
|
||||
# in the various events' admin controllers.
|
||||
#
|
||||
# Provides :
|
||||
# - $scope.categories = [{Category}]
|
||||
# - $scope.datePicker = {}
|
||||
# - $scope.submited(content)
|
||||
# - $scope.cancel()
|
||||
# - $scope.addFile()
|
||||
# - $scope.deleteFile(file)
|
||||
# - $scope.fileinputClass(v)
|
||||
# - $scope.toggleStartDatePicker($event)
|
||||
# - $scope.toggleEndDatePicker($event)
|
||||
# - $scope.toggleRecurrenceEnd(e)
|
||||
#
|
||||
# Requires :
|
||||
# - $scope.event.event_files_attributes = []
|
||||
# - $state (Ui-Router) [ 'app.public.events_list' ]
|
||||
##
|
||||
class EventsController
|
||||
constructor: ($scope, $state, Event, Category) ->
|
||||
|
||||
## Retrieve the list of categories from the server (stage, atelier, ...)
|
||||
Category.query().$promise.then (data)->
|
||||
$scope.categories = data.map (d) ->
|
||||
id: d.id
|
||||
name: d.name
|
||||
|
||||
## default parameters for AngularUI-Bootstrap datepicker
|
||||
$scope.datePicker =
|
||||
format: Fablab.uibDateFormat
|
||||
startOpened: false # default: datePicker is not shown
|
||||
endOpened: false
|
||||
recurrenceEndOpened: false
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
|
||||
|
||||
##
|
||||
# For use with ngUpload (https://github.com/twilson63/ngUpload).
|
||||
# Intended to be the callback when an upload is done: any raised error will be stacked in the
|
||||
# $scope.alerts array. If everything goes fine, the user is redirected to the project page.
|
||||
# @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.events_list')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Changes the user's view to the events list page
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.public.events_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 event's attachements list.
|
||||
##
|
||||
$scope.addFile = ->
|
||||
$scope.event.event_files_attributes.push {}
|
||||
|
||||
|
||||
|
||||
##
|
||||
# This will remove the given file from the event's 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.event.event_files_attributes.indexOf(file)
|
||||
if file.id?
|
||||
file._destroy = true
|
||||
else
|
||||
$scope.event.event_files_attributes.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Show/Hide the "start" datepicker (open the drop down/close it)
|
||||
##
|
||||
$scope.toggleStartDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.startOpened = !$scope.datePicker.startOpened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Show/Hide the "end" datepicker (open the drop down/close it)
|
||||
##
|
||||
$scope.toggleEndDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.endOpened = !$scope.datePicker.endOpened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Masks/displays the recurrence pane allowing the admin to set the current event as recursive
|
||||
##
|
||||
$scope.toggleRecurrenceEnd = (e)->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$scope.datePicker.recurrenceEndOpened = !$scope.datePicker.recurrenceEndOpened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the events listing page (admin view)
|
||||
##
|
||||
Application.Controllers.controller "AdminEventsController", ["$scope", "$state", 'Event', 'eventsPromise', ($scope, $state, Event, eventsPromise) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## By default, the pagination mode is activated to limit the page size
|
||||
$scope.paginateActive = true
|
||||
|
||||
## The events displayed on the page
|
||||
$scope.events = eventsPromise
|
||||
|
||||
## Current virtual page
|
||||
$scope.page = 2
|
||||
|
||||
##
|
||||
# Adds a bucket of events to the bottom of the page, grouped by month
|
||||
##
|
||||
$scope.loadMoreEvents = ->
|
||||
Event.query {page: $scope.page}, (data)->
|
||||
$scope.events = $scope.events.concat data
|
||||
paginationCheck(data, $scope.events)
|
||||
$scope.page += 1
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
paginationCheck(eventsPromise, $scope.events)
|
||||
|
||||
|
||||
##
|
||||
# Check if all events are already displayed OR if the button 'load more events'
|
||||
# is required
|
||||
# @param lastEvents {Array} last events loaded onto the diplay (ie. last "page")
|
||||
# @param events {Array} full list of events displayed on the page (not only the last retrieved)
|
||||
##
|
||||
paginationCheck = (lastEvents, events)->
|
||||
if lastEvents.length > 0
|
||||
$scope.paginateActive = false if events.length >= lastEvents[0].nb_total_events
|
||||
else
|
||||
$scope.paginateActive = false
|
||||
|
||||
|
||||
|
||||
# init the controller (call at the end !)
|
||||
initialize()
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the reservations listing page for a specific event
|
||||
##
|
||||
Application.Controllers.controller "ShowEventReservationsController", ["$scope", 'eventPromise', 'reservationsPromise', ($scope, eventPromise, reservationsPromise) ->
|
||||
|
||||
## retrieve the event from the ID provided in the current URL
|
||||
$scope.event = eventPromise
|
||||
|
||||
## list of reservations for the current event
|
||||
$scope.reservations = reservationsPromise
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the event creation page
|
||||
##
|
||||
Application.Controllers.controller "NewEventController", ["$scope", "$state", "$locale", 'Event', 'Category', 'CSRF', '_t'
|
||||
, ($scope, $state, $locale, Event, Category, CSRF, _t) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/events/"
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = 'post'
|
||||
|
||||
## Default event parameters
|
||||
$scope.event =
|
||||
event_files_attributes: []
|
||||
start_date: new Date()
|
||||
end_date: new Date()
|
||||
start_time: new Date()
|
||||
end_time: new Date()
|
||||
all_day: 'false'
|
||||
recurrence: 'none'
|
||||
category_ids: []
|
||||
|
||||
## Possible types of recurrences for an event
|
||||
$scope.recurrenceTypes = [
|
||||
{label: _t('none'), value: 'none'},
|
||||
{label: _t('every_days'), value: 'day'},
|
||||
{label: _t('every_week'), value: 'week'},
|
||||
{label: _t('every_month'), value: 'month'},
|
||||
{label: _t('every_year'), value: 'year'}
|
||||
]
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
|
||||
## Using the EventsController
|
||||
new EventsController($scope, $state, Event, Category)
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the events edition page
|
||||
##
|
||||
Application.Controllers.controller "EditEventController", ["$scope", "$state", "$stateParams", "$locale", 'Event', 'Category', 'CSRF', 'eventPromise'
|
||||
, ($scope, $state, $stateParams, $locale, Event, Category, CSRF, eventPromise) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/events/" + $stateParams.id
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = 'put'
|
||||
|
||||
## 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;
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
# init the dates to JS objects
|
||||
$scope.event.start_date = moment($scope.event.start_date).toDate()
|
||||
$scope.event.end_date = moment($scope.event.end_date).toDate()
|
||||
|
||||
## Using the EventsController
|
||||
new EventsController($scope, $state, Event, Category)
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
495
app/assets/javascripts/controllers/admin/events.coffee.erb
Normal file
495
app/assets/javascripts/controllers/admin/events.coffee.erb
Normal file
@ -0,0 +1,495 @@
|
||||
'use strict'
|
||||
|
||||
### COMMON CODE ###
|
||||
|
||||
##
|
||||
# Provides a set of common properties and methods to the $scope parameter. They are used
|
||||
# in the various events' admin controllers.
|
||||
#
|
||||
# Provides :
|
||||
# - $scope.datePicker = {}
|
||||
# - $scope.submited(content)
|
||||
# - $scope.cancel()
|
||||
# - $scope.addFile()
|
||||
# - $scope.deleteFile(file)
|
||||
# - $scope.fileinputClass(v)
|
||||
# - $scope.toggleStartDatePicker($event)
|
||||
# - $scope.toggleEndDatePicker($event)
|
||||
# - $scope.toggleRecurrenceEnd(e)
|
||||
#
|
||||
# Requires :
|
||||
# - $scope.event.event_files_attributes = []
|
||||
# - $state (Ui-Router) [ 'app.public.events_list' ]
|
||||
##
|
||||
class EventsController
|
||||
constructor: ($scope, $state) ->
|
||||
|
||||
## default parameters for AngularUI-Bootstrap datepicker
|
||||
$scope.datePicker =
|
||||
format: Fablab.uibDateFormat
|
||||
startOpened: false # default: datePicker is not shown
|
||||
endOpened: false
|
||||
recurrenceEndOpened: false
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
|
||||
|
||||
##
|
||||
# For use with ngUpload (https://github.com/twilson63/ngUpload).
|
||||
# Intended to be the callback when an upload is done: any raised error will be stacked in the
|
||||
# $scope.alerts array. If everything goes fine, the user is redirected to the project page.
|
||||
# @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.events_list')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Changes the user's view to the events list page
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.public.events_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 event's attachements list.
|
||||
##
|
||||
$scope.addFile = ->
|
||||
$scope.event.event_files_attributes.push {}
|
||||
|
||||
|
||||
|
||||
##
|
||||
# This will remove the given file from the event's 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.event.event_files_attributes.indexOf(file)
|
||||
if file.id?
|
||||
file._destroy = true
|
||||
else
|
||||
$scope.event.event_files_attributes.splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Show/Hide the "start" datepicker (open the drop down/close it)
|
||||
##
|
||||
$scope.toggleStartDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.startOpened = !$scope.datePicker.startOpened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Show/Hide the "end" datepicker (open the drop down/close it)
|
||||
##
|
||||
$scope.toggleEndDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.endOpened = !$scope.datePicker.endOpened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Masks/displays the recurrence pane allowing the admin to set the current event as recursive
|
||||
##
|
||||
$scope.toggleRecurrenceEnd = (e)->
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
$scope.datePicker.recurrenceEndOpened = !$scope.datePicker.recurrenceEndOpened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Initialize a new price item in the additional prices list
|
||||
##
|
||||
$scope.addPrice = ->
|
||||
$scope.event.prices.push({
|
||||
category: null,
|
||||
amount: null,
|
||||
})
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the events listing page (admin view)
|
||||
##
|
||||
Application.Controllers.controller "AdminEventsController", ["$scope", "$state", 'dialogs', '$uibModal', 'growl', 'Event', 'Category', 'EventTheme', 'AgeRange', 'PriceCategory', 'eventsPromise', 'categoriesPromise', 'themesPromise', 'ageRangesPromise', 'priceCategoriesPromise', '_t'
|
||||
, ($scope, $state, dialogs, $uibModal, growl, Event, Category, EventTheme, AgeRange, PriceCategory, eventsPromise, categoriesPromise, themesPromise, ageRangesPromise, priceCategoriesPromise, _t) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## By default, the pagination mode is activated to limit the page size
|
||||
$scope.paginateActive = true
|
||||
|
||||
## The events displayed on the page
|
||||
$scope.events = eventsPromise
|
||||
|
||||
## Current virtual page
|
||||
$scope.page = 2
|
||||
|
||||
## Temporary datastore for creating new elements
|
||||
$scope.inserted =
|
||||
category: null
|
||||
theme: null
|
||||
age_range: null
|
||||
|
||||
## List of categories for the events
|
||||
$scope.categories = categoriesPromise
|
||||
|
||||
## List of events themes
|
||||
$scope.themes = themesPromise
|
||||
|
||||
## List of age ranges
|
||||
$scope.ageRanges = ageRangesPromise
|
||||
|
||||
## List of price categories for the events
|
||||
$scope.priceCategories = priceCategoriesPromise
|
||||
|
||||
##
|
||||
# Adds a bucket of events to the bottom of the page, grouped by month
|
||||
##
|
||||
$scope.loadMoreEvents = ->
|
||||
Event.query {page: $scope.page}, (data)->
|
||||
$scope.events = $scope.events.concat data
|
||||
paginationCheck(data, $scope.events)
|
||||
$scope.page += 1
|
||||
|
||||
|
||||
##
|
||||
# Saves a new element / Update an existing one to the server (form validation callback)
|
||||
# @param model {string} model name
|
||||
# @param data {Object} element name
|
||||
# @param [id] {number} element id, in case of update
|
||||
##
|
||||
$scope.saveElement = (model, data, id) ->
|
||||
if id?
|
||||
getModel(model)[0].update {id: id}, data
|
||||
else
|
||||
getModel(model)[0].save data, (resp)->
|
||||
getModel(model)[1][getModel(model)[1].length-1].id = resp.id
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Deletes the element at the specified index
|
||||
# @param model {string} model name
|
||||
# @param index {number} element index in the $scope[model] array
|
||||
##
|
||||
$scope.removeElement = (model, index) ->
|
||||
if model == 'category' and getModel(model)[1].length == 1
|
||||
growl.error(_t('at_least_one_category_is_required')+' '+_t('unable_to_delete_the_last_one'))
|
||||
return false
|
||||
if getModel(model)[1][index].related_to > 0
|
||||
growl.error(_t('unable_to_delete_ELEMENT_already_in_use_NUMBER_times', {ELEMENT:model, NUMBER:getModel(model)[1][index].related_to}, "messageformat"))
|
||||
return false
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_ELEMENT', {ELEMENT:model}, "messageformat")
|
||||
, -> # delete confirmed
|
||||
getModel(model)[0].delete getModel(model)[1][index], null, ->
|
||||
getModel(model)[1].splice(index, 1)
|
||||
, ->
|
||||
growl.error(_t('unable_to_delete_an_error_occured'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Creates a new empty entry in the $scope[model] array
|
||||
# @param model {string} model name
|
||||
##
|
||||
$scope.addElement = (model) ->
|
||||
$scope.inserted[model] =
|
||||
name: ''
|
||||
related_to: 0
|
||||
getModel(model)[1].push($scope.inserted[model])
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Removes the newly inserted but not saved element / Cancel the current element modification
|
||||
# @param model {string} model name
|
||||
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||
# @param index {number} element index in the $scope[model] array
|
||||
##
|
||||
$scope.cancelElement = (model, rowform, index) ->
|
||||
if getModel(model)[1][index].id?
|
||||
rowform.$cancel()
|
||||
else
|
||||
getModel(model)[1].splice(index, 1)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal dialog allowing the definition of a new price category.
|
||||
# Save it once filled and handle the result.
|
||||
##
|
||||
$scope.newPriceCategory = ->
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "admin/events/price_form.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
category: -> {}
|
||||
controller: 'PriceCategoryController'
|
||||
.result['finally'](null).then (p_cat) ->
|
||||
# save the price category to the API
|
||||
PriceCategory.save p_cat, (cat) ->
|
||||
$scope.priceCategories.push(cat)
|
||||
growl.success(_t('price_category_successfully_created'))
|
||||
, (err)->
|
||||
growl.error(_t('unable_to_add_the_price_category_check_name_already_used'))
|
||||
console.error(err)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Update the given price category with the new properties
|
||||
# to specify in a modal dialog
|
||||
# @param index {number} index of the caterory in the $scope.priceCategories array
|
||||
# @param id {number} price category ID, must match the ID of the category at the index specified above
|
||||
##
|
||||
$scope.editPriceCategory = (id, index) ->
|
||||
if $scope.priceCategories[index].id != id
|
||||
growl.error(_t('unexpected_error_occurred_please_refresh'))
|
||||
else
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "admin/events/price_form.html" %>'
|
||||
size: 'md'
|
||||
resolve:
|
||||
category: -> $scope.priceCategories[index]
|
||||
controller: 'PriceCategoryController'
|
||||
.result['finally'](null).then (p_cat) ->
|
||||
# update the price category to the API
|
||||
PriceCategory.update {id: id}, {price_category: p_cat}, (cat) ->
|
||||
$scope.priceCategories[index] = cat
|
||||
growl.success(_t('price_category_successfully_updated'))
|
||||
, (err)->
|
||||
growl.error(_t('unable_to_update_the_price_category'))
|
||||
console.error(err)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Delete the given price category from the API
|
||||
# @param index {number} index of the caterory in the $scope.priceCategories array
|
||||
# @param id {number} price category ID, must match the ID of the category at the index specified above
|
||||
##
|
||||
$scope.removePriceCategory = (id, index) ->
|
||||
if $scope.priceCategories[index].id != id
|
||||
growl.error(_t('unexpected_error_occurred_please_refresh'))
|
||||
else if $scope.priceCategories[index].events > 0
|
||||
growl.error(_t('unable_to_delete_this_price_category_because_it_is_already_used'))
|
||||
else
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_price_category')
|
||||
, -> # delete confirmed
|
||||
PriceCategory.remove {id: id}, -> # successfully deleted
|
||||
growl.success _t('price_category_successfully_deleted')
|
||||
$scope.priceCategories.splice(index, 1)
|
||||
, ->
|
||||
growl.error _t('price_category_deletion_failed')
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
paginationCheck(eventsPromise, $scope.events)
|
||||
|
||||
|
||||
##
|
||||
# Check if all events are already displayed OR if the button 'load more events'
|
||||
# is required
|
||||
# @param lastEvents {Array} last events loaded onto the diplay (ie. last "page")
|
||||
# @param events {Array} full list of events displayed on the page (not only the last retrieved)
|
||||
##
|
||||
paginationCheck = (lastEvents, events)->
|
||||
if lastEvents.length > 0
|
||||
$scope.paginateActive = false if events.length >= lastEvents[0].nb_total_events
|
||||
else
|
||||
$scope.paginateActive = false
|
||||
|
||||
##
|
||||
# Return the model and the datastore matching the given name
|
||||
# @param name {string} 'category', 'theme' or 'age_range'
|
||||
# @return {[Object, Array]} model and datastore
|
||||
##
|
||||
getModel = (name) ->
|
||||
switch name
|
||||
when 'category' then [Category, $scope.categories]
|
||||
when 'theme' then [EventTheme, $scope.themes]
|
||||
when 'age_range' then [AgeRange, $scope.ageRanges]
|
||||
else [null, []]
|
||||
|
||||
|
||||
# init the controller (call at the end !)
|
||||
initialize()
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the reservations listing page for a specific event
|
||||
##
|
||||
Application.Controllers.controller "ShowEventReservationsController", ["$scope", 'eventPromise', 'reservationsPromise', ($scope, eventPromise, reservationsPromise) ->
|
||||
|
||||
## retrieve the event from the ID provided in the current URL
|
||||
$scope.event = eventPromise
|
||||
|
||||
## list of reservations for the current event
|
||||
$scope.reservations = reservationsPromise
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# 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) ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/events/"
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = 'post'
|
||||
|
||||
## List of categories for the events
|
||||
$scope.categories = categoriesPromise
|
||||
|
||||
## List of events themes
|
||||
$scope.themes = themesPromise
|
||||
|
||||
## List of age ranges
|
||||
$scope.ageRanges = ageRangesPromise
|
||||
|
||||
## List of availables price's categories
|
||||
$scope.priceCategories = priceCategoriesPromise
|
||||
|
||||
## Default event parameters
|
||||
$scope.event =
|
||||
event_files_attributes: []
|
||||
start_date: new Date()
|
||||
end_date: new Date()
|
||||
start_time: new Date()
|
||||
end_time: new Date()
|
||||
all_day: 'false'
|
||||
recurrence: 'none'
|
||||
category_id: null
|
||||
prices: []
|
||||
|
||||
## Possible types of recurrences for an event
|
||||
$scope.recurrenceTypes = [
|
||||
{label: _t('none'), value: 'none'},
|
||||
{label: _t('every_days'), value: 'day'},
|
||||
{label: _t('every_week'), value: 'week'},
|
||||
{label: _t('every_month'), value: 'month'},
|
||||
{label: _t('every_year'), value: 'year'}
|
||||
]
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM;
|
||||
|
||||
|
||||
## Using the EventsController
|
||||
new EventsController($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) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = "/api/events/" + $stateParams.id
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = 'put'
|
||||
|
||||
## 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
|
||||
|
||||
## List of availables price's categories
|
||||
$scope.priceCategories = priceCategoriesPromise
|
||||
|
||||
## List of events themes
|
||||
$scope.themes = themesPromise
|
||||
|
||||
## List of age ranges
|
||||
$scope.ageRanges = ageRangesPromise
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
# init the dates to JS objects
|
||||
$scope.event.start_date = moment($scope.event.start_date).toDate()
|
||||
$scope.event.end_date = moment($scope.event.end_date).toDate()
|
||||
|
||||
## Using the EventsController
|
||||
new EventsController($scope, $state)
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
@ -334,7 +334,7 @@ Application.Controllers.controller "GraphsController", ["$scope", "$state", "$ro
|
||||
callback(results)
|
||||
recursiveCb()
|
||||
else # palmares (ranking)
|
||||
queryElasticRanking index.es_type_key, $scope.ranking.groupCriterion, $scope.ranking.sortCriterion, index.graph.limit, (results, error) ->
|
||||
queryElasticRanking index.es_type_key, $scope.ranking.groupCriterion, $scope.ranking.sortCriterion, (results, error) ->
|
||||
if (error)
|
||||
callback([], error)
|
||||
else
|
||||
@ -373,17 +373,18 @@ Application.Controllers.controller "GraphsController", ["$scope", "$state", "$ro
|
||||
|
||||
##
|
||||
# For ranking displays, run the elasticSearch query to retreive the /stats/type aggregations
|
||||
# @param esType {String} elasticSearch document type (subscription|machine|training|...)
|
||||
# @param statType {String} statistics type (year|month|hour|booking|...)
|
||||
# @param esType {string} elasticSearch document type (subscription|machine|training|...)
|
||||
# @param groupKey {string} statistics subtype or custom field
|
||||
# @param sortKey {string} statistics type or 'ca'
|
||||
# @param callback {function} function be to run after results were retrieved,
|
||||
# it will receive two parameters : results {Array}, error {String} (if any)
|
||||
##
|
||||
queryElasticRanking = (esType, groupKey, sortKey, limit, callback) ->
|
||||
queryElasticRanking = (esType, groupKey, sortKey, callback) ->
|
||||
# handle invalid callback
|
||||
if typeof(callback) != "function"
|
||||
console.error('[graphsController::queryElasticRanking] Error: invalid callback provided')
|
||||
return
|
||||
if !esType or !groupKey or !sortKey or typeof limit != 'number'
|
||||
if !esType or !groupKey or !sortKey
|
||||
callback([], '[graphsController::queryElasticRanking] Error: invalid parameters provided')
|
||||
|
||||
# run query
|
||||
|
@ -133,6 +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 refunds (R[text]) - does not apply here
|
||||
sample = sample.replace(/R\[([^\]]+)\]/g, "")
|
||||
sample
|
||||
@ -494,6 +496,7 @@ Application.Controllers.controller 'AvoirModalController', ["$scope", "$uibModal
|
||||
{name: _t('by_cash'), value: 'cash'}
|
||||
{name: _t('by_cheque'), value: 'cheque'}
|
||||
{name: _t('by_transfer'), value: 'transfer'}
|
||||
{name: _t('by_wallet'), value: 'wallet'}
|
||||
]
|
||||
|
||||
## If a subscription was took with the current invoice, should it be canceled or not
|
||||
|
@ -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'
|
||||
, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member) ->
|
||||
Application.Controllers.controller "AdminMembersController", ["$scope", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export'
|
||||
, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member, Export) ->
|
||||
|
||||
|
||||
|
||||
@ -204,6 +204,16 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
|
||||
resetSearchMember()
|
||||
memberSearch()
|
||||
|
||||
##
|
||||
# Callback to alert the admin that the export request was acknowledged and is
|
||||
# processing right now.
|
||||
##
|
||||
$scope.alertExport = (type) ->
|
||||
Export.status({category: 'users', type: type}).then (res) ->
|
||||
unless (res.data.exists)
|
||||
growl.success _t('export_is_running_you_ll_be_notified_when_its_ready')
|
||||
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
@ -260,8 +270,8 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
|
||||
##
|
||||
# Controller used in the member edition page
|
||||
##
|
||||
Application.Controllers.controller "EditMemberController", ["$scope", "$state", "$stateParams", "Member", 'Training', 'dialogs', 'growl', 'Group', 'Subscription', 'CSRF', 'memberPromise', 'tagsPromise', '$uibModal', 'Plan', '$filter', '_t'
|
||||
, ($scope, $state, $stateParams, Member, Training, dialogs, growl, Group, Subscription, CSRF, memberPromise, tagsPromise, $uibModal, Plan, $filter, _t) ->
|
||||
Application.Controllers.controller "EditMemberController", ["$scope", "$state", "$stateParams", "Member", 'Training', 'dialogs', 'growl', 'Group', 'Subscription', 'CSRF', 'memberPromise', 'tagsPromise', '$uibModal', 'Plan', '$filter', '_t', 'walletPromise', 'transactionsPromise', 'activeProviderPromise', 'Wallet'
|
||||
, ($scope, $state, $stateParams, Member, Training, dialogs, growl, Group, Subscription, CSRF, memberPromise, tagsPromise, $uibModal, Plan, $filter, _t, walletPromise, transactionsPromise, activeProviderPromise, Wallet) ->
|
||||
|
||||
|
||||
|
||||
@ -300,6 +310,17 @@ Application.Controllers.controller "EditMemberController", ["$scope", "$state",
|
||||
## Profiles types (student/standard/...)
|
||||
$scope.groups = []
|
||||
|
||||
## the user wallet
|
||||
$scope.wallet = walletPromise
|
||||
|
||||
## user wallet transactions
|
||||
$scope.transactions = transactionsPromise
|
||||
|
||||
## used in wallet partial template to identify parent view
|
||||
$scope.view = 'member_edit'
|
||||
|
||||
# current active authentication provider
|
||||
$scope.activeProvider = activeProviderPromise
|
||||
|
||||
|
||||
##
|
||||
@ -396,6 +417,39 @@ Application.Controllers.controller "EditMemberController", ["$scope", "$state",
|
||||
$scope.subscription = subscription
|
||||
|
||||
|
||||
$scope.createWalletCreditModal = (user, wallet)->
|
||||
modalInstance = $uibModal.open
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "wallet/credit_modal.html" %>'
|
||||
controller: ['$scope', '$uibModalInstance', 'Wallet', '$locale', ($scope, $uibModalInstance, Wallet, $locale) ->
|
||||
|
||||
## currency symbol for the current locale (cf. angular-i18n)
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
##
|
||||
# Modal dialog validation callback
|
||||
##
|
||||
$scope.ok = ->
|
||||
Wallet.credit { id: wallet.id }, { amount: $scope.amount }, (_wallet)->
|
||||
|
||||
growl.success(_t('wallet_credit_successfully'))
|
||||
$uibModalInstance.close(_wallet)
|
||||
, (error)->
|
||||
growl.error(_t('a_problem_occurred_for_wallet_credit'))
|
||||
|
||||
##
|
||||
# Modal dialog cancellation callback
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
# once the form was validated succesfully ...
|
||||
modalInstance.result.then (wallet) ->
|
||||
$scope.wallet = wallet
|
||||
Wallet.transactions {id: wallet.id}, (transactions) ->
|
||||
$scope.transactions = transactions
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
|
@ -60,6 +60,18 @@ class PlanController
|
||||
|
||||
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
'use strict'
|
||||
|
||||
##
|
||||
# Controller used in price category creation/edition form dialog
|
||||
##
|
||||
Application.Controllers.controller "PriceCategoryController", ["$scope", "$uibModalInstance", "category"
|
||||
, ($scope, $uibModalInstance, category) ->
|
||||
|
||||
## Price category to edit/empty object for new category
|
||||
$scope.category = category
|
||||
|
||||
##
|
||||
# Callback for form validation
|
||||
##
|
||||
$scope.ok = ->
|
||||
$uibModalInstance.close($scope.category)
|
||||
|
||||
##
|
||||
# Do not validate the modifications, hide the modal
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
@ -3,8 +3,8 @@
|
||||
##
|
||||
# Controller used in the prices edition page
|
||||
##
|
||||
Application.Controllers.controller "EditPricingController", ["$scope", "$state", '$uibModal', 'TrainingsPricing', '$filter', 'Credit', 'Pricing', 'Plan', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', '_t'
|
||||
, ($scope, $state, $uibModal, TrainingsPricing, $filter, Credit, Pricing, Plan, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, _t) ->
|
||||
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) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
## List of machines prices (not considering any plan)
|
||||
@ -34,6 +34,9 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
## List of machines
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## List of coupons
|
||||
$scope.coupons = couponsPromise
|
||||
|
||||
## The plans list ordering. Default: by group
|
||||
$scope.orderPlans = 'group_id'
|
||||
|
||||
@ -275,7 +278,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
##
|
||||
$scope.deletePlan = (plans, id) ->
|
||||
if typeof id != 'number'
|
||||
console.error('[editPricingController::deletePlan] Error: invalid id parameter')
|
||||
console.error('[EditPricingController::deletePlan] Error: invalid id parameter')
|
||||
else
|
||||
# open a confirmation dialog
|
||||
dialogs.confirm
|
||||
@ -287,10 +290,10 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
# the admin has confirmed, delete the plan
|
||||
Plan.delete {id: id}, (res) ->
|
||||
growl.success(_t('subscription_plan_was_successfully_deleted'))
|
||||
$scope.plans.splice(findPlanIdxById(plans, id), 1)
|
||||
$scope.plans.splice(findItemIdxById(plans, id), 1)
|
||||
|
||||
, (error) ->
|
||||
console.error('[editPricingController::deletePlan] Error: '+error.statusText) if error.statusText
|
||||
console.error('[EditPricingController::deletePlan] Error: '+error.statusText) if error.statusText
|
||||
growl.error(_t('unable_to_delete_the_specified_subscription_an_error_occurred'))
|
||||
|
||||
|
||||
@ -308,12 +311,70 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
##
|
||||
# Delete a coupon from the server's database and, in case of success, from the list in memory
|
||||
# @param coupons {Array<Object>} should be called with $scope.coupons
|
||||
# @param id {number} ID of the coupon to delete
|
||||
##
|
||||
$scope.deleteCoupon = (coupons, id) ->
|
||||
if typeof id != 'number'
|
||||
console.error('[EditPricingController::deleteCoupon] Error: invalid id parameter')
|
||||
else
|
||||
# open a confirmation dialog
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_coupon')
|
||||
, ->
|
||||
# the admin has confirmed, delete the coupon
|
||||
Coupon.delete {id: id}, (res) ->
|
||||
growl.success(_t('coupon_was_successfully_deleted'))
|
||||
$scope.coupons.splice(findItemIdxById(coupons, id), 1)
|
||||
|
||||
findPlanIdxById = (plans, id)->
|
||||
(plans.map (plan)->
|
||||
plan.id
|
||||
).indexOf(id)
|
||||
, (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'))
|
||||
else
|
||||
growl.error(_t('unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal allowing to select an user and send him the details of the provided coupon
|
||||
# @param coupon {Object} The coupon to send
|
||||
##
|
||||
$scope.sendCouponToUser = (coupon) ->
|
||||
$uibModal.open
|
||||
templateUrl: '<%= asset_path "admin/pricing/sendCoupon.html" %>'
|
||||
resolve:
|
||||
coupon: -> coupon
|
||||
size: 'md'
|
||||
controller: ['$scope', '$uibModalInstance', 'Coupon', 'coupon', '_t', ($scope, $uibModalInstance, Coupon, coupon, _t) ->
|
||||
|
||||
## Member, receiver of the coupon
|
||||
$scope.ctrl =
|
||||
member: null
|
||||
|
||||
## Details of the coupon to send
|
||||
$scope.coupon = coupon
|
||||
|
||||
## 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}))
|
||||
$uibModalInstance.close({user_id: $scope.ctrl.member.id})
|
||||
, (err) ->
|
||||
growl.error(_t('an_error_occurred_unable_to_send_the_coupon'))
|
||||
|
||||
## Callback to close the modal and cancel the sending process
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
@ -329,6 +390,19 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Retrieve an item index by its ID from the given array of objects
|
||||
# @param items {Array<{id:number}>}
|
||||
# @param id {number}
|
||||
# @returns {number} item index in the provided array
|
||||
##
|
||||
findItemIdxById = (items, id)->
|
||||
(items.map (item)->
|
||||
item.id
|
||||
).indexOf(id)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Group the given credits array into a map associating the plan ID with its associated trainings/machines
|
||||
# @return {Object} the association map
|
||||
|
@ -45,7 +45,6 @@ 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.eventReducedAmountAlert = { name: 'event_reduced_amount_alert', value: settingsPromise.event_reduced_amount_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 }
|
||||
@ -75,6 +74,14 @@ Application.Controllers.controller "SettingsController", ["$scope", 'Setting', '
|
||||
name: 'booking_cancel_delay'
|
||||
value: parseInt(settingsPromise.booking_cancel_delay)
|
||||
|
||||
$scope.enableReminder =
|
||||
name: 'reminder_enable'
|
||||
value: (settingsPromise.reminder_enable == 'true')
|
||||
|
||||
$scope.reminderDelay =
|
||||
name: 'reminder_delay'
|
||||
value: parseInt(settingsPromise.reminder_delay)
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -108,7 +115,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:setting.name}))
|
||||
growl.success(_t('customization_of_SETTING_successfully_saved', {SETTING:_t(setting.name)}))
|
||||
, (error)->
|
||||
console.log(error)
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "StatisticsController", ["$scope", "$state", "$rootScope", "Statistics", "es", "Member", '_t', 'membersPromise', 'statisticsPromise'
|
||||
, ($scope, $state, $rootScope, Statistics, es, Member, _t, membersPromise, statisticsPromise) ->
|
||||
|
||||
|
||||
Application.Controllers.controller "StatisticsController", ["$scope", "$state", "$rootScope", '$uibModal', "es", "Member", '_t', 'membersPromise', 'statisticsPromise'
|
||||
, ($scope, $state, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise) ->
|
||||
|
||||
|
||||
|
||||
@ -53,9 +55,13 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
## total of the stat field for non simple types
|
||||
$scope.sumStat = 0
|
||||
|
||||
## Results of custom aggregations for the current type
|
||||
$scope.customAggs = {}
|
||||
|
||||
## default: results are not sorted
|
||||
$scope.sorting =
|
||||
ca: 'none'
|
||||
date: 'desc'
|
||||
|
||||
## active tab will be set here
|
||||
$scope.selectedIndex = null
|
||||
@ -148,6 +154,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
$scope.customFilter.value = null
|
||||
$scope.customFilter.exclude = false
|
||||
$scope.sorting.ca = 'none'
|
||||
$scope.sorting.date = 'desc'
|
||||
buildCustomFiltersList()
|
||||
refreshStats()
|
||||
|
||||
@ -271,6 +278,33 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal dialog asking the user for details about exporting the statistics tables to an excel file
|
||||
##
|
||||
$scope.exportToExcel = ->
|
||||
options =
|
||||
templateUrl: '<%= asset_path "admin/statistics/export.html" %>'
|
||||
size: 'sm'
|
||||
controller: 'ExportStatisticsController'
|
||||
resolve:
|
||||
dates: ->
|
||||
start: $scope.datePickerStart.selected
|
||||
end: $scope.datePickerEnd.selected
|
||||
query: ->
|
||||
custom = buildCustomFilterQuery()
|
||||
buildElasticDataQuery($scope.type.active.key, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting)
|
||||
index: ->
|
||||
key: $scope.selectedIndex.es_type_key
|
||||
type: ->
|
||||
key: $scope.type.active.key
|
||||
|
||||
$uibModal.open options
|
||||
.result['finally'](null).then (info)->
|
||||
console.log(info)
|
||||
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
@ -306,14 +340,10 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
$scope.sumCA = 0
|
||||
$scope.averageAge = 0
|
||||
$scope.sumStat = 0
|
||||
$scope.customAggs = {}
|
||||
$scope.totalHits = null
|
||||
$scope.searchDate = new Date()
|
||||
custom = null
|
||||
if $scope.customFilter.criterion and $scope.customFilter.criterion.key and $scope.customFilter.value
|
||||
custom = {}
|
||||
custom.key = $scope.customFilter.criterion.key
|
||||
custom.value = $scope.customFilter.value
|
||||
custom.exclude = $scope.customFilter.exclude
|
||||
custom = buildCustomFilterQuery()
|
||||
queryElasticStats $scope.selectedIndex.es_type_key, $scope.type.active.key, custom, (res, err)->
|
||||
if (err)
|
||||
console.error("[statisticsController::refreshStats] Unable to refresh due to "+err)
|
||||
@ -324,6 +354,8 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
$scope.averageAge = Math.round(res.aggregations.average_age.value * 100) / 100
|
||||
$scope.sumStat = res.aggregations.total_stat.value
|
||||
$scope.scrollId = res._scroll_id
|
||||
for custom in $scope.type.active.custom_aggregations
|
||||
$scope.customAggs[custom.field] = res.aggregations[custom.field].value
|
||||
|
||||
|
||||
|
||||
@ -347,6 +379,9 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
"type": index
|
||||
"size": RESULTS_PER_PAGE
|
||||
"scroll": ES_SCROLL_TIME+'m'
|
||||
"stat-type": type
|
||||
"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)
|
||||
, (error, response) ->
|
||||
if (error)
|
||||
@ -429,7 +464,7 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
"field": "age"
|
||||
"total_stat":
|
||||
"sum":
|
||||
"field": "sta"
|
||||
"field": "stat"
|
||||
}
|
||||
q
|
||||
|
||||
@ -485,8 +520,141 @@ Application.Controllers.controller "StatisticsController", ["$scope", "$state",
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Build and return an object according to the custom filter set by the user, used to request elasticsearch
|
||||
# @return {Object|null}
|
||||
##
|
||||
buildCustomFilterQuery = ->
|
||||
custom = null
|
||||
if !angular.isUndefinedOrNull($scope.customFilter.criterion) and
|
||||
!angular.isUndefinedOrNull($scope.customFilter.criterion.key) and
|
||||
!angular.isUndefinedOrNull($scope.customFilter.value)
|
||||
custom = {}
|
||||
custom.key = $scope.customFilter.criterion.key
|
||||
custom.value = $scope.customFilter.value
|
||||
custom.exclude = $scope.customFilter.exclude
|
||||
custom
|
||||
|
||||
|
||||
|
||||
# init the controller (call at the end !)
|
||||
initialize()
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
Application.Controllers.controller 'ExportStatisticsController', [ '$scope', '$uibModalInstance', 'Export','dates', 'query', 'index', 'type', 'CSRF', 'growl', '_t'
|
||||
, ($scope, $uibModalInstance, Export, dates, query, index, type, CSRF, growl, _t) ->
|
||||
|
||||
## Retrieve Anti-CSRF tokens from cookies
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## Bindings for date range
|
||||
$scope.dates = dates
|
||||
|
||||
## Body of the query to export
|
||||
$scope.query = JSON.stringify(query)
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = '/stats/'+index.key+'/export'
|
||||
|
||||
## Key of the current search' statistic type
|
||||
$scope.typeKey = type.key
|
||||
|
||||
## Form action on the above URL
|
||||
$scope.method = "post"
|
||||
|
||||
## Anti-CSRF token to inject into the download form
|
||||
$scope.csrfToken = angular.element('meta[name="csrf-token"]')[0].content
|
||||
|
||||
## Binding of the export type (global / current)
|
||||
$scope.export =
|
||||
type: 'current'
|
||||
|
||||
## datePicker parameters for interval beginning
|
||||
$scope.exportStart =
|
||||
format: Fablab.uibDateFormat
|
||||
opened: false # default: datePicker is not shown
|
||||
minDate: null
|
||||
maxDate: moment().subtract(1, 'day').toDate()
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
## datePicker parameters for interval ending
|
||||
$scope.exportEnd =
|
||||
format: Fablab.uibDateFormat
|
||||
opened: false # default: datePicker is not shown
|
||||
minDate: null
|
||||
maxDate: moment().subtract(1, 'day').toDate()
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to open the datepicker (interval start)
|
||||
# @param $event {Object} jQuery event object
|
||||
##
|
||||
$scope.toggleStartDatePicker = ($event) ->
|
||||
$scope.exportStart.opened = !$scope.exportStart.opened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to open the datepicker (interval end)
|
||||
# @param $event {Object} jQuery event object
|
||||
##
|
||||
$scope.toggleEndDatePicker = ($event) ->
|
||||
$scope.exportEnd.opened = !$scope.exportEnd.opened
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback when exchanging the export type between 'global' and 'current view'
|
||||
# Adjust the query and the requesting url according to this type.
|
||||
##
|
||||
$scope.setRequest = ->
|
||||
if $scope.export.type == 'global'
|
||||
$scope.actionUrl = '/stats/global/export'
|
||||
$scope.query = JSON.stringify(
|
||||
"query":
|
||||
"bool":
|
||||
"must": [
|
||||
{
|
||||
"range":
|
||||
"date":
|
||||
"gte": moment($scope.dates.start).format()
|
||||
"lte": moment($scope.dates.end).format()
|
||||
}
|
||||
]
|
||||
)
|
||||
else
|
||||
$scope.actionUrl = '/stats/'+index.key+'/export'
|
||||
$scope.query = JSON.stringify(query)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to close the modal, telling the caller what is exported
|
||||
##
|
||||
$scope.exportData = ->
|
||||
statusQry = {category: 'statistics', type: $scope.export.type, query: $scope.query}
|
||||
unless $scope.export.type == 'global'
|
||||
statusQry['type'] = index.key
|
||||
statusQry['key'] = type.key
|
||||
|
||||
Export.status(statusQry).then (res) ->
|
||||
unless (res.data.exists)
|
||||
growl.success _t('export_is_running_you_ll_be_notified_when_its_ready')
|
||||
|
||||
$uibModalInstance.close(statusQry)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to cancel the export and close the modal
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
@ -1,7 +1,148 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "TrainingsController", ["$scope", "$state", "$uibModal", 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl'
|
||||
, ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl) ->
|
||||
### COMMON CODE ###
|
||||
|
||||
##
|
||||
# Provides a set of common callback methods to the $scope parameter. These methods are used
|
||||
# in the various trainings' admin controllers.
|
||||
#
|
||||
# Provides :
|
||||
# - $scope.submited(content)
|
||||
# - $scope.fileinputClass(v)
|
||||
#
|
||||
# Requires :
|
||||
# - $state (Ui-Router) [ 'app.admin.trainings' ]
|
||||
##
|
||||
class TrainingsController
|
||||
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 trainings 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.admin.trainings')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Changes the current user's view, redirecting him to the machines list
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.admin.trainings')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# 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'
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the training creation page (admin)
|
||||
##
|
||||
Application.Controllers.controller "NewTrainingController", [ '$scope', '$state', 'machinesPromise', 'CSRF'
|
||||
, ($scope, $state, machinesPromise, CSRF) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## Form action on the following URL
|
||||
$scope.method = 'post'
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = '/api/trainings/'
|
||||
|
||||
## list of machines
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## Using the TrainingsController
|
||||
new TrainingsController($scope, $state)
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the training edition page (admin)
|
||||
##
|
||||
Application.Controllers.controller "EditTrainingController", [ '$scope', '$state', '$stateParams', 'trainingPromise', 'machinesPromise', 'CSRF'
|
||||
, ($scope, $state, $stateParams, trainingPromise, machinesPromise, CSRF) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## Form action on the following URL
|
||||
$scope.method = 'patch'
|
||||
|
||||
## API URL where the form will be posted
|
||||
$scope.actionUrl = '/api/trainings/' + $stateParams.id
|
||||
|
||||
## Details of the training to edit (id in URL)
|
||||
$scope.training = trainingPromise
|
||||
|
||||
## list of machines
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
CSRF.setMetaTags()
|
||||
|
||||
## Using the TrainingsController
|
||||
new TrainingsController($scope, $state)
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the trainings management page, allowing admins users to see and manage the list of trainings and reservations.
|
||||
##
|
||||
Application.Controllers.controller "TrainingsAdminController", ["$scope", "$state", "$uibModal", 'Training', 'trainingsPromise', 'machinesPromise', '_t', 'growl', 'dialogs'
|
||||
, ($scope, $state, $uibModal, Training, trainingsPromise, machinesPromise, _t, growl, dialogs) ->
|
||||
|
||||
|
||||
|
||||
@ -40,35 +181,6 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Create a new empty training object and append it to the $scope.trainings list
|
||||
##
|
||||
$scope.addTraining = ->
|
||||
$scope.inserted =
|
||||
name: ''
|
||||
machine_ids: []
|
||||
$scope.trainings.push($scope.inserted)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Saves a new training / Update an existing training to the server (form validation callback)
|
||||
# @param data {Object} training name, associated machine(s) and default places number
|
||||
# @param id {number} training id, in case of update
|
||||
##
|
||||
$scope.saveTraining = (data, id) ->
|
||||
if id?
|
||||
Training.update {id: id},
|
||||
training: data
|
||||
else
|
||||
Training.save
|
||||
training: data
|
||||
, (resp) ->
|
||||
$scope.trainings[$scope.trainings.length-1] = resp
|
||||
console.log(resp)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Removes the newly inserted but not saved training / Cancel the current training modification
|
||||
# @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||
@ -138,6 +250,12 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
|
||||
# @param training {Object} training to delete
|
||||
##
|
||||
$scope.removeTraining = (index, training)->
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_training')
|
||||
, -> # deletion confirmed
|
||||
training.$delete ->
|
||||
$scope.trainings.splice(index, 1)
|
||||
growl.info(_t('training_successfully_deleted'))
|
||||
@ -146,25 +264,6 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open the modal to edit description of the training
|
||||
# @param training {Object} Training to edit description
|
||||
##
|
||||
$scope.openModalToSetDescription = (training)->
|
||||
$uibModal.open(
|
||||
templateUrl: "<%= asset_path 'admin/trainings/modal_edit.html' %>"
|
||||
controller: ['$scope', '$uibModalInstance', 'Training', 'growl', ($scope, $uibModalInstance, Training, growl)->
|
||||
$scope.training = training
|
||||
$scope.save = ->
|
||||
Training.update id: training.id, { training: { description: $scope.training.description } }, (training)->
|
||||
$uibModalInstance.close()
|
||||
growl.success(_t('description_was_successfully_saved'))
|
||||
return
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Takes a month number and return its localized literal name
|
||||
# @param {Number} from 0 to 11
|
||||
@ -197,8 +296,15 @@ Application.Controllers.controller "TrainingsController", ["$scope", "$state", "
|
||||
# The selected training details will be loaded from the API and rendered into the accordions.
|
||||
##
|
||||
$scope.selectTrainingToMonitor = ->
|
||||
Training.get {id: $scope.monitoring.training.id}, (training) ->
|
||||
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'
|
||||
, ($rootScope, $scope, $window, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t) ->
|
||||
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) ->
|
||||
|
||||
|
||||
|
||||
@ -14,14 +14,24 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## Fab-manager's version
|
||||
$scope.version =
|
||||
version: ''
|
||||
|
||||
##
|
||||
# Set the current user to the provided value and initialize the session
|
||||
# @param user {Object} Rails/Devise user
|
||||
##
|
||||
$scope.setCurrentUser = (user) ->
|
||||
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: ''}
|
||||
|
||||
|
||||
##
|
||||
@ -88,6 +98,7 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
# default user's parameters
|
||||
$scope.user =
|
||||
is_allow_contact: true
|
||||
is_allow_newsletter: false
|
||||
|
||||
# Errors display
|
||||
$scope.alerts = []
|
||||
@ -98,11 +109,18 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
$scope.ok = ->
|
||||
# try to create the account
|
||||
$scope.alerts = []
|
||||
# remove 'organization' attribute
|
||||
orga = $scope.user.organization
|
||||
delete $scope.user.organization
|
||||
# register on server
|
||||
Auth.register($scope.user).then (user) ->
|
||||
# creation successful
|
||||
$uibModalInstance.close(user)
|
||||
, (error) ->
|
||||
# creation failed...
|
||||
# restore organization param
|
||||
$scope.user.organization = orga
|
||||
# display errors
|
||||
angular.forEach error.data.errors, (v, k) ->
|
||||
angular.forEach v, (err) ->
|
||||
$scope.alerts.push
|
||||
@ -197,6 +215,7 @@ Application.Controllers.controller 'ApplicationController', ["$rootScope", "$sco
|
||||
# try to retrieve any currently logged user
|
||||
Auth.login().then (user) ->
|
||||
$scope.setCurrentUser(user)
|
||||
# force users to complete their profile if they are not
|
||||
if user.need_completion
|
||||
$state.transitionTo('app.logged.profileCompletion')
|
||||
, (error) ->
|
||||
|
168
app/assets/javascripts/controllers/calendar.coffee
Normal file
168
app/assets/javascripts/controllers/calendar.coffee
Normal file
@ -0,0 +1,168 @@
|
||||
'use strict'
|
||||
|
||||
##
|
||||
# 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) ->
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
currentMachineEvent = null
|
||||
machinesPromise.forEach((m) -> m.checked = true)
|
||||
trainingsPromise.forEach((t) -> t.checked = true)
|
||||
|
||||
## check all formation/machine is select in filter
|
||||
isSelectAll = (type, scope) ->
|
||||
scope[type].length == scope[type].filter((t) -> t.checked).length
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## List of trainings
|
||||
$scope.trainings = trainingsPromise
|
||||
|
||||
## List of machines
|
||||
$scope.machines = machinesPromise
|
||||
|
||||
## add availabilities source to event sources
|
||||
$scope.eventSources = []
|
||||
|
||||
## filter availabilities if have change
|
||||
$scope.filterAvailabilities = (filter, scope) ->
|
||||
scope ||= $scope
|
||||
scope.filter = $scope.filter =
|
||||
trainings: isSelectAll('trainings', scope)
|
||||
machines: isSelectAll('machines', scope)
|
||||
evt: filter.evt
|
||||
dispo: filter.dispo
|
||||
$scope.calendarConfig.events = availabilitySourceUrl()
|
||||
|
||||
|
||||
## a variable for formation/machine/event/dispo checkbox is or not checked
|
||||
$scope.filter =
|
||||
trainings: isSelectAll('trainings', $scope)
|
||||
machines: isSelectAll('machines', $scope)
|
||||
evt: true
|
||||
dispo: true
|
||||
|
||||
## toggle to select all formation/machine
|
||||
$scope.toggleFilter = (type, filter) ->
|
||||
$scope[type].forEach((t) -> t.checked = filter[type])
|
||||
$scope.filterAvailabilities(filter, $scope)
|
||||
|
||||
$scope.openFilterAside = ->
|
||||
$aside.open
|
||||
templateUrl: 'filterAside.html'
|
||||
placement: 'right'
|
||||
size: 'md'
|
||||
backdrop: false
|
||||
resolve:
|
||||
trainings: ->
|
||||
$scope.trainings
|
||||
machines: ->
|
||||
$scope.machines
|
||||
filter: ->
|
||||
$scope.filter
|
||||
toggleFilter: ->
|
||||
$scope.toggleFilter
|
||||
filterAvailabilities: ->
|
||||
$scope.filterAvailabilities
|
||||
controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'filter', 'toggleFilter', 'filterAvailabilities', ($scope, $uibModalInstance, trainings, machines, filter, toggleFilter, filterAvailabilities) ->
|
||||
$scope.trainings = trainings
|
||||
$scope.machines = machines
|
||||
$scope.filter = filter
|
||||
|
||||
$scope.toggleFilter = (type, filter) ->
|
||||
toggleFilter(type, filter)
|
||||
|
||||
$scope.filterAvailabilities = (filter) ->
|
||||
filterAvailabilities(filter, $scope)
|
||||
|
||||
$scope.close = (e) ->
|
||||
$uibModalInstance.dismiss()
|
||||
e.stopPropagation()
|
||||
]
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
calendarEventClickCb = (event, jsEvent, view) ->
|
||||
## current calendar object
|
||||
calendar = uiCalendarConfig.calendars.calendar
|
||||
if event.available_type == 'machines'
|
||||
currentMachineEvent = event
|
||||
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
|
||||
$state.go('app.public.machines_show', {id: event.machine_id})
|
||||
|
||||
## agendaDay view: disable slotEventOverlap
|
||||
## agendaWeek view: enable slotEventOverlap
|
||||
toggleSlotEventOverlap = (view) ->
|
||||
# set defaultView, because when we change slotEventOverlap
|
||||
# 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
|
||||
$scope.calendarConfig.defaultDate = today
|
||||
else
|
||||
$scope.calendarConfig.defaultDate = view.start
|
||||
if view.type == 'agendaDay'
|
||||
$scope.calendarConfig.slotEventOverlap = false
|
||||
else
|
||||
$scope.calendarConfig.slotEventOverlap = true
|
||||
|
||||
## function is called when calendar view is rendered or changed
|
||||
viewRenderCb = (view, element) ->
|
||||
toggleSlotEventOverlap(view)
|
||||
if view.type == 'agendaDay'
|
||||
# get availabilties by 1 day for show machine slots
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents')
|
||||
|
||||
eventRenderCb = (event, element) ->
|
||||
if event.tags.length > 0
|
||||
html = ''
|
||||
for tag in event.tags
|
||||
html += "<span class='label label-success text-white'>#{tag.name}</span> "
|
||||
element.find('.fc-title').append("<br/>"+html)
|
||||
return
|
||||
|
||||
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}
|
||||
|
||||
availabilitySourceUrl = ->
|
||||
"/api/availabilities/public?#{$.param(getFilter())}"
|
||||
|
||||
initialize = ->
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig = CalendarConfig
|
||||
events: availabilitySourceUrl()
|
||||
slotEventOverlap: true
|
||||
header:
|
||||
left: 'month agendaWeek agendaDay'
|
||||
center: 'title'
|
||||
right: 'today prev,next'
|
||||
minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss'))
|
||||
maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss'))
|
||||
defaultView: if window.innerWidth <= 480 then 'agendaDay' else 'agendaWeek'
|
||||
eventClick: (event, jsEvent, view)->
|
||||
calendarEventClickCb(event, jsEvent, view)
|
||||
viewRender: (view, element) ->
|
||||
viewRenderCb(view, element)
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element)
|
||||
|
||||
|
||||
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
@ -1,13 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "EventsController", ["$scope", "$state", 'Event', ($scope, $state, Event) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# Number of events added to the page when the user clicks on 'load next events'
|
||||
EVENTS_PER_PAGE = 12
|
||||
Application.Controllers.controller "EventsController", ["$scope", "$state", 'Event', 'categoriesPromise', 'themesPromise', 'ageRangesPromise'
|
||||
, ($scope, $state, Event, categoriesPromise, themesPromise, ageRangesPromise) ->
|
||||
|
||||
|
||||
|
||||
@ -16,34 +10,41 @@ Application.Controllers.controller "EventsController", ["$scope", "$state", 'Eve
|
||||
## The events displayed on the page
|
||||
$scope.events = []
|
||||
|
||||
## By default, the pagination mode is activated to limit the page size
|
||||
$scope.paginateActive = true
|
||||
|
||||
## The currently displayed page number
|
||||
$scope.page = 1
|
||||
|
||||
## List of categories for the events
|
||||
$scope.categories = categoriesPromise
|
||||
|
||||
## List of events themes
|
||||
$scope.themes = themesPromise
|
||||
|
||||
## List of age ranges
|
||||
$scope.ageRanges = ageRangesPromise
|
||||
|
||||
## Hide or show the 'load more' button
|
||||
$scope.noMoreResults = false
|
||||
|
||||
## Active filters for the events list
|
||||
$scope.filters =
|
||||
category_id: null
|
||||
theme_id: null
|
||||
age_range_id: null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Adds EVENTS_PER_PAGE events to the bottom of the page, grouped by month
|
||||
# Adds a resultset of events to the bottom of the page, grouped by month
|
||||
##
|
||||
$scope.loadMoreEvents = ->
|
||||
Event.query {page: $scope.page}, (data) ->
|
||||
Event.query Object.assign({page: $scope.page}, $scope.filters), (data) ->
|
||||
$scope.events = $scope.events.concat data
|
||||
if data.length > 0
|
||||
$scope.paginateActive = false if ($scope.page-2)*EVENTS_PER_PAGE+data.length >= data[0].nb_total_events
|
||||
|
||||
$scope.eventsGroupByMonth = _.groupBy($scope.events, (obj) ->
|
||||
_.map ['month', 'year'], (key, value) -> obj[key]
|
||||
)
|
||||
$scope.monthOrder = _.sortBy _.keys($scope.eventsGroupByMonth), (k)->
|
||||
monthYearArray = k.split(',')
|
||||
date = new Date()
|
||||
date.setMonth(monthYearArray[0])
|
||||
date.setYear(monthYearArray[1])
|
||||
return -date.getTime()
|
||||
else
|
||||
$scope.paginateActive = false
|
||||
groupEvents($scope.events)
|
||||
$scope.page += 1
|
||||
|
||||
if (!data[0] || data[0].nb_total_events <= $scope.events.length)
|
||||
$scope.noMoreResults = true
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -55,13 +56,69 @@ Application.Controllers.controller "EventsController", ["$scope", "$state", 'Eve
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Callback to refresh the events list according to the filters set
|
||||
##
|
||||
$scope.filterEvents = ->
|
||||
# reinitialize results datasets
|
||||
$scope.page = 1
|
||||
$scope.eventsGroupByMonth = {}
|
||||
$scope.events = []
|
||||
$scope.monthOrder = []
|
||||
$scope.noMoreResults = false
|
||||
|
||||
# run a search query
|
||||
Event.query Object.assign({page: $scope.page}, $scope.filters), (data) ->
|
||||
$scope.events = data
|
||||
groupEvents(data)
|
||||
$scope.page += 1
|
||||
|
||||
if (!data[0] || data[0].nb_total_events <= $scope.events.length)
|
||||
$scope.noMoreResults = true
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Test if the provided event occurs on a single day or on many days
|
||||
# @param event {{start_date:Date, end_date:Date}} Event object as retreived from the API
|
||||
# @return {boolean} false if the event occurs on many days
|
||||
##
|
||||
$scope.onSingleDay = (event) ->
|
||||
moment(event.start_date).isSame(event.end_date, 'day')
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
$scope.loadMoreEvents()
|
||||
$scope.filterEvents()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Group the provided events by month/year and concat them with existing results
|
||||
# Then compute the ordered list of months for the complete resultset.
|
||||
# Affect the resulting events groups in $scope.eventsGroupByMonth and the ordered month keys in $scope.monthOrder.
|
||||
# @param {Array} Events retrived from the API
|
||||
##
|
||||
groupEvents = (events) ->
|
||||
if events.length > 0
|
||||
eventsGroupedByMonth = _.groupBy(events, (obj) ->
|
||||
_.map ['month', 'year'], (key, value) -> obj[key]
|
||||
)
|
||||
$scope.eventsGroupByMonth = Object.assign($scope.eventsGroupByMonth, eventsGroupedByMonth)
|
||||
|
||||
monthsOrder = _.sortBy _.keys($scope.eventsGroupByMonth), (k)->
|
||||
monthYearArray = k.split(',')
|
||||
date = new Date()
|
||||
date.setMonth(monthYearArray[0])
|
||||
date.setYear(monthYearArray[1])
|
||||
return -date.getTime()
|
||||
|
||||
$scope.monthOrder = monthsOrder
|
||||
|
||||
|
||||
|
||||
@ -75,13 +132,12 @@ Application.Controllers.controller "EventsController", ["$scope", "$state", 'Eve
|
||||
|
||||
|
||||
|
||||
Application.Controllers.controller "ShowEventController", ["$scope", "$state", "$stateParams", "Event", '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'eventPromise', 'reducedAmountAlert', 'growl', '_t'
|
||||
($scope, $state, $stateParams, Event, $uibModal, Member, Reservation, Price, CustomAsset, eventPromise, reducedAmountAlert, growl, _t) ->
|
||||
Application.Controllers.controller "ShowEventController", ["$scope", "$state", "$stateParams", "Event", '$uibModal', 'Member', 'Reservation', 'Price', 'CustomAsset', 'eventPromise', 'growl', '_t', 'Wallet', 'helpers', 'priceCategoriesPromise', 'settingsPromise',
|
||||
($scope, $state, $stateParams, Event, $uibModal, Member, Reservation, Price, CustomAsset, eventPromise, growl, _t, Wallet, helpers, priceCategoriesPromise, settingsPromise) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
$scope.reducedAmountAlert = reducedAmountAlert.setting.value
|
||||
|
||||
## reservations for the currently shown event
|
||||
$scope.reservations = []
|
||||
@ -92,18 +148,30 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
|
||||
## parameters for a new reservation
|
||||
$scope.reserve =
|
||||
nbPlaces: []
|
||||
nbReducedPlaces: []
|
||||
nbPlaces:
|
||||
normal: []
|
||||
nbReservePlaces: 0
|
||||
nbReserveReducedPlaces: 0
|
||||
tickets: {}
|
||||
toReserve: false
|
||||
amountTotal : 0
|
||||
totalSeats: 0
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
|
||||
|
||||
# get the details for the current event (event's id is recovered from the current URL)
|
||||
## Get the details for the current event (event's id is recovered from the current URL)
|
||||
$scope.event = eventPromise
|
||||
|
||||
## List of price categories for the events
|
||||
$scope.priceCategories = priceCategoriesPromise
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -117,21 +185,27 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
|
||||
|
||||
##
|
||||
# Callback to call when the number of places change in the current booking
|
||||
# Callback to call when the number of tickets to book changes in the current booking
|
||||
##
|
||||
$scope.changeNbPlaces = ->
|
||||
reste = $scope.event.nb_free_places - $scope.reserve.nbReservePlaces
|
||||
$scope.reserve.nbReducedPlaces = [0..reste]
|
||||
$scope.computeEventAmount()
|
||||
# compute the total remaing places
|
||||
remain = $scope.event.nb_free_places - $scope.reserve.nbReservePlaces
|
||||
for ticket of $scope.reserve.tickets
|
||||
remain -= $scope.reserve.tickets[ticket]
|
||||
# we store the total number of seats booked, this is used to know if the 'pay' button must be shown
|
||||
$scope.reserve.totalSeats = $scope.event.nb_free_places - remain
|
||||
|
||||
# update the availables seats for full price tickets
|
||||
fullPriceRemains = $scope.reserve.nbReservePlaces + remain
|
||||
$scope.reserve.nbPlaces.normal = [0..fullPriceRemains]
|
||||
|
||||
# update the available seats for other prices tickets
|
||||
for key of $scope.reserve.nbPlaces
|
||||
if key != 'normal'
|
||||
priceRemain = $scope.reserve.tickets[key] + remain
|
||||
$scope.reserve.nbPlaces[key] = [0..priceRemain]
|
||||
|
||||
##
|
||||
# Callback to call when the number of discounted places change in the current booking
|
||||
##
|
||||
$scope.changeNbReducedPlaces = ->
|
||||
reste = $scope.event.nb_free_places - $scope.reserve.nbReserveReducedPlaces
|
||||
$scope.reserve.nbPlaces = [0..reste]
|
||||
# recompute the total price
|
||||
$scope.computeEventAmount()
|
||||
|
||||
|
||||
@ -185,10 +259,12 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
reservation = mkReservation($scope.ctrl.member, $scope.reserve, $scope.event)
|
||||
|
||||
if $scope.currentUser.role isnt 'admin' and $scope.reserve.amountTotal > 0
|
||||
Wallet.getWalletByUser {user_id: $scope.ctrl.member.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.reserve.amountTotal, wallet.amount)
|
||||
if $scope.currentUser.role isnt 'admin' and amountToPay > 0
|
||||
payByStripe(reservation)
|
||||
else
|
||||
if $scope.currentUser.role is 'admin' or $scope.reserve.amountTotal is 0
|
||||
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
|
||||
@ -206,20 +282,31 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
reservable_type: 'Event'
|
||||
slots_attributes: []
|
||||
nb_reserve_places: $scope.reserve.nbReservePlaces
|
||||
nb_reserve_reduced_places: $scope.reserve.nbReserveReducedPlaces
|
||||
tickets_attributes: []
|
||||
# a single slot is used for events
|
||||
reservation.slots_attributes.push
|
||||
start_at: $scope.event.start_date
|
||||
end_at: $scope.event.end_date
|
||||
availability_id: $scope.event.availability.id
|
||||
# iterate over reservations per prices
|
||||
for price_id, seats of $scope.reserve.tickets
|
||||
reservation.tickets_attributes.push
|
||||
event_price_category_id: price_id
|
||||
booked: seats
|
||||
# set the attempting marker
|
||||
$scope.attempting = true
|
||||
# save the reservation to the API
|
||||
Reservation.save reservation: reservation, (reservation) ->
|
||||
# reservation successfull
|
||||
afterPayment(reservation)
|
||||
$scope.attempting = false
|
||||
, (response)->
|
||||
# reservation failed
|
||||
$scope.alerts = []
|
||||
$scope.alerts.push
|
||||
msg: response.data.card[0]
|
||||
type: 'danger'
|
||||
# unset the attempting marker
|
||||
$scope.attempting = false
|
||||
|
||||
|
||||
@ -227,7 +314,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
##
|
||||
# Callback to alter an already booked reservation date. A modal window will be opened to allow the user to choose
|
||||
# a new date for his reservation (if any available)
|
||||
# @param reservation {{id:number, reservable_id:number, nb_reserve_places:number, nb_reserve_reduced_places:number}}
|
||||
# @param reservation {{id:number, reservable_id:number, nb_reserve_places:number}}
|
||||
# @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
##
|
||||
$scope.modifyReservation = (reservation, e)->
|
||||
@ -247,7 +334,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
|
||||
# set the reservable_id to the first available event
|
||||
for e in event.recurrence_events
|
||||
if e.nb_free_places > (reservation.nb_reserve_places + reservation.nb_reserve_reduced_places)
|
||||
if e.nb_free_places > reservation.total_booked_seats
|
||||
$scope.reservation.reservable_id = e.id
|
||||
break
|
||||
|
||||
@ -277,23 +364,29 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
# remove the reservation from the user's reservations list for this event (occurrence)
|
||||
$scope.reservations.splice(index, 1)
|
||||
$scope.event.nb_free_places = $scope.event.nb_free_places + reservation.nb_reserve_places + reservation.nb_reserve_reduced_places
|
||||
# add the number of places transfered (to the new date) to the total of free places for this event
|
||||
$scope.event.nb_free_places = $scope.event.nb_free_places + reservation.total_booked_seats
|
||||
# remove the number of places transfered from the total of free places of the receiving occurrance
|
||||
angular.forEach $scope.event.recurrence_events, (e)->
|
||||
if e.id is parseInt(reservation.reservable_id, 10)
|
||||
e.nb_free_places = e.nb_free_places - reservation.nb_reserve_places - reservation.nb_reserve_reduced_places
|
||||
if e.id is parseInt(reservation.reservable.id, 10)
|
||||
e.nb_free_places = e.nb_free_places - reservation.total_booked_seats
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Checks if the provided reservation is able to be modified
|
||||
# @param reservation {{nb_reserve_places:number, nb_reserve_reduced_places:number}}
|
||||
# Checks if the provided reservation is able to be moved (date change)
|
||||
# @param reservation {{total_booked_seats:number}}
|
||||
##
|
||||
$scope.reservationCanModify = (reservation)->
|
||||
slotStart = moment(reservation.slots[0].start_at)
|
||||
now = moment()
|
||||
|
||||
isAble = false
|
||||
angular.forEach $scope.event.recurrence_events, (e)->
|
||||
isAble = true if e.nb_free_places > (reservation.nb_reserve_places + reservation.nb_reserve_reduced_places)
|
||||
isAble
|
||||
isAble = true if e.nb_free_places >= reservation.total_booked_seats
|
||||
return (isAble and $scope.enableBookingMove and slotStart.diff(now, "hours") >= $scope.moveBookingDelay)
|
||||
|
||||
|
||||
|
||||
@ -305,36 +398,62 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
# first we check that a user was selected
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, $scope.reserve, $scope.event)
|
||||
Price.compute {reservation: r}, (res) ->
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.reserve.amountTotal = res.price
|
||||
else
|
||||
$scope.reserve.amountTotal = null
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Return the URL allowing to share the current project on the Facebook social network
|
||||
##
|
||||
$scope.shareOnFacebook = ->
|
||||
'https://www.facebook.com/share.php?u='+$state.href('app.public.events_show', {id: $scope.event.id}, {absolute: true}).replace('#', '%23')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Return the URL allowing to share the current project on the Twitter social network
|
||||
##
|
||||
$scope.shareOnTwitter = ->
|
||||
'https://twitter.com/intent/tweet?url='+encodeURIComponent($state.href('app.public.events_show', {id: $scope.event.id}, {absolute: true}))+'&text='+encodeURIComponent($scope.event.title)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Return the textual description of the conditions applyable to the given price's category
|
||||
# @param category_id {number} ID of the price's category
|
||||
##
|
||||
$scope.getPriceCategoryConditions = (category_id) ->
|
||||
for cat in $scope.priceCategories
|
||||
if cat.id == category_id
|
||||
return cat.conditions
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
##
|
||||
# Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
##
|
||||
initialize = ->
|
||||
# gather the current user or the list of users if the current user is an admin
|
||||
# set the controlled user as the current user if the current user is not an admin
|
||||
if $scope.currentUser
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
|
||||
# check that the event's reduced rate is initialized
|
||||
if !$scope.event.reduced_amount
|
||||
$scope.event.reduced_amount = 0
|
||||
|
||||
# initialize the "reserve" object with the event's data
|
||||
$scope.reserve.nbPlaces = [0..$scope.event.nb_free_places]
|
||||
$scope.reserve.nbReducedPlaces = [0..$scope.event.nb_free_places]
|
||||
resetEventReserve()
|
||||
|
||||
# if non-admin, get the current user's reservations into $scope.reservations
|
||||
if $scope.currentUser
|
||||
getReservations($scope.event.id, 'Event', $scope.currentUser.id)
|
||||
|
||||
# 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.computeEventAmount()
|
||||
|
||||
|
||||
##
|
||||
@ -353,8 +472,8 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
# Create an hash map implementing the Reservation specs
|
||||
# @param member {Object} User as retreived from the API: current user / selected user if current is admin
|
||||
# @param reserve {Object} Reservation parameters (places...)
|
||||
# @param event {Object} Current event (Atelier/Stage)
|
||||
# @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, nb_reserve_places:Number, nb_reserve_reduced_places:Number}}
|
||||
# @param event {Object} Current event
|
||||
# @return {{user_id:number, reservable_id:number, reservable_type:string, slots_attributes:Array<Object>, nb_reserve_places:number}}
|
||||
##
|
||||
mkReservation = (member, reserve, event) ->
|
||||
reservation =
|
||||
@ -363,7 +482,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
reservable_type: 'Event'
|
||||
slots_attributes: []
|
||||
nb_reserve_places: reserve.nbReservePlaces
|
||||
nb_reserve_reduced_places: reserve.nbReserveReducedPlaces
|
||||
tickets_attributes: []
|
||||
|
||||
reservation.slots_attributes.push
|
||||
start_at: event.start_date
|
||||
@ -371,22 +490,49 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
availability_id: event.availability.id
|
||||
offered: event.offered || false
|
||||
|
||||
for evt_px_cat in event.prices
|
||||
booked = reserve.tickets[evt_px_cat.id]
|
||||
if booked > 0
|
||||
reservation.tickets_attributes.push
|
||||
event_price_category_id: evt_px_cat.id
|
||||
booked: booked
|
||||
|
||||
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
|
||||
|
||||
|
||||
##
|
||||
# Set the current reservation to the default values. This implies to reservation form to be hidden.
|
||||
##
|
||||
resetEventReserve = ->
|
||||
if $scope.event
|
||||
$scope.reserve =
|
||||
nbPlaces: [0..$scope.event.nb_free_places]
|
||||
nbReducedPlaces: [0..$scope.event.nb_free_places]
|
||||
nbPlaces:
|
||||
normal: [0..$scope.event.nb_free_places]
|
||||
nbReservePlaces: 0
|
||||
nbReserveReducedPlaces: 0
|
||||
tickets: {}
|
||||
toReserve: false
|
||||
amountTotal : 0
|
||||
totalSeats: 0
|
||||
|
||||
for evt_px_cat in $scope.event.prices
|
||||
$scope.reserve.nbPlaces[evt_px_cat.id] = [0..$scope.event.nb_free_places]
|
||||
$scope.reserve.tickets[evt_px_cat.id] = 0
|
||||
|
||||
$scope.event.offered = false
|
||||
|
||||
|
||||
@ -403,16 +549,24 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute({reservation: reservation}).$promise
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
objectToPay: ->
|
||||
eventToReserve: $scope.event
|
||||
reserve: $scope.reserve
|
||||
member: $scope.ctrl.member
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'growl', ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, growl) ->
|
||||
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
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = price.price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
@ -420,6 +574,10 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
# Reservation
|
||||
$scope.reservation = reservation
|
||||
|
||||
$scope.currencySymbol = $locale.NUMBER_FORMATS.CURRENCY_SYM
|
||||
|
||||
$scope.numberFilter = $filter('number')
|
||||
|
||||
# Callback for the stripe payment authorization
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
@ -427,7 +585,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save reservation: $scope.reservation, (reservation) ->
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
@ -453,24 +611,42 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute({reservation: reservation}).$promise
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation) ->
|
||||
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', '$locale', 'helpers', '$filter', 'coupon',
|
||||
($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, $locale, helpers, $filter, coupon) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Price
|
||||
$scope.amount = price.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_on_site)')
|
||||
$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 validate the payment
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save reservation: $scope.reservation, (reservation) ->
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
@ -496,7 +672,7 @@ Application.Controllers.controller "ShowEventController", ["$scope", "$state", "
|
||||
# @param resveration {Object} booked reservation
|
||||
##
|
||||
afterPayment = (reservation)->
|
||||
$scope.event.nb_free_places = $scope.event.nb_free_places - reservation.nb_reserve_places - reservation.nb_reserve_reduced_places
|
||||
$scope.event.nb_free_places = $scope.event.nb_free_places - reservation.total_booked_seats
|
||||
resetEventReserve()
|
||||
$scope.reserveSuccess = true
|
||||
$scope.reservations.push reservation
|
||||
|
@ -126,7 +126,7 @@ _reserveMachine = (machine, e) ->
|
||||
|
||||
# modal is close with validation
|
||||
$scope.ok = ->
|
||||
$state.go('app.logged.trainings_reserve')
|
||||
$state.go('app.logged.trainings_reserve', {id: $scope.machine.trainings[0].id})
|
||||
$uibModalInstance.close(machine)
|
||||
|
||||
# modal is closed with escaping
|
||||
@ -231,8 +231,8 @@ 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
|
||||
$scope.machine = machinePromise
|
||||
@ -245,6 +245,12 @@ Application.Controllers.controller "ShowMachineController", ['$scope', '$state',
|
||||
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_machine')
|
||||
, -> # deletion confirmed
|
||||
# delete the machine then redirect to the machines listing
|
||||
machine.$delete ->
|
||||
$state.go('app.public.machines_list')
|
||||
@ -268,38 +274,26 @@ 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', 'settingsPromise',
|
||||
($scope, $state, $stateParams, $uibModal, _t, moment, Machine, Auth, dialogs, $timeout, Price, Member, Availability, Slot, Setting, CustomAsset, plansPromise, groupsPromise, growl, settingsPromise) ->
|
||||
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) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# The calendar is divided in slots of 60 minutes
|
||||
BASE_SLOT = '01:00:00'
|
||||
|
||||
# The calendar will be initialized positioned under 9:00 AM
|
||||
DEFAULT_CALENDAR_POSITION = '09:00:00'
|
||||
|
||||
# The user is unable to modify his already booked reservation 1 day before it occurs
|
||||
PREVENT_BOOKING_MODIFICATION_DELAY = 1
|
||||
|
||||
# Slot already booked by the current user
|
||||
FREE_SLOT_BORDER_COLOR = '#e4cd78'
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::MACHINE_COLOR %>'
|
||||
|
||||
# Slot already booked by another user
|
||||
UNAVAILABLE_SLOT_BORDER_COLOR = '#1d98ec'
|
||||
UNAVAILABLE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::MACHINE_IS_RESERVED_BY_USER %>'
|
||||
|
||||
# Slot free to be booked
|
||||
BOOKED_SLOT_BORDER_COLOR = '#b2e774'
|
||||
BOOKED_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::IS_RESERVED_BY_CURRENT_USER %>'
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar()
|
||||
$scope.calendar = null
|
||||
|
||||
## bind the machine availabilities with full-Calendar events
|
||||
$scope.eventSources = []
|
||||
|
||||
@ -321,12 +315,13 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
## total amount of the bill to pay
|
||||
$scope.amountTotal = 0
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
|
||||
## is the user allowed to change the date of his booking
|
||||
$scope.enableBookingMove = true
|
||||
|
||||
## how many hours before the reservation, the user is still allowed to change his booking
|
||||
$scope.moveBookingDelay = 24
|
||||
|
||||
## list of plans, classified by group
|
||||
$scope.plansClassifiedByGroup = []
|
||||
for group in groupsPromise
|
||||
@ -340,34 +335,12 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
member: {}
|
||||
|
||||
## current machine to reserve
|
||||
$scope.machine = {}
|
||||
$scope.machine = machinePromise
|
||||
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig =
|
||||
timezone: Fablab.timezone
|
||||
lang: Fablab.fullcalendar_locale
|
||||
header:
|
||||
left: 'month agendaWeek'
|
||||
center: 'title'
|
||||
right: 'today prev,next'
|
||||
firstDay: 1 # Week start on monday (France)
|
||||
scrollTime: DEFAULT_CALENDAR_POSITION
|
||||
slotDuration: BASE_SLOT
|
||||
allDayDefault: false
|
||||
minTime: '00:00:00'
|
||||
maxTime: '24:00:00'
|
||||
height: 'auto'
|
||||
buttonIcons:
|
||||
prev: 'left-single-arrow'
|
||||
next: 'right-single-arrow'
|
||||
timeFormat:
|
||||
agenda:'H:mm'
|
||||
month: 'H(:mm)'
|
||||
axisFormat: 'H:mm'
|
||||
|
||||
allDaySlot: false
|
||||
defaultView: 'agendaWeek'
|
||||
editable: false
|
||||
$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) ->
|
||||
@ -391,12 +364,6 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
## Global config: delay in hours before a booking while the cancellation is forbidden
|
||||
$scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay)
|
||||
|
||||
## Global config: calendar window in the morning
|
||||
$scope.calendarConfig.minTime = moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss'))
|
||||
|
||||
## Global config: calendar window in the evening
|
||||
$scope.calendarConfig.maxTime = moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss'))
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -412,7 +379,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$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
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -425,7 +392,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = ''
|
||||
$scope.slotToPlace = null
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -458,7 +425,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.slotToModify.can_modify = false
|
||||
$scope.slotToModify = null
|
||||
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, (err) -> # failure
|
||||
growl.error(_t('unable_to_change_the_reservation'))
|
||||
console.error(err)
|
||||
@ -476,7 +443,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.slotToModify.backgroundColor = 'white'
|
||||
$scope.slotToModify = null
|
||||
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -532,8 +499,8 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.plansAreShown = false
|
||||
updateCartPrice()
|
||||
$timeout ->
|
||||
$scope.calendar.fullCalendar 'refetchEvents'
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -569,10 +536,12 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
reservation = mkReservation($scope.ctrl.member, $scope.eventsReserved, $scope.selectedPlan)
|
||||
|
||||
if $scope.currentUser.role isnt 'admin' and $scope.amountTotal > 0
|
||||
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 $scope.amountTotal is 0
|
||||
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
|
||||
@ -637,11 +606,10 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
|
||||
$scope.machine = Machine.get {id: $stateParams.id}
|
||||
, ->
|
||||
return
|
||||
, ->
|
||||
$state.go('app.public.machines_list')
|
||||
# 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()
|
||||
|
||||
|
||||
|
||||
@ -670,13 +638,28 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
|
||||
|
||||
|
||||
##
|
||||
# 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 {reservation: r}, (res) ->
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
setSlotsDetails(res.details)
|
||||
else
|
||||
@ -732,7 +715,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.slotToModify = event
|
||||
event.backgroundColor = '#eee'
|
||||
event.title = _t('i_change')
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
@ -750,7 +733,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.canceledSlot.is_reserved = false
|
||||
$scope.canceledSlot.can_modify = false
|
||||
$scope.canceledSlot = null
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cancellation_failed')
|
||||
, ->
|
||||
@ -758,7 +741,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
$scope.selectedPlan = null
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
updateCartPrice()
|
||||
|
||||
@ -776,6 +759,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -791,12 +775,20 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute({reservation: reservation}).$promise
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation) ->
|
||||
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 = price.price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
@ -804,6 +796,12 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
# 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
|
||||
##
|
||||
@ -813,7 +811,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save reservation: $scope.reservation, (reservation) ->
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
@ -839,18 +837,38 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute({reservation: reservation}).$promise
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation) ->
|
||||
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) ->
|
||||
|
||||
# Price
|
||||
$scope.amount = price.price
|
||||
# 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_on_site)')
|
||||
$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')
|
||||
|
||||
@ -859,7 +877,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save reservation: $scope.reservation, (reservation) ->
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
@ -930,8 +948,8 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", "$stat
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.plansAreShown = false
|
||||
|
||||
$scope.calendar.fullCalendar 'refetchEvents'
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
|
@ -16,18 +16,23 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
{
|
||||
state: 'app.public.machines_list'
|
||||
linkText: 'reserve_a_machine'
|
||||
linkIcon: 'calendar'
|
||||
linkIcon: 'cogs'
|
||||
}
|
||||
{
|
||||
state: 'app.logged.trainings_reserve'
|
||||
state: 'app.public.trainings_list'
|
||||
linkText: 'trainings_registrations'
|
||||
linkIcon: 'graduation-cap'
|
||||
}
|
||||
{
|
||||
state: 'app.public.events_list'
|
||||
linkText: 'courses_and_workshops_registrations'
|
||||
linkText: 'events_registrations'
|
||||
linkIcon: 'tags'
|
||||
}
|
||||
{
|
||||
state: 'app.public.calendar'
|
||||
linkText: 'public_calendar'
|
||||
linkIcon: 'calendar'
|
||||
}
|
||||
{
|
||||
state: 'app.public.projects_list'
|
||||
linkText: 'projects_gallery'
|
||||
@ -73,7 +78,7 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
}
|
||||
{
|
||||
state: 'app.admin.events'
|
||||
linkText: 'courses_and_workshops_monitoring'
|
||||
linkText: 'manage_the_events'
|
||||
linkIcon: 'tags'
|
||||
}
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScope", "$state", '$uibModal', 'Auth', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t'
|
||||
, ($scope, $rootScope, $state, $uibModal, Auth, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t) ->
|
||||
Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScope", "$state", '$uibModal', 'Auth', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers'
|
||||
, ($scope, $rootScope, $state, $uibModal, Auth, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers) ->
|
||||
|
||||
|
||||
|
||||
@ -30,11 +30,20 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
member_id: null
|
||||
|
||||
## already subscribed plan of the current user
|
||||
$scope.paidPlan = null
|
||||
$scope.paid =
|
||||
plan: null
|
||||
|
||||
## plan to subscribe (shopping cart)
|
||||
$scope.selectedPlan = null
|
||||
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
|
||||
## Storage for the total price (plan price + coupon, if any)
|
||||
$scope.cart =
|
||||
total: null
|
||||
|
||||
## text that appears in the bottom-right box of the page (subscriptions rules details)
|
||||
$scope.subscriptionExplicationsAlert = subscriptionExplicationsPromise.setting.value
|
||||
|
||||
@ -44,7 +53,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
##
|
||||
$scope.updateMember = ->
|
||||
$scope.selectedPlan = null
|
||||
$scope.paidPlan = null
|
||||
$scope.paid.plan = null
|
||||
$scope.group.change = false
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
@ -61,6 +70,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
if $scope.isAuthenticated()
|
||||
if $scope.selectedPlan != plan
|
||||
$scope.selectedPlan = plan
|
||||
updateCartPrice()
|
||||
else
|
||||
$scope.selectedPlan = null
|
||||
else
|
||||
@ -72,9 +82,12 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
# Callback to trigger the payment process of the subscription
|
||||
##
|
||||
$scope.openSubscribePlanModal = ->
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
Wallet.getWalletByUser {user_id: $scope.ctrl.member.id}, (wallet) ->
|
||||
amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount)
|
||||
if $scope.currentUser.role isnt 'admin' and amountToPay > 0
|
||||
payByStripe()
|
||||
else
|
||||
if $scope.currentUser.role is 'admin' or amountToPay is 0
|
||||
payOnSite()
|
||||
|
||||
|
||||
@ -144,12 +157,33 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
if $scope.currentUser
|
||||
if $scope.currentUser.role isnt 'admin'
|
||||
$scope.ctrl.member = $scope.currentUser
|
||||
$scope.paidPlan = $scope.currentUser.subscribed_plan
|
||||
$scope.paid.plan = $scope.currentUser.subscribed_plan
|
||||
$scope.group.id = $scope.currentUser.group_id
|
||||
|
||||
$scope.$on 'devise:new-session', (event, user)->
|
||||
$scope.ctrl.member = user
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Compute the total amount for the current reservation according to the previously set parameters
|
||||
# and assign the result in $scope.reserve.amountTotal
|
||||
##
|
||||
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
|
||||
discount = $scope.cart.total * $scope.coupon.applied.percent_off / 100
|
||||
$scope.cart.total -= discount
|
||||
else
|
||||
$scope.reserve.amountTotal = null
|
||||
|
||||
|
||||
##
|
||||
@ -162,18 +196,43 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
resolve:
|
||||
selectedPlan: -> $scope.selectedPlan
|
||||
member: -> $scope.ctrl.member
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'Subscription', 'CustomAsset', ($scope, $uibModalInstance, $state, selectedPlan, member, Subscription, CustomAsset) ->
|
||||
$scope.amount = selectedPlan.amount
|
||||
price: -> $scope.cart.total
|
||||
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
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# Final price to pay by the user
|
||||
$scope.amount = helpers.getAmountToPay(price, wallet.amount)
|
||||
|
||||
# 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')
|
||||
|
||||
# retrieve the CGV
|
||||
CustomAsset.get {name: 'cgv-file'}, (cgv) ->
|
||||
$scope.cgv = cgv.custom_asset
|
||||
|
||||
##
|
||||
# Callback for click on the 'proceed' button.
|
||||
# Handle the stripe's card tokenization process response and save the subscription to the API with the
|
||||
# card token just created.
|
||||
##
|
||||
$scope.payment = (status, response) ->
|
||||
if response.error
|
||||
growl.error(response.error.message)
|
||||
else
|
||||
$scope.attempting = true
|
||||
Subscription.save
|
||||
coupon_code: (coupon.code if coupon)
|
||||
subscription:
|
||||
plan_id: selectedPlan.id
|
||||
user_id: member.id
|
||||
@ -188,7 +247,7 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
.result['finally'](null).then (subscription)->
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
$scope.paidPlan = angular.copy($scope.selectedPlan)
|
||||
$scope.paid.plan = angular.copy($scope.selectedPlan)
|
||||
$scope.selectedPlan = null
|
||||
|
||||
|
||||
@ -203,12 +262,50 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
resolve:
|
||||
selectedPlan: -> $scope.selectedPlan
|
||||
member: -> $scope.ctrl.member
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'selectedPlan', 'member', 'Subscription', ($scope, $uibModalInstance, $state, selectedPlan, member, Subscription) ->
|
||||
price: -> $scope.cart.total
|
||||
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) ->
|
||||
# user wallet amount
|
||||
$scope.walletAmount = wallet.amount
|
||||
|
||||
# subcription price, coupon subtracted if any
|
||||
$scope.price = price
|
||||
|
||||
# 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')
|
||||
|
||||
# The plan that the user is about to subscribe
|
||||
$scope.plan = selectedPlan
|
||||
|
||||
# The member who is subscribing a plan
|
||||
$scope.member = member
|
||||
|
||||
# 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 for the 'proceed' button.
|
||||
# Save the subscription to the API
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Subscription.save
|
||||
coupon_code: (coupon.code if coupon)
|
||||
subscription:
|
||||
plan_id: selectedPlan.id
|
||||
user_id: member.id
|
||||
@ -219,16 +316,18 @@ Application.Controllers.controller "PlansIndexController", ["$scope", "$rootScop
|
||||
$scope.alerts.push({msg: _t('an_error_occured_during_the_payment_process_please_try_again_later'), type: 'danger' })
|
||||
$scope.attempting = false
|
||||
|
||||
##
|
||||
# Callback for the 'cancel' button.
|
||||
# Close the modal box.
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$uibModalInstance.dismiss('cancel')
|
||||
]
|
||||
.result['finally'](null).then (reservation)->
|
||||
$scope.ctrl.member.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
Auth._currentUser.subscribed_plan = angular.copy($scope.selectedPlan)
|
||||
index = $scope.members.indexOf($scope.ctrl.member)
|
||||
$scope.members.splice(index, 1)
|
||||
$scope.ctrl.member = null
|
||||
$scope.paidPlan = angular.copy($scope.selectedPlan)
|
||||
$scope.paid.plan = angular.copy($scope.selectedPlan)
|
||||
$scope.selectedPlan = null
|
||||
|
||||
|
||||
|
@ -7,16 +7,19 @@
|
||||
# in the various projects' admin controllers.
|
||||
#
|
||||
# Provides :
|
||||
# - $scope.totalSteps
|
||||
# - $scope.machines = [{Machine}]
|
||||
# - $scope.components = [{Component}]
|
||||
# - $scope.themes = [{Theme}]
|
||||
# - $scope.licences = [{Licence}]
|
||||
# - $scope.allowedExtensions = [{String}]
|
||||
# - $scope.submited(content)
|
||||
# - $scope.cancel()
|
||||
# - $scope.addFile()
|
||||
# - $scope.deleteFile(file)
|
||||
# - $scope.addStep()
|
||||
# - $scope.deleteStep(step)
|
||||
# - $scope.changeStepIndex(step, newIdx)
|
||||
#
|
||||
# Requires :
|
||||
# - $scope.project.project_caos_attributes = []
|
||||
@ -24,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)->
|
||||
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)->
|
||||
@ -50,6 +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
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -74,14 +83,6 @@ class ProjectsController
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Changes the user's view to the projects list page
|
||||
##
|
||||
$scope.cancel = ->
|
||||
$state.go('app.public.projects_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.
|
||||
@ -122,23 +123,56 @@ class ProjectsController
|
||||
# This will create a single new empty entry into the project's steps list.
|
||||
##
|
||||
$scope.addStep = ->
|
||||
$scope.project.project_steps_attributes.push {}
|
||||
$scope.totalSteps += 1
|
||||
$scope.project.project_steps_attributes.push { step_nb: $scope.totalSteps }
|
||||
|
||||
|
||||
|
||||
|
||||
##
|
||||
# This will remove the given stip from the project's steps list. If the step was previously saved
|
||||
# This will remove the given step from the project's steps list. If the step was previously saved
|
||||
# on the server, it will be marked for deletion for the next saving. Otherwise, it will be simply truncated from
|
||||
# the steps array.
|
||||
# @param file {Object} the file to delete
|
||||
##
|
||||
$scope.deleteStep = (step) ->
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_step')
|
||||
, -> # deletion confirmed
|
||||
index = $scope.project.project_steps_attributes.indexOf(step)
|
||||
if step.id?
|
||||
step._destroy = true
|
||||
else
|
||||
$scope.project.project_steps_attributes.splice(index, 1)
|
||||
|
||||
# update the new total number of steps
|
||||
$scope.totalSteps -= 1
|
||||
# reindex the remaning steps
|
||||
for s in $scope.project.project_steps_attributes
|
||||
if s.step_nb > step.step_nb
|
||||
s.step_nb -= 1
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Change the step_nb property of the given step to the new value provided. The step that was previously at this
|
||||
# index will be assigned to the old position of the provided step.
|
||||
# @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
# @param step {Object} the project's step to reindex
|
||||
# @param newIdx {number} the new index to assign to the step
|
||||
##
|
||||
$scope.changeStepIndex = (event, step, newIdx) ->
|
||||
event.preventDefault() if event
|
||||
for s in $scope.project.project_steps_attributes
|
||||
if s.step_nb == newIdx
|
||||
s.step_nb = step.step_nb
|
||||
step.step_nb = newIdx
|
||||
break
|
||||
false
|
||||
|
||||
|
||||
$scope.autoCompleteName = (nameLookup) ->
|
||||
unless nameLookup
|
||||
@ -286,8 +320,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'
|
||||
, ($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, Diacritics) ->
|
||||
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
|
||||
@ -304,7 +338,7 @@ Application.Controllers.controller "NewProjectController", ["$scope", "$state",
|
||||
$scope.matchingMembers = []
|
||||
|
||||
## Using the ProjectsController
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics)
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, allowedExtensions, _t)
|
||||
]
|
||||
|
||||
|
||||
@ -312,8 +346,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'
|
||||
, ($scope, $state, $stateParams, Project, Machine, Member, Component, Theme, Licence, $document, CSRF, projectPromise, Diacritics) ->
|
||||
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
|
||||
@ -330,7 +364,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)
|
||||
new ProjectsController($scope, $state, Project, Machine, Member, Component, Theme, Licence, $document, Diacritics, dialogs, allowedExtensions, _t)
|
||||
]
|
||||
|
||||
|
||||
@ -394,6 +428,8 @@ Application.Controllers.controller "ShowProjectController", ["$scope", "$state",
|
||||
else
|
||||
console.error _t('unauthorized_operation')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Open a modal box containg a form that allow the end-user to signal an abusive content
|
||||
# @param e {Object} jQuery event
|
||||
@ -429,4 +465,19 @@ Application.Controllers.controller "ShowProjectController", ["$scope", "$state",
|
||||
growl.error(_t('an_error_occured_while_sending_your_report'))
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Return the URL allowing to share the current project on the Facebook social network
|
||||
##
|
||||
$scope.shareOnFacebook = ->
|
||||
'https://www.facebook.com/share.php?u='+$state.href('app.public.projects_show', {id: $scope.project.slug}, {absolute: true}).replace('#', '%23')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Return the URL allowing to share the current project on the Twitter social network
|
||||
##
|
||||
$scope.shareOnTwitter = ->
|
||||
'https://twitter.com/intent/tweet?url='+encodeURIComponent($state.href('app.public.projects_show', {id: $scope.project.slug}, {absolute: true}))+'&text='+encodeURIComponent($scope.project.name)
|
||||
]
|
||||
|
@ -1,40 +1,99 @@
|
||||
'use strict'
|
||||
|
||||
##
|
||||
# Public listing of the trainings
|
||||
##
|
||||
Application.Controllers.controller "TrainingsController", ['$scope', '$state', 'trainingsPromise', ($scope, $state, trainingsPromise) ->
|
||||
|
||||
## List of trainings
|
||||
$scope.trainings = trainingsPromise
|
||||
|
||||
##
|
||||
# Callback for the 'reserve' button
|
||||
##
|
||||
$scope.reserveTraining = (training, event) ->
|
||||
$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.slug})
|
||||
]
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Public view of a specific training
|
||||
##
|
||||
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)
|
||||
##
|
||||
$scope.cancel = (event) ->
|
||||
$state.go('app.public.trainings_list')
|
||||
]
|
||||
|
||||
|
||||
##
|
||||
# Controller used in the training reservation agenda page.
|
||||
# This controller is very similar to the machine reservation controller with one major difference: here, ONLY ONE
|
||||
# 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', "$uibModal", 'Auth', 'dialogs', '$timeout', 'Price', 'Availability', 'Slot', 'Member', 'Setting', 'CustomAsset', '$compile', 'availabilityTrainingsPromise', 'plansPromise', 'groupsPromise', 'growl', 'settingsPromise', '_t',
|
||||
($scope, $state, $stateParams, $uibModal, Auth, dialogs, $timeout, Price, Availability, Slot, Member, Setting, CustomAsset, $compile, availabilityTrainingsPromise, plansPromise, groupsPromise, growl, settingsPromise, _t) ->
|
||||
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) ->
|
||||
|
||||
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# The calendar is divided in slots of 60 minutes
|
||||
BASE_SLOT = '01:00:00'
|
||||
|
||||
# The calendar will be initialized positioned under 9:00 AM
|
||||
DEFAULT_CALENDAR_POSITION = '09:00:00'
|
||||
|
||||
# The user is unable to modify his already booked reservation 1 day before it occurs
|
||||
PREVENT_BOOKING_MODIFICATION_DELAY = 1
|
||||
|
||||
# Color of the selected event backgound
|
||||
SELECTED_EVENT_BG_COLOR = '#ffdd00'
|
||||
|
||||
# Slot already booked by the current user
|
||||
FREE_SLOT_BORDER_COLOR = '#bd7ae9'
|
||||
FREE_SLOT_BORDER_COLOR = '<%= AvailabilityHelper::TRAINING_COLOR %>'
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar()
|
||||
$scope.calendar = null
|
||||
|
||||
## bind the trainings availabilities with full-Calendar events
|
||||
$scope.eventSources = [ { events: availabilityTrainingsPromise, textColor: 'black' } ]
|
||||
|
||||
@ -71,36 +130,21 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
## Once a training reservation was modified, will contains {newReservedSlot:{}, oldReservedSlot:{}}
|
||||
$scope.modifiedSlots = null
|
||||
|
||||
## fullCalendar (v2) configuration
|
||||
$scope.calendarConfig =
|
||||
timezone: Fablab.timezone
|
||||
lang: Fablab.fullcalendar_locale
|
||||
header:
|
||||
left: 'month agendaWeek'
|
||||
center: 'title'
|
||||
right: 'today prev,next'
|
||||
firstDay: 1 # Week start on monday (France)
|
||||
scrollTime: DEFAULT_CALENDAR_POSITION
|
||||
slotDuration: BASE_SLOT
|
||||
allDayDefault: false
|
||||
minTime: '00:00:00'
|
||||
maxTime: '24:00:00'
|
||||
height: 'auto'
|
||||
buttonIcons:
|
||||
prev: 'left-single-arrow'
|
||||
next: 'right-single-arrow'
|
||||
timeFormat:
|
||||
agenda:'H:mm'
|
||||
month: 'H(:mm)'
|
||||
axisFormat: 'H:mm'
|
||||
## Selected training unless 'all' trainings are displayed
|
||||
$scope.training = trainingPromise
|
||||
|
||||
allDaySlot: false
|
||||
defaultView: 'agendaWeek'
|
||||
editable: false
|
||||
## Discount coupon to apply to the basket, if any
|
||||
$scope.coupon =
|
||||
applied: null
|
||||
|
||||
## 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)
|
||||
eventAfterAllRender: (view)->
|
||||
$scope.events = $scope.calendar.fullCalendar 'clientEvents'
|
||||
$scope.events = uiCalendarConfig.calendars.calendar.fullCalendar 'clientEvents'
|
||||
eventRender: (event, element, view) ->
|
||||
eventRenderCb(event, element, view)
|
||||
|
||||
@ -112,8 +156,6 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.moveBookingDelay = parseInt(settingsPromise.booking_move_delay)
|
||||
$scope.enableBookingCancel = (settingsPromise.booking_cancel_enable == "true")
|
||||
$scope.cancelBookingDelay = parseInt(settingsPromise.booking_cancel_delay)
|
||||
$scope.calendarConfig.minTime = moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss'))
|
||||
$scope.calendarConfig.maxTime = moment.duration(moment(settingsPromise.booking_window_end).format('HH:mm:ss'))
|
||||
|
||||
|
||||
|
||||
@ -125,8 +167,8 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
if $scope.ctrl.member
|
||||
Member.get {id: $scope.ctrl.member.id}, (member) ->
|
||||
$scope.ctrl.member = member
|
||||
Availability.trainings {member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
$scope.calendar.fullCalendar 'removeEvents'
|
||||
Availability.trainings {trainingId: $stateParams.id, member_id: $scope.ctrl.member.id}, (trainings) ->
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents'
|
||||
$scope.eventSources.push
|
||||
events: trainings
|
||||
textColor: 'black'
|
||||
@ -162,8 +204,8 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.selectedPlan = null
|
||||
$scope.trainingIsValid = false
|
||||
$timeout ->
|
||||
$scope.calendar.fullCalendar 'refetchEvents'
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -176,10 +218,12 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
reservation = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan)
|
||||
|
||||
if $scope.currentUser.role isnt 'admin' and $scope.amountTotal > 0
|
||||
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 $scope.amountTotal is 0
|
||||
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
|
||||
@ -235,7 +279,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$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
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -248,7 +292,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.slotToPlace.backgroundColor = 'white'
|
||||
$scope.slotToPlace.title = $scope.slotToPlace.training.name
|
||||
$scope.slotToPlace = null
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -281,7 +325,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.slotToModify.can_modify = false
|
||||
$scope.slotToModify.is_completed = false if $scope.slotToModify.is_completed
|
||||
$scope.slotToModify = null
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # failure
|
||||
growl.error('an_error_occured_preventing_the_booked_slot_from_being_modified')
|
||||
|
||||
@ -297,7 +341,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$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
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
@ -307,7 +351,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.updatePrices = ->
|
||||
if Object.keys($scope.ctrl.member).length > 0
|
||||
r = mkReservation($scope.ctrl.member, $scope.selectedTraining, $scope.selectedPlan)
|
||||
Price.compute {reservation: r}, (res) ->
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.amountTotal = res.price
|
||||
else
|
||||
$scope.amountTotal = null
|
||||
@ -324,6 +368,11 @@ 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()
|
||||
|
||||
|
||||
|
||||
##
|
||||
@ -351,6 +400,21 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
|
||||
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
##
|
||||
# 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 ...),
|
||||
@ -362,6 +426,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
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
|
||||
@ -378,7 +443,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
angular.forEach $scope.events, (e)->
|
||||
if event.id != e.id
|
||||
e.backgroundColor = 'white'
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
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)
|
||||
@ -388,7 +453,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.slotToPlace = event
|
||||
event.backgroundColor = '#bbb'
|
||||
event.title = event.training.name + ' - ' + _t('i_shift')
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
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)
|
||||
@ -406,7 +471,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.slotToModify = event
|
||||
event.backgroundColor = '#eee'
|
||||
event.title = event.training.name + ' - ' + _t('i_change')
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
else if type == 'cancel'
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
@ -425,7 +490,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
$scope.canceledSlot.can_modify = false
|
||||
$scope.canceledSlot.is_completed = false if event.is_completed
|
||||
$scope.canceledSlot = null
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
, -> # error while canceling
|
||||
growl.error _t('cancellation_failed')
|
||||
, -> # canceled
|
||||
@ -440,11 +505,12 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
||||
##
|
||||
eventRenderCb = (event, element, view)->
|
||||
element.attr(
|
||||
'uib-popover': event.training.description
|
||||
'popover-trigger': 'mouseenter'
|
||||
)
|
||||
$compile(element)($scope)
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
@ -460,12 +526,20 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute({reservation: reservation}).$promise
|
||||
Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise
|
||||
wallet: ->
|
||||
Wallet.getWalletByUser({user_id: reservation.user_id}).$promise
|
||||
cgv: ->
|
||||
CustomAsset.get({name: 'cgv-file'}).$promise
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation) ->
|
||||
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 = price.price
|
||||
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount)
|
||||
|
||||
# CGV
|
||||
$scope.cgv = cgv.custom_asset
|
||||
@ -473,6 +547,10 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# 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
|
||||
##
|
||||
@ -482,7 +560,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
else
|
||||
$scope.attempting = true
|
||||
$scope.reservation.card_token = response.id
|
||||
Reservation.save reservation: $scope.reservation, (reservation) ->
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
, (response)->
|
||||
$scope.alerts = []
|
||||
@ -511,17 +589,35 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
reservation: ->
|
||||
reservation
|
||||
price: ->
|
||||
Price.compute({reservation: reservation}).$promise
|
||||
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation) ->
|
||||
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.amount = price.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_on_site)')
|
||||
$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')
|
||||
|
||||
@ -530,7 +626,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
##
|
||||
$scope.ok = ->
|
||||
$scope.attempting = true
|
||||
Reservation.save reservation: $scope.reservation, (reservation) ->
|
||||
Reservation.save mkRequestParams($scope.reservation, coupon), (reservation) ->
|
||||
$uibModalInstance.close(reservation)
|
||||
$scope.attempting = true
|
||||
, (response)->
|
||||
@ -553,7 +649,7 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
# 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 {reservation: r}, (res) ->
|
||||
Price.compute mkRequestParams(r, $scope.coupon.applied), (res) ->
|
||||
$scope.selectedTrainingAmount = res.price
|
||||
else
|
||||
$scope.selectedTrainingAmount = null
|
||||
@ -587,8 +683,8 @@ Application.Controllers.controller "ReserveTrainingController", ["$scope", "$sta
|
||||
Auth._currentUser.training_credits = angular.copy(reservation.user.training_credits)
|
||||
Auth._currentUser.machine_credits = angular.copy(reservation.user.machine_credits)
|
||||
|
||||
$scope.calendar.fullCalendar 'refetchEvents'
|
||||
$scope.calendar.fullCalendar 'rerenderEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
|
||||
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
|
||||
|
||||
|
||||
|
||||
|
12
app/assets/javascripts/controllers/wallet.coffee
Normal file
12
app/assets/javascripts/controllers/wallet.coffee
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "WalletController", ['$scope', 'walletPromise', 'transactionsPromise', ($scope, walletPromise, transactionsPromise)->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## current user wallet
|
||||
$scope.wallet = walletPromise
|
||||
|
||||
## current wallet transactions
|
||||
$scope.transactions = transactionsPromise
|
||||
]
|
48
app/assets/javascripts/directives/coupon.coffee.erb
Normal file
48
app/assets/javascripts/directives/coupon.coffee.erb
Normal file
@ -0,0 +1,48 @@
|
||||
Application.Directives.directive 'coupon', [ 'Coupon', 'growl', '_t', (Coupon, growl, _t) ->
|
||||
{
|
||||
restrict: 'E'
|
||||
scope:
|
||||
show: '='
|
||||
coupon: '='
|
||||
userId: '@'
|
||||
hasSelectSlot: '='
|
||||
templateUrl: '<%= asset_path "shared/_coupon.html" %>'
|
||||
link: ($scope, element, attributes) ->
|
||||
|
||||
# Whether code input is shown or not (ie. the link 'I have a coupon' is shown)
|
||||
$scope.code =
|
||||
input: false
|
||||
|
||||
# Available status are: 'pending', 'valid', 'invalid'
|
||||
$scope.status = 'pending'
|
||||
|
||||
# Binding for the code inputed
|
||||
$scope.couponCode = null
|
||||
|
||||
$scope.$watch 'hasSelectSlot', (newValue) ->
|
||||
unless newValue
|
||||
$scope.coupon = null
|
||||
$scope.couponCode = null
|
||||
$scope.code.input = false
|
||||
|
||||
|
||||
##
|
||||
# Callback to validate the code
|
||||
##
|
||||
$scope.validateCode = ->
|
||||
if $scope.couponCode == ''
|
||||
$scope.status = 'pending'
|
||||
$scope.coupon = null
|
||||
else
|
||||
Coupon.validate {code: $scope.couponCode, user_id: $scope.userId}, (res) ->
|
||||
$scope.status = 'valid'
|
||||
$scope.coupon = res
|
||||
growl.success(_t('the_coupon_has_been_applied_you_get_PERCENT_discount', {PERCENT: res.percent_off}))
|
||||
, (err) ->
|
||||
$scope.status = 'invalid'
|
||||
$scope.coupon = null
|
||||
growl.error(_t('unable_to_apply_the_coupon_because_'+err.data.status))
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -18,7 +18,7 @@ Application.Directives.directive 'url', [ ->
|
||||
|
||||
|
||||
Application.Directives.directive 'endpoint', [ ->
|
||||
ENDPOINT_REGEXP = /^\/([-._~:?#\[\]@!$&'()*+,;=%\w]+\/?)*$/
|
||||
ENDPOINT_REGEXP = /^\/?([-._~:?#\[\]@!$&'()*+,;=%\w]+\/?)*$/
|
||||
{
|
||||
require: 'ngModel'
|
||||
link: (scope, element, attributes, ctrl) ->
|
||||
|
@ -98,10 +98,25 @@ Application.Filters.filter "humanize", [ ->
|
||||
Humanize.truncate(element, param, null)
|
||||
]
|
||||
|
||||
##
|
||||
# This filter will convert ASCII carriage-return character to the HTML break-line tag
|
||||
##
|
||||
Application.Filters.filter "breakFilter", [ ->
|
||||
(text) ->
|
||||
if text != undefined
|
||||
text.replace(/\n/g, '<br />')
|
||||
if text?
|
||||
text.replace(/\n+/g, '<br />')
|
||||
]
|
||||
|
||||
##
|
||||
# This filter will take a HTML text as input and will return it without the html tags
|
||||
##
|
||||
Application.Filters.filter "simpleText", [ ->
|
||||
(text) ->
|
||||
if text?
|
||||
text = text.replace(/<br\s*\/?>/g, '\n')
|
||||
text.replace(/<\/?\w+[^>]*>/g, '')
|
||||
else
|
||||
""
|
||||
]
|
||||
|
||||
Application.Filters.filter "toTrusted", [ "$sce", ($sce) ->
|
||||
@ -218,3 +233,28 @@ Application.Filters.filter 'toIsoDate', [ ->
|
||||
moment(date).format('YYYY-MM-DD')
|
||||
|
||||
]
|
||||
|
||||
Application.Filters.filter 'booleanFormat', [ '_t', (_t) ->
|
||||
(boolean) ->
|
||||
if boolean or boolean == 'true'
|
||||
_t('yes')
|
||||
else
|
||||
_t('no')
|
||||
]
|
||||
|
||||
Application.Filters.filter 'booleanFormat', [ '_t', (_t) ->
|
||||
(boolean) ->
|
||||
if (typeof boolean == 'boolean' and boolean) or (typeof boolean == 'string' and boolean == 'true')
|
||||
_t('yes')
|
||||
else
|
||||
_t('no')
|
||||
]
|
||||
|
||||
Application.Filters.filter 'maxCount', [ '_t', (_t) ->
|
||||
(max) ->
|
||||
if typeof max == 'undefined' or max == null or (typeof max == 'number' and max == 0)
|
||||
_t('unlimited')
|
||||
else
|
||||
max
|
||||
]
|
||||
|
||||
|
@ -197,6 +197,22 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.logged.dashboard.invoices').$promise
|
||||
]
|
||||
.state 'app.logged.dashboard.wallet',
|
||||
url: '/wallet'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "dashboard/wallet.html" %>'
|
||||
controller: 'WalletController'
|
||||
resolve:
|
||||
walletPromise: ['Wallet', 'currentUser', (Wallet, currentUser)->
|
||||
Wallet.getWalletByUser(user_id: currentUser.id).$promise
|
||||
]
|
||||
transactionsPromise: ['Wallet', 'walletPromise', (Wallet, walletPromise)->
|
||||
Wallet.transactions(id: walletPromise.id).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.shared.wallet']).$promise
|
||||
]
|
||||
|
||||
|
||||
# members
|
||||
@ -232,7 +248,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/projects?q&page&theme_id&component_id&machine_id&from&whole_network'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "projects/index.html" %>'
|
||||
templateUrl: '<%= asset_path "projects/index.html.erb" %>'
|
||||
controller: 'ProjectsController'
|
||||
resolve:
|
||||
themesPromise: ['Theme', (Theme)->
|
||||
@ -254,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
|
||||
]
|
||||
@ -280,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
|
||||
]
|
||||
@ -290,7 +312,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/machines'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "machines/index.html" %>'
|
||||
templateUrl: '<%= asset_path "machines/index.html.erb" %>'
|
||||
controller: 'MachinesController'
|
||||
resolve:
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
@ -335,6 +357,9 @@ angular.module('application.router', ['ui.router']).
|
||||
groupsPromise: ['Group', (Group)->
|
||||
Group.query().$promise
|
||||
]
|
||||
machinePromise: ['Machine', '$stateParams', (Machine, $stateParams)->
|
||||
Machine.get(id: $stateParams.id).$promise
|
||||
]
|
||||
settingsPromise: ['Setting', (Setting)->
|
||||
Setting.query(names: "['machine_explications_alert',
|
||||
'booking_window_start',
|
||||
@ -347,7 +372,8 @@ 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']).$promise
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input']).$promise
|
||||
]
|
||||
.state 'app.admin.machines_edit',
|
||||
url: '/machines/:id/edit'
|
||||
@ -363,8 +389,34 @@ angular.module('application.router', ['ui.router']).
|
||||
Translations.query(['app.admin.machines_edit', 'app.shared.machine']).$promise
|
||||
]
|
||||
# trainings
|
||||
.state 'app.public.trainings_list',
|
||||
url: '/trainings'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "trainings/index.html.erb" %>'
|
||||
controller: 'TrainingsController'
|
||||
resolve:
|
||||
trainingsPromise: ['Training', (Training)->
|
||||
Training.query({ public_page: true }).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.trainings_list']).$promise
|
||||
]
|
||||
.state 'app.public.training_show',
|
||||
url: '/trainings/:id'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "trainings/show.html" %>'
|
||||
controller: 'ShowTrainingController'
|
||||
resolve:
|
||||
trainingPromise: ['Training', '$stateParams', (Training, $stateParams)->
|
||||
Training.get({id: $stateParams.id}).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.training_show']).$promise
|
||||
]
|
||||
.state 'app.logged.trainings_reserve',
|
||||
url: '/trainings/reserve'
|
||||
url: '/trainings/:id/reserve'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "trainings/reserve.html" %>'
|
||||
@ -379,8 +431,11 @@ angular.module('application.router', ['ui.router']).
|
||||
groupsPromise: ['Group', (Group)->
|
||||
Group.query().$promise
|
||||
]
|
||||
availabilityTrainingsPromise: ['Availability', (Availability)->
|
||||
Availability.trainings().$promise
|
||||
availabilityTrainingsPromise: ['Availability', '$stateParams', (Availability, $stateParams)->
|
||||
Availability.trainings({trainingId: $stateParams.id}).$promise
|
||||
]
|
||||
trainingPromise: ['Training', '$stateParams', (Training, $stateParams)->
|
||||
Training.get({id: $stateParams.id}).$promise unless $stateParams.id == 'all'
|
||||
]
|
||||
settingsPromise: ['Setting', (Setting)->
|
||||
Setting.query(names: "['booking_window_start',
|
||||
@ -395,14 +450,15 @@ 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']).$promise
|
||||
'app.shared.stripe', 'app.shared.valid_reservation_modal', 'app.shared.confirm_modify_slot_modal',
|
||||
'app.shared.wallet', 'app.shared.coupon_input']).$promise
|
||||
]
|
||||
# notifications
|
||||
.state 'app.logged.notifications',
|
||||
url: '/notifications'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "notifications/index.html" %>'
|
||||
templateUrl: '<%= asset_path "notifications/index.html.erb" %>'
|
||||
controller: 'NotificationsController'
|
||||
resolve:
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
@ -415,7 +471,7 @@ angular.module('application.router', ['ui.router']).
|
||||
abstract: Fablab.withoutPlans
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "plans/index.html" %>'
|
||||
templateUrl: '<%= asset_path "plans/index.html.erb" %>'
|
||||
controller: 'PlansIndexController'
|
||||
resolve:
|
||||
subscriptionExplicationsPromise: ['Setting', (Setting)->
|
||||
@ -428,7 +484,8 @@ angular.module('application.router', ['ui.router']).
|
||||
Group.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.plans', 'app.shared.member_select', 'app.shared.stripe']).$promise
|
||||
Translations.query(['app.public.plans', 'app.shared.member_select', 'app.shared.stripe', 'app.shared.wallet',
|
||||
'app.shared.coupon_input']).$promise
|
||||
]
|
||||
|
||||
# events
|
||||
@ -436,9 +493,18 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/events'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "events/index.html" %>'
|
||||
templateUrl: '<%= asset_path "events/index.html.erb" %>'
|
||||
controller: 'EventsController'
|
||||
resolve:
|
||||
categoriesPromise: ['Category', (Category) ->
|
||||
Category.query().$promise
|
||||
]
|
||||
themesPromise: ['EventTheme', (EventTheme) ->
|
||||
EventTheme.query().$promise
|
||||
]
|
||||
ageRangesPromise: ['AgeRange', (AgeRange) ->
|
||||
AgeRange.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.public.events_list').$promise
|
||||
]
|
||||
@ -452,11 +518,39 @@ angular.module('application.router', ['ui.router']).
|
||||
eventPromise: ['Event', '$stateParams', (Event, $stateParams)->
|
||||
Event.get(id: $stateParams.id).$promise
|
||||
]
|
||||
reducedAmountAlert: ['Setting', (Setting)->
|
||||
Setting.get(name: 'event_reduced_amount_alert').$promise
|
||||
priceCategoriesPromise: ['PriceCategory', (PriceCategory) ->
|
||||
PriceCategory.query().$promise
|
||||
]
|
||||
settingsPromise: ['Setting', (Setting)->
|
||||
Setting.query(names: "['booking_move_enable', 'booking_move_delay']").$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.events_show', 'app.shared.member_select', 'app.shared.stripe', 'app.shared.valid_reservation_modal']).$promise
|
||||
Translations.query(['app.public.events_show', 'app.shared.member_select', 'app.shared.stripe',
|
||||
'app.shared.valid_reservation_modal', 'app.shared.wallet', 'app.shared.coupon_input']).$promise
|
||||
]
|
||||
|
||||
# global calendar (trainings, machines and events)
|
||||
.state 'app.public.calendar',
|
||||
url: '/calendar'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "calendar/calendar.html" %>'
|
||||
controller: 'CalendarController'
|
||||
resolve:
|
||||
bookingWindowStart: ['Setting', (Setting)->
|
||||
Setting.get(name: 'booking_window_start').$promise
|
||||
]
|
||||
bookingWindowEnd: ['Setting', (Setting)->
|
||||
Setting.get(name: 'booking_window_end').$promise
|
||||
]
|
||||
trainingsPromise: ['Training', (Training)->
|
||||
Training.query().$promise
|
||||
]
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
Machine.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.public.calendar']).$promise
|
||||
]
|
||||
|
||||
# --- namespace /admin/... ---
|
||||
@ -468,9 +562,6 @@ angular.module('application.router', ['ui.router']).
|
||||
templateUrl: '<%= asset_path "admin/calendar/calendar.html" %>'
|
||||
controller: 'AdminCalendarController'
|
||||
resolve:
|
||||
availabilitiesPromise: ['Availability', (Availability)->
|
||||
Availability.query().$promise
|
||||
]
|
||||
bookingWindowStart: ['Setting', (Setting)->
|
||||
Setting.get(name: 'booking_window_start').$promise
|
||||
]
|
||||
@ -489,7 +580,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/admin/project_elements'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/project_elements/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/project_elements/index.html.erb" %>'
|
||||
controller: 'ProjectElementsController'
|
||||
resolve:
|
||||
componentsPromise: ['Component', (Component)->
|
||||
@ -510,8 +601,8 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/admin/trainings'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/trainings/index.html" %>'
|
||||
controller: 'TrainingsController'
|
||||
templateUrl: '<%= asset_path "admin/trainings/index.html.erb" %>'
|
||||
controller: 'TrainingsAdminController'
|
||||
resolve:
|
||||
trainingsPromise: ['Training', (Training)->
|
||||
Training.query().$promise
|
||||
@ -520,20 +611,60 @@ angular.module('application.router', ['ui.router']).
|
||||
Machine.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.admin.trainings').$promise
|
||||
Translations.query(['app.admin.trainings', 'app.shared.trainings']).$promise
|
||||
]
|
||||
.state 'app.admin.trainings_new',
|
||||
url: '/admin/trainings/new'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/trainings/new.html" %>'
|
||||
controller: 'NewTrainingController'
|
||||
resolve:
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
Machine.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.trainings_new', 'app.shared.trainings']).$promise
|
||||
]
|
||||
.state 'app.admin.trainings_edit',
|
||||
url: '/admin/trainings/:id/edit'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/trainings/edit.html" %>'
|
||||
controller: 'EditTrainingController'
|
||||
resolve:
|
||||
trainingPromise: ['Training', '$stateParams', (Training, $stateParams)->
|
||||
Training.get(id: $stateParams.id).$promise
|
||||
]
|
||||
machinesPromise: ['Machine', (Machine)->
|
||||
Machine.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.shared.trainings').$promise
|
||||
]
|
||||
|
||||
# events
|
||||
.state 'app.admin.events',
|
||||
url: '/admin/events'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/events/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/events/index.html.erb" %>'
|
||||
controller: 'AdminEventsController'
|
||||
resolve:
|
||||
eventsPromise: ['Event', (Event)->
|
||||
Event.query(page: 1).$promise
|
||||
]
|
||||
categoriesPromise: ['Category', (Category) ->
|
||||
Category.query().$promise
|
||||
]
|
||||
themesPromise: ['EventTheme', (EventTheme) ->
|
||||
EventTheme.query().$promise
|
||||
]
|
||||
ageRangesPromise: ['AgeRange', (AgeRange) ->
|
||||
AgeRange.query().$promise
|
||||
]
|
||||
priceCategoriesPromise: ['PriceCategory', (PriceCategory) ->
|
||||
PriceCategory.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.admin.events').$promise
|
||||
]
|
||||
@ -544,6 +675,18 @@ angular.module('application.router', ['ui.router']).
|
||||
templateUrl: '<%= asset_path "events/new.html" %>'
|
||||
controller: 'NewEventController'
|
||||
resolve:
|
||||
categoriesPromise: ['Category', (Category) ->
|
||||
Category.query().$promise
|
||||
]
|
||||
themesPromise: ['EventTheme', (EventTheme) ->
|
||||
EventTheme.query().$promise
|
||||
]
|
||||
ageRangesPromise: ['AgeRange', (AgeRange) ->
|
||||
AgeRange.query().$promise
|
||||
]
|
||||
priceCategoriesPromise: ['PriceCategory', (PriceCategory) ->
|
||||
PriceCategory.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.events_new', 'app.shared.event']).$promise
|
||||
]
|
||||
@ -557,6 +700,18 @@ angular.module('application.router', ['ui.router']).
|
||||
eventPromise: ['Event', '$stateParams', (Event, $stateParams)->
|
||||
Event.get(id: $stateParams.id).$promise
|
||||
]
|
||||
categoriesPromise: ['Category', (Category) ->
|
||||
Category.query().$promise
|
||||
]
|
||||
themesPromise: ['EventTheme', (EventTheme) ->
|
||||
EventTheme.query().$promise
|
||||
]
|
||||
ageRangesPromise: ['AgeRange', (AgeRange) ->
|
||||
AgeRange.query().$promise
|
||||
]
|
||||
priceCategoriesPromise: ['PriceCategory', (PriceCategory) ->
|
||||
PriceCategory.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.events_edit', 'app.shared.event']).$promise
|
||||
]
|
||||
@ -582,7 +737,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/admin/pricing'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/pricing/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/pricing/index.html.erb" %>'
|
||||
controller: 'EditPricingController'
|
||||
resolve:
|
||||
plans: ['Plan', (Plan) ->
|
||||
@ -598,7 +753,7 @@ angular.module('application.router', ['ui.router']).
|
||||
TrainingsPricing.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.admin.pricing').$promise
|
||||
Translations.query(['app.admin.pricing', 'app.shared.member_select', 'app.shared.coupon']).$promise
|
||||
]
|
||||
trainingsPromise: ['Training', (Training) ->
|
||||
Training.query().$promise
|
||||
@ -612,6 +767,9 @@ angular.module('application.router', ['ui.router']).
|
||||
trainingCreditsPromise: ['Credit', (Credit) ->
|
||||
Credit.query({creditable_type: 'Training'}).$promise
|
||||
]
|
||||
couponsPromise: ['Coupon', (Coupon) ->
|
||||
Coupon.query().$promise
|
||||
]
|
||||
|
||||
# plans
|
||||
.state 'app.admin.plans',
|
||||
@ -656,6 +814,30 @@ angular.module('application.router', ['ui.router']).
|
||||
Translations.query(['app.admin.plans.edit', 'app.shared.plan']).$promise
|
||||
]
|
||||
|
||||
# coupons
|
||||
.state 'app.admin.coupons_new',
|
||||
url: '/admin/coupons/new'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/coupons/new.html" %>'
|
||||
controller: 'NewCouponController'
|
||||
resolve:
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.coupons_new', 'app.shared.coupon']).$promise
|
||||
]
|
||||
.state 'app.admin.coupons_edit',
|
||||
url: '/admin/coupons/:id/edit'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/coupons/edit.html" %>'
|
||||
controller: 'EditCouponController'
|
||||
resolve:
|
||||
couponPromise: ['Coupon', '$stateParams', (Coupon, $stateParams) ->
|
||||
Coupon.get({id: $stateParams.id}).$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.coupons_edit', 'app.shared.coupon']).$promise
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -664,7 +846,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/admin/invoices'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/invoices/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/invoices/index.html.erb" %>'
|
||||
controller: 'InvoicesController'
|
||||
resolve:
|
||||
settings: ['Setting', (Setting)->
|
||||
@ -695,16 +877,16 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/admin/members'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/members/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/members/index.html.erb" %>'
|
||||
controller: 'AdminMembersController'
|
||||
'groups@app.admin.members':
|
||||
templateUrl: '<%= asset_path "admin/groups/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/groups/index.html.erb" %>'
|
||||
controller: 'GroupsController'
|
||||
'tags@app.admin.members':
|
||||
templateUrl: '<%= asset_path "admin/tags/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/tags/index.html.erb" %>'
|
||||
controller: 'TagsController'
|
||||
'authentification@app.admin.members':
|
||||
templateUrl: '<%= asset_path "admin/authentications/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/authentications/index.html.erb" %>'
|
||||
controller: 'AuthentificationController'
|
||||
resolve:
|
||||
membersPromise: ['Member', (Member)->
|
||||
@ -745,11 +927,20 @@ angular.module('application.router', ['ui.router']).
|
||||
memberPromise: ['Member', '$stateParams', (Member, $stateParams)->
|
||||
Member.get(id: $stateParams.id).$promise
|
||||
]
|
||||
activeProviderPromise: ['AuthProvider', (AuthProvider) ->
|
||||
AuthProvider.active().$promise
|
||||
]
|
||||
walletPromise: ['Wallet', '$stateParams', (Wallet, $stateParams)->
|
||||
Wallet.getWalletByUser(user_id: $stateParams.id).$promise
|
||||
]
|
||||
transactionsPromise: ['Wallet', 'walletPromise', (Wallet, walletPromise)->
|
||||
Wallet.transactions(id: walletPromise.id).$promise
|
||||
]
|
||||
tagsPromise: ['Tag', (Tag)->
|
||||
Tag.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.members_edit', 'app.shared.user', 'app.shared.user_admin']).$promise
|
||||
Translations.query(['app.admin.members_edit', 'app.shared.user', 'app.shared.user_admin', 'app.shared.wallet']).$promise
|
||||
]
|
||||
.state 'app.admin.admins_new',
|
||||
url: '/admin/admins/new'
|
||||
@ -804,7 +995,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/admin/statistics'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/statistics/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/statistics/index.html.erb" %>'
|
||||
controller: 'StatisticsController'
|
||||
resolve:
|
||||
membersPromise: ['Member', (Member) ->
|
||||
@ -832,7 +1023,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/admin/settings'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/settings/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/settings/index.html.erb" %>'
|
||||
controller: 'SettingsController'
|
||||
resolve:
|
||||
settingsPromise: ['Setting', (Setting)->
|
||||
@ -846,7 +1037,6 @@ angular.module('application.router', ['ui.router']).
|
||||
'training_explications_alert',
|
||||
'training_information_message',
|
||||
'subscription_explications_alert',
|
||||
'event_reduced_amount_alert',
|
||||
'booking_window_start',
|
||||
'booking_window_end',
|
||||
'booking_move_enable',
|
||||
@ -856,7 +1046,9 @@ angular.module('application.router', ['ui.router']).
|
||||
'main_color',
|
||||
'secondary_color',
|
||||
'fablab_name',
|
||||
'name_genre'
|
||||
'name_genre',
|
||||
'reminder_enable',
|
||||
'reminder_delay'
|
||||
]").$promise
|
||||
]
|
||||
cguFile: ['CustomAsset', (CustomAsset) ->
|
||||
@ -880,7 +1072,7 @@ angular.module('application.router', ['ui.router']).
|
||||
url: '/open_api_clients'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/open_api_clients/index.html" %>'
|
||||
templateUrl: '<%= asset_path "admin/open_api_clients/index.html.erb" %>'
|
||||
controller: 'OpenAPIClientsController'
|
||||
resolve:
|
||||
clientsPromise: ['OpenAPIClient', (OpenAPIClient)->
|
||||
|
8
app/assets/javascripts/services/age_range.coffee
Normal file
8
app/assets/javascripts/services/age_range.coffee
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'AgeRange', ["$resource", ($resource)->
|
||||
$resource "/api/age_ranges/:id",
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
@ -14,7 +14,8 @@ Application.Services.factory 'Availability', ["$resource", ($resource)->
|
||||
isArray: true
|
||||
trainings:
|
||||
method: 'GET'
|
||||
url: '/api/availabilities/trainings'
|
||||
url: '/api/availabilities/trainings/:trainingId'
|
||||
params: {trainingId: "@trainingId"}
|
||||
isArray: true
|
||||
update:
|
||||
method: 'PUT'
|
||||
|
38
app/assets/javascripts/services/calendar.coffee
Normal file
38
app/assets/javascripts/services/calendar.coffee
Normal file
@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'CalendarConfig', [->
|
||||
(options = {}) ->
|
||||
# The calendar is divided in slots of 1 hour
|
||||
BASE_SLOT = '01:00:00'
|
||||
|
||||
# The calendar will be initialized positioned under 9:00 AM
|
||||
DEFAULT_CALENDAR_POSITION = '09:00:00'
|
||||
|
||||
defaultOptions =
|
||||
timezone: Fablab.timezone
|
||||
lang: Fablab.fullcalendar_locale
|
||||
header:
|
||||
left: 'month agendaWeek'
|
||||
center: 'title'
|
||||
right: 'today prev,next'
|
||||
firstDay: 1 # Week start on monday (France)
|
||||
scrollTime: DEFAULT_CALENDAR_POSITION
|
||||
slotDuration: BASE_SLOT
|
||||
allDayDefault: false
|
||||
minTime: "00:00:00"
|
||||
maxTime: "24:00:00"
|
||||
height: 'auto'
|
||||
buttonIcons:
|
||||
prev: 'left-single-arrow'
|
||||
next: 'right-single-arrow'
|
||||
timeFormat:
|
||||
agenda:'H:mm'
|
||||
month: 'H(:mm)'
|
||||
axisFormat: 'H:mm'
|
||||
|
||||
allDaySlot: false
|
||||
defaultView: 'agendaWeek'
|
||||
editable: false
|
||||
|
||||
Object.assign({}, defaultOptions, options)
|
||||
]
|
14
app/assets/javascripts/services/coupon.coffee
Normal file
14
app/assets/javascripts/services/coupon.coffee
Normal file
@ -0,0 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'Coupon', ["$resource", ($resource)->
|
||||
$resource "/api/coupons/:id",
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
validate:
|
||||
method: 'POST'
|
||||
url: '/api/coupons/validate'
|
||||
send:
|
||||
method: 'POST'
|
||||
url: '/api/coupons/send'
|
||||
]
|
8
app/assets/javascripts/services/event_theme.coffee
Normal file
8
app/assets/javascripts/services/event_theme.coffee
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'EventTheme', ["$resource", ($resource)->
|
||||
$resource "/api/event_themes/:id",
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
6
app/assets/javascripts/services/export.coffee
Normal file
6
app/assets/javascripts/services/export.coffee
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'Export', ["$http", ($http)->
|
||||
status: (query) ->
|
||||
$http.post('/api/exports/status', query)
|
||||
]
|
6
app/assets/javascripts/services/helpers.coffee
Normal file
6
app/assets/javascripts/services/helpers.coffee
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'helpers', [()->
|
||||
getAmountToPay: (price, walletAmount)->
|
||||
if walletAmount > price then 0 else price - walletAmount
|
||||
]
|
8
app/assets/javascripts/services/price_category.coffee
Normal file
8
app/assets/javascripts/services/price_category.coffee
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'PriceCategory', ["$resource", ($resource)->
|
||||
$resource "/api/price_categories/:id",
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
@ -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
|
||||
]
|
||||
|
@ -5,4 +5,7 @@ Application.Services.factory 'Training', ["$resource", ($resource)->
|
||||
{id: "@id"},
|
||||
update:
|
||||
method: 'PUT'
|
||||
availabilities:
|
||||
method: 'GET'
|
||||
url: "/api/trainings/:id/availabilities"
|
||||
]
|
||||
|
5
app/assets/javascripts/services/version.coffee
Normal file
5
app/assets/javascripts/services/version.coffee
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'Version', ["$resource", ($resource)->
|
||||
$resource "/api/version"
|
||||
]
|
18
app/assets/javascripts/services/wallet.coffee
Normal file
18
app/assets/javascripts/services/wallet.coffee
Normal file
@ -0,0 +1,18 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'Wallet', ["$resource", ($resource)->
|
||||
$resource "/api/wallet",
|
||||
{},
|
||||
getWalletByUser:
|
||||
method: 'GET'
|
||||
url: '/api/wallet/by_user/:user_id'
|
||||
isArray: false
|
||||
transactions:
|
||||
method: 'GET'
|
||||
url: '/api/wallet/:id/transactions'
|
||||
isArray: true
|
||||
credit:
|
||||
method: 'PUT'
|
||||
url: '/api/wallet/:id/credit'
|
||||
isArray: false
|
||||
]
|
@ -62,11 +62,9 @@ p {
|
||||
line-height: rem-calc(24);
|
||||
|
||||
&.intro, .intro {
|
||||
font-family: $font-proxima-condensed;
|
||||
font-size: rem-calc(16);
|
||||
line-height: rem-calc(24);
|
||||
margin: 1.038em 0 30px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-facebook {
|
||||
border: 1px solid #8b9dc3;
|
||||
background-color: #3b5998;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #7d8fb4;
|
||||
background-color: #394c89;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-twitter {
|
||||
border: 1px solid #ccd6dd;
|
||||
background-color: #55acee;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #bdc7ce;
|
||||
background-color: #539fdf;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.btn-block {
|
||||
padding-left: 12px;
|
||||
|
@ -6,6 +6,7 @@
|
||||
.bg-token { background-color: rgba(230, 208, 137, 0.49); }
|
||||
.bg-machine { background-color: $beige; }
|
||||
.bg-formation { background-color: $violet; }
|
||||
.bg-event { background-color: $japonica; }
|
||||
.bg-atelier { background-color: $blue; }
|
||||
.bg-stage { background-color: $violet; }
|
||||
.bg-success { background-color: $brand-success; }
|
||||
@ -35,3 +36,7 @@
|
||||
.text-blue { color: $blue; }
|
||||
.text-muted { color: $text-muted; }
|
||||
.text-danger, .red { color: $red !important; }
|
||||
.text-purple { color: $violet !important; }
|
||||
.text-japonica { color: $japonica !important; }
|
||||
.text-beige { color: $beige !important; }
|
||||
.text-green, .green { color: #79C84A !important; }
|
||||
|
@ -154,8 +154,11 @@
|
||||
}
|
||||
|
||||
.article-thumbnail {
|
||||
// max-height: 400px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,6 +420,7 @@
|
||||
|
||||
.event {
|
||||
transition: all 0.07s linear;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.event:hover {
|
||||
@ -426,13 +430,13 @@ color: white;
|
||||
|
||||
|
||||
.event:hover * {
|
||||
color: #eee;
|
||||
color: #eee !important;
|
||||
border-color: #eee;
|
||||
}
|
||||
|
||||
.box-h-m {
|
||||
height: 150px;
|
||||
max-height: 150px;
|
||||
height: 175px;
|
||||
max-height: 175px;
|
||||
}
|
||||
|
||||
.half-w {
|
||||
@ -446,25 +450,31 @@ border-color: #d0d0d0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.crop-130 {
|
||||
height: 130px;
|
||||
width: 130px;
|
||||
max-width: 130px;
|
||||
max-height: 130px;
|
||||
.crop-155 {
|
||||
height: 155px;
|
||||
width: 155px;
|
||||
max-width: 155px;
|
||||
max-height: 155px;
|
||||
overflow: hidden;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.crop-130 img {
|
||||
height: 130px;
|
||||
.crop-155 img {
|
||||
height: 155px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1280px) and (min-width: 770px) {
|
||||
.crop-130 {
|
||||
@media only screen and (max-width: 1375px) and (min-width: 770px) {
|
||||
.crop-155 {
|
||||
height: 90px;
|
||||
width: 90px;
|
||||
margin-top: 25px;
|
||||
margin-top: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1375px) and (min-width: 1125px) {
|
||||
.half-w {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,6 +499,11 @@ padding: 10px;
|
||||
@media only screen and (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-version {
|
||||
margin-right: 10px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.disabling-overlay {
|
||||
@ -504,3 +519,77 @@ padding: 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-amount-container {
|
||||
padding: 20px 0;
|
||||
border-top: 2px dotted $border-color;
|
||||
border-bottom: 2px dotted $border-color;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
|
||||
.wallet-amount {
|
||||
font-size: rem-calc(40);
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
color: #616161;
|
||||
|
||||
span {
|
||||
font-weight: 500;
|
||||
font-size: .7em;
|
||||
}
|
||||
|
||||
&.cr-green {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.amountGroup {
|
||||
input {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
margin-left: 5px;
|
||||
padding-right: 6px;
|
||||
font-weight: bold;
|
||||
color: $green;
|
||||
font-size: 1.2em;
|
||||
line-height: 0;
|
||||
}
|
||||
.afterAmount {
|
||||
margin-left: -35px;
|
||||
font-weight: bold;
|
||||
color: $green;
|
||||
font-size: 1.2em;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
input[type=checkbox] {
|
||||
font-size: 16px;
|
||||
width: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.link-icon {
|
||||
color: #1c94c4;
|
||||
i { margin: 0 5px 0 10px; }
|
||||
span {
|
||||
border-bottom: 1px dashed #00b3ee;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.description-hover {
|
||||
span {
|
||||
display: inline-block;
|
||||
border-bottom: 1px dashed #00b3ee;
|
||||
cursor: help;
|
||||
}
|
||||
}
|
@ -88,7 +88,7 @@
|
||||
color: black;
|
||||
}
|
||||
.heading-title {
|
||||
overflow: hidden;
|
||||
//overflow: hidden;
|
||||
height: 94px;
|
||||
h1 {
|
||||
margin: 0 0 0 15px;
|
||||
@ -605,3 +605,13 @@ body.container{
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.calendar-filter {
|
||||
h3 {
|
||||
line-height: 2.1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-filter-aside {
|
||||
padding: 20px;
|
||||
}
|
||||
|
@ -91,7 +91,7 @@
|
||||
cursor: pointer;
|
||||
z-index: 9999;
|
||||
text-align: right;
|
||||
.training-reserve &, .machine-reserve & { display: none; }
|
||||
.training-reserve &, .machine-reserve &, .public-calendar & { display: none; }
|
||||
}
|
||||
|
||||
.fc-v-event.fc-end {
|
||||
@ -102,6 +102,15 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.calendar-filter {
|
||||
.badge {
|
||||
cursor: pointer;
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -102,6 +102,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;}
|
||||
@ -125,7 +126,9 @@ p, .widget p {
|
||||
.pull-in{margin-left: -15px;margin-right: -15px;}
|
||||
.pull-out{margin:-10px -15px;}
|
||||
|
||||
.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}
|
||||
@ -171,6 +174,7 @@ p, .widget p {
|
||||
}
|
||||
.r-n { border-radius: 0 0 0 0; }
|
||||
|
||||
.p-xs { padding: 5px;}
|
||||
.p-lg { padding: 30px; }
|
||||
.p-l { padding: 16px; }
|
||||
|
||||
@ -342,6 +346,7 @@ p, .widget p {
|
||||
|
||||
@media screen and (min-width: $screen-lg-min) {
|
||||
.b-r-lg {border-right: 1px solid $border-color; }
|
||||
.hide-b-r-lg { border: none !important; }
|
||||
}
|
||||
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
*= require bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min
|
||||
*= require summernote/dist/summernote
|
||||
*= require jquery-minicolors/jquery.minicolors.css
|
||||
*= require angular-aside/dist/css/angular-aside
|
||||
*/
|
||||
|
||||
@import "app.functions";
|
||||
|
@ -43,6 +43,7 @@ $blue: $brand-info;
|
||||
$green: $brand-success;
|
||||
$beige: #e4cd78;
|
||||
$violet: #bd7ae9;
|
||||
$japonica: #dd7e6b;
|
||||
|
||||
$border-color: #dddddd;
|
||||
$header-bg: $bg-gray;
|
||||
|
@ -0,0 +1,65 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title"><span translate>{{ 'data_mapping' }}</span> : {{field.local_field}}</h3>
|
||||
</div>
|
||||
<div class="modal-body m-lg">
|
||||
<div>
|
||||
<span translate>{{ 'expected_data_type' }}</span> : {{datatype}}
|
||||
</div>
|
||||
<form name="mappingForm" class="m-t-md">
|
||||
<ng-switch on="datatype">
|
||||
|
||||
<!-- BOOLEAN -->
|
||||
<div ng-switch-when="boolean">
|
||||
<label for="add_mapping" translate>{{ 'mappings' }}</label>
|
||||
<ul class="list-unstyled">
|
||||
<li class="m-t-sm m-l">
|
||||
<input type="text"
|
||||
name="true_value"
|
||||
id="true_value"
|
||||
class="form-control inline width-35 m-r "
|
||||
ng-model="transformation.rules.false_value">
|
||||
<i class="fa fa-arrows-h"></i>
|
||||
<label for="true_value" class="m-l">true</label>
|
||||
</li>
|
||||
<li class="m-t-sm m-l">
|
||||
<input type="text"
|
||||
name="false_value"
|
||||
id="false_value"
|
||||
class="form-control inline width-35 m-r "
|
||||
ng-model="transformation.rules.true_value">
|
||||
<i class="fa fa-arrows-h"></i>
|
||||
<label for="false_value" class="m-l">false</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- DATE -->
|
||||
<div ng-switch-when="date">
|
||||
<label for="date_format" translate>{{ 'input_format' }}</label>
|
||||
<select name="date_format"
|
||||
id="date_format"
|
||||
class="form-control"
|
||||
ng-model="transformation.rules.format"
|
||||
ng-options="format.value as format.label for format in formats.date">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- INTEGER -->
|
||||
<div ng-switch-when="integer">
|
||||
<label for="add_mapping" translate>{{ 'mappings' }}</label>
|
||||
<button class="btn btn-default pull-right" ng-click="addIntegerMapping()"><i class="fa fa-plus"></i></button>
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="map in transformation.rules.mapping" class="m-t-sm m-l">
|
||||
<input type="text" class="form-control inline width-35 m-r " ng-model="map.from">
|
||||
<i class="fa fa-arrows-h"></i>
|
||||
<input type="number" class="form-control inline width-35 m-l" ng-model="map.to">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ng-switch>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" ng-click="ok()" ng-disabled="!mappingForm.$valid" ng-if="datatype != 'string' && datatype != 'text'" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-warning" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
@ -21,6 +21,9 @@
|
||||
<td>{{m.api_data_type}}</td>
|
||||
<td>{{m.api_field}}</td>
|
||||
<td>
|
||||
<button class="btn btn-info" ng-click="defineDataMapping(m)">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="m._destroy = true">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
@ -38,7 +41,7 @@
|
||||
<td ng-class="{'has-error': mappingForm['auth_mapping[local_field]'].$dirty && mappingForm['auth_mapping[local_field]'].$invalid}">
|
||||
<select class="form-control"
|
||||
name="auth_mapping[local_field]"
|
||||
ng-options="field for field in mappingFields[newMapping.local_model]"
|
||||
ng-options="field[0] as field[0] for field in mappingFields[newMapping.local_model]"
|
||||
ng-model="newMapping.local_field"
|
||||
required>
|
||||
</select>
|
||||
|
@ -14,6 +14,8 @@
|
||||
<tr>
|
||||
<th style="width:15%" translate>{{ 'name' }}</th>
|
||||
|
||||
<th style="width:15%" translate>{{ 'strategy_name' }}</th>
|
||||
|
||||
<th style="width:15%" translate>{{ 'type' }}</th>
|
||||
|
||||
<th style="width:15%" translate>{{ 'state' }}</th>
|
||||
@ -24,6 +26,7 @@
|
||||
<tbody>
|
||||
<tr ng-repeat="provider in providers | filter:searchFilter">
|
||||
<td>{{ provider.name }}</td>
|
||||
<td>{{ provider.strategy_name }}</td>
|
||||
<td>{{ getType(provider.providable_type) }}</td>
|
||||
<td>{{ getState(provider.status) }}</td>
|
||||
<td>
|
||||
|
103
app/assets/templates/admin/coupons/_form.html.erb
Normal file
103
app/assets/templates/admin/coupons/_form.html.erb
Normal file
@ -0,0 +1,103 @@
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[name]'].$dirty && couponForm['coupon[name]'].$invalid}">
|
||||
<label for="coupon[name]">{{ 'name' | translate }} *</label>
|
||||
<input type="text" id="coupon[name]"
|
||||
name="coupon[name]"
|
||||
class="form-control"
|
||||
ng-model="coupon.name"
|
||||
required="required"/>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[name]'].$dirty && couponForm['coupon[name]'].$error.required" translate>{{ 'name_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$invalid}">
|
||||
<label for="coupon[code]">{{ 'code' | translate }} *</label>
|
||||
<input type="text" id="coupon[code]"
|
||||
name="coupon[code]"
|
||||
class="form-control"
|
||||
ng-model="coupon.code"
|
||||
ng-pattern="/^[A-Z0-9\-]+$/"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
required="required"/>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$error.required" translate>{{ 'code_is_required' }}</span>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[code]'].$dirty && couponForm['coupon[code]'].$error.pattern" translate>{{ 'code_must_be_composed_of_capital_letters_digits_and_or_dashes' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': couponForm['coupon[percent_off]'].$dirty && couponForm['coupon[percent_off]'].$invalid}">
|
||||
<label for="coupon[percent_off]">{{ 'percent_off' | translate }} *</label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="coupon[percent_off]"
|
||||
name="coupon[percent_off]"
|
||||
class="form-control"
|
||||
ng-model="coupon.percent_off"
|
||||
min="0"
|
||||
max="100"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
required="required"/>
|
||||
<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[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]"
|
||||
name="coupon[validity_per_user]"
|
||||
class="form-control"
|
||||
ng-model="coupon.validity_per_user"
|
||||
required="required"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
ng-options="( validity | translate ) for validity in validities">
|
||||
</select>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[validity_per_user]'].$dirty && couponForm['coupon[validity_per_user]'].$error.required" translate>{{ 'validity_per_user_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="coupon[valid_until]" translate>{{ 'valid_until' }}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="coupon[valid_until]"
|
||||
name="coupon[valid_until]"
|
||||
class="form-control"
|
||||
ng-model="coupon.valid_until"
|
||||
uib-datepicker-popup="{{datePicker.format}}"
|
||||
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 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}">
|
||||
<label for="coupon[max_usages]">{{ 'max_usages' | translate }}</label>
|
||||
<input type="number" id="coupon[max_usages]"
|
||||
name="coupon[max_usages]"
|
||||
class="form-control"
|
||||
ng-model="coupon.max_usages"
|
||||
ng-disabled="mode == 'EDIT'"
|
||||
min="0"/>
|
||||
<span class="help-block error" ng-show="couponForm['coupon[max_usages]'].$dirty && couponForm['coupon[max_usages]'].$error.min" translate>{{ 'max_usages_must_be_equal_or_greater_than_0' }}</span>
|
||||
|
||||
<span class="help-block text-info text-xs">
|
||||
<i class="fa fa-lightbulb-o"></i> {{ 'leave_empty_for_no_limit' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="coupon[active]" translate>{{ 'enabled' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="coupon.active"
|
||||
id="coupon[active]"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true" />
|
||||
<input type="hidden" name="coupon[active]" value="{{coupon.active}}"/>
|
||||
</div>
|
40
app/assets/templates/admin/coupons/edit.html.erb
Normal file
40
app/assets/templates/admin/coupons/edit.html.erb
Normal file
@ -0,0 +1,40 @@
|
||||
<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-7 col-sm-7 col-md-8 b-l">
|
||||
<section class="heading-title">
|
||||
<h1>{{ 'coupon' | translate }} : {{ coupon.name }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-3 col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
<a class="btn btn-lg btn-block btn-default m-t-xs" ui-sref="app.admin.pricing" translate>{{ 'cancel' }}</a>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row no-gutter">
|
||||
<div class=" col-sm-12 col-md-9 b-r nopadding">
|
||||
|
||||
<div id="couponForm">
|
||||
<form name="couponForm" novalidate="novalidate" class="col-lg-7 col-lg-offset-2 m-t-lg form-group">
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/coupons/_form.html' %>'"></ng-include>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="button" value="{{ 'confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="couponForm.$invalid" ng-click="updateCoupon()"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
33
app/assets/templates/admin/coupons/new.html.erb
Normal file
33
app/assets/templates/admin/coupons/new.html.erb
Normal file
@ -0,0 +1,33 @@
|
||||
<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">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'add_a_coupon' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row no-gutter">
|
||||
<div class=" col-sm-12 col-md-9 b-r nopadding">
|
||||
|
||||
<div id="couponForm">
|
||||
<form name="couponForm" novalidate="novalidate" class="col-lg-10 col-lg-offset-2 m-t-lg form-group">
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/coupons/_form.html' %>'"></ng-include>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="button" value="{{ 'save' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="couponForm.$invalid" ng-click="saveCoupon()"/>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
120
app/assets/templates/admin/events/filters.html.erb
Normal file
120
app/assets/templates/admin/events/filters.html.erb
Normal file
@ -0,0 +1,120 @@
|
||||
<div class="m-t">
|
||||
<h3 translate>{{ 'categories' }}</h3>
|
||||
<p translate>{{ 'at_least_one_category_is_required' }}</p>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('category')" translate>{{ 'add_a_category' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="category in categories">
|
||||
<td>
|
||||
<span editable-text="category.name" e-cols="100" e-name="name" e-form="rowform" e-required>
|
||||
{{ category.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveElement('category', $data, category.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted.category == category">
|
||||
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelElement('category', rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeElement('category', $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 translate>{{ 'themes' }}</h3>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('theme')" translate>{{ 'add_a_theme' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="theme in themes">
|
||||
<td>
|
||||
<span editable-text="theme.name" e-cols="100" e-name="name" e-form="rowform" e-required>
|
||||
{{ theme.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveElement('theme', $data, theme.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted.theme == theme">
|
||||
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelElement('theme', rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeElement('theme', $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 translate>{{ 'age_ranges' }}</h3>
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addElement('age_range')" translate>{{ 'add_a_range' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="range in ageRanges">
|
||||
<td>
|
||||
<span editable-text="range.name" e-cols="100" e-name="name" e-form="rowform" e-required>
|
||||
{{ range.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveElement('age_range', $data, range.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted.age_range == range">
|
||||
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelElement('age_range', rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeElement('age_range', $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'fablab_courses_and_workshops' }}</h1>
|
||||
<h1 translate>{{ 'fablab_events' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -21,57 +21,19 @@
|
||||
<section class="m-lg">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
<uib-tab heading="{{ 'events_monitoring' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/events/monitoring.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<div class="col-md-6 m-b">
|
||||
<select ng-model="selectedTimezone" class="form-control">
|
||||
<option value="" translate>{{ 'all_events' }}</option>
|
||||
<option value="passed" translate>{{ 'passed_events' }}</option>
|
||||
<option value="future" translate>{{ 'events_to_come' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<uib-tab heading="{{ 'manage_filters' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/events/filters.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30%" translate>{{ 'title' }}</th>
|
||||
<th style="width:30%" translate>{{ 'dates' }}</th>
|
||||
<th style="width:40%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="event in filtered = (events | eventsReservationsFilter:selectedTimezone)">
|
||||
<td>
|
||||
<a ui-sref="app.public.events_show({id: event.id})">{{ event.title }} </a>
|
||||
</td>
|
||||
<td>
|
||||
<span> {{ 'from_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }} <span class="text-sm font-thin" translate>{{ 'to_date' }}</span> {{event.end_date | amDateFormat:'LL'}}</span>
|
||||
<br/>
|
||||
<span ng-if="event.all_day == 'true'" translate>{{ 'all_day' }}</span>
|
||||
<span ng-if="event.all_day == 'false'">
|
||||
{{ 'from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
|
||||
<span class="text-sm font-thin" translate>{{ 'to_time' }}</span>
|
||||
{{event.end_date | amDateFormat:'LT'}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.admin.event_reservations({id: event.id})">
|
||||
<i class="fa fa-bookmark"></i> {{ 'view_reservations' | translate }}
|
||||
</button>
|
||||
<button class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive" translate>{{ 'load_the_next_courses_and_workshops' }}</a>
|
||||
<uib-tab heading="{{ 'manage_prices_categories' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/events/prices.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
50
app/assets/templates/admin/events/monitoring.html.erb
Normal file
50
app/assets/templates/admin/events/monitoring.html.erb
Normal file
@ -0,0 +1,50 @@
|
||||
<div class="col-md-6 m-b m-t">
|
||||
<select ng-model="selectedTimezone" class="form-control">
|
||||
<option value="" translate>{{ 'all_events' }}</option>
|
||||
<option value="passed" translate>{{ 'passed_events' }}</option>
|
||||
<option value="future" translate>{{ 'events_to_come' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30%" translate>{{ 'title' }}</th>
|
||||
<th style="width:30%" translate>{{ 'dates' }}</th>
|
||||
<th style="width:40%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="event in filtered = (events | eventsReservationsFilter:selectedTimezone)">
|
||||
<td>
|
||||
<a ui-sref="app.public.events_show({id: event.id})">{{ event.title }} </a>
|
||||
</td>
|
||||
<td>
|
||||
<span> {{ 'from_DATE' | translate:{DATE:(event.start_date | amDateFormat:'LL')} }} <span class="text-sm font-thin" translate>{{ 'to_date' }}</span> {{event.end_date | amDateFormat:'LL'}}</span>
|
||||
<br/>
|
||||
<span ng-if="event.all_day == 'true'" translate>{{ 'all_day' }}</span>
|
||||
<span ng-if="event.all_day == 'false'">
|
||||
{{ 'from_TIME' | translate:{TIME:(event.start_date | amDateFormat:'LT')} }}
|
||||
<span class="text-sm font-thin" translate>{{ 'to_time' }}</span>
|
||||
{{event.end_date | amDateFormat:'LT'}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.admin.event_reservations({id: event.id})">
|
||||
<i class="fa fa-bookmark"></i> {{ 'view_reservations' | translate }}
|
||||
</button>
|
||||
<button class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<a class="btn btn-warning" ng-click="loadMoreEvents()" ng-if="paginateActive" translate>{{ 'load_the_next_events' }}</a>
|
||||
</div>
|
||||
</div>
|
40
app/assets/templates/admin/events/price_form.html
Normal file
40
app/assets/templates/admin/events/price_form.html
Normal file
@ -0,0 +1,40 @@
|
||||
<div class="modal-header">
|
||||
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
|
||||
<h1 translate>{{ 'price_category' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form" name="priceCategoryForm" class="form-horizontal" novalidate autocomplete="off" ng-keydown="priceCategoryForm.$valid && $event.which == 13 && ok()">
|
||||
<div class="form-group" ng-class="{'has-error': priceCategoryForm.name.$dirty && priceCategoryForm.name.$invalid}">
|
||||
<div class="col-sm-12">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-tag"></i></span>
|
||||
<input type="text"
|
||||
name="name"
|
||||
ng-model="category.name"
|
||||
class="form-control"
|
||||
placeholder="{{ 'category_name' | translate }}"
|
||||
required />
|
||||
</div>
|
||||
<span class="help-block" ng-show="priceCategoryForm.name.$dirty && priceCategoryForm.name.$error.required" translate>{{ 'category_name_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': priceCategoryForm.conditions.$dirty && priceCategoryForm.conditions.$invalid}">
|
||||
<div class="col-sm-12">
|
||||
<textarea ng-model="category.conditions"
|
||||
rows="10"
|
||||
class="form-control"
|
||||
id="conditions"
|
||||
placeholder="{{ 'enter_here_the_conditions_under_which_this_price_is_applicable' | translate }}"
|
||||
name="conditions"
|
||||
required>
|
||||
</textarea>
|
||||
<span class="help-block" ng-show="priceCategoryForm.conditions.$dirty && priceCategoryForm.conditions.$error.required" translate>{{ 'conditions_are_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok()" ng-disabled="priceCategoryForm.$invalid" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
31
app/assets/templates/admin/events/prices.html.erb
Normal file
31
app/assets/templates/admin/events/prices.html.erb
Normal file
@ -0,0 +1,31 @@
|
||||
<div class="m-t">
|
||||
<h3 translate>{{ 'prices_categories' }}</h3>
|
||||
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="newPriceCategory()" translate>{{ 'add_a_price_category' }}</button>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40%" translate>{{ 'name' }}</th>
|
||||
<th style="width:40%" translate>{{ 'usages_count' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="category in priceCategories">
|
||||
<td>{{ category.name }}</td>
|
||||
<td>{{ category.events }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ng-click="editPriceCategory(category.id, $index)">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removePriceCategory(category.id, $index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
@ -32,7 +32,10 @@
|
||||
<a ui-sref="app.logged.members_show({id: reservation.user_id})">{{ reservation.user_full_name }} </a>
|
||||
</td>
|
||||
<td>{{ reservation.created_at | amDateFormat:'LL LTS' }}</td>
|
||||
<td><span ng-if="reservation.nb_reserve_places > 0">{{ 'full_price_' | translate }} {{reservation.nb_reserve_places}}<br/></span><span ng-if="reservation.nb_reserve_reduced_places > 0">{{ 'reduced_rate_' | translate }} {{reservation.nb_reserve_reduced_places}}</span></td>
|
||||
<td>
|
||||
<span ng-if="reservation.nb_reserve_places > 0">{{ 'full_price_' | translate }} {{reservation.nb_reserve_places}}<br/></span>
|
||||
<span ng-repeat="ticket in reservation.tickets">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.public.events_show({id: event.id})">
|
||||
|
@ -210,6 +210,7 @@
|
||||
<li ng-click="invoice.reference.help = 'addDay.html'">{{ 'day' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addInvoiceNumber.html'">{{ '#_of_invoice' | translate }}</li>
|
||||
<li ng-click="invoice.reference.help = 'addOnlineInfo.html'">{{ 'online_sales' | translate }}</li>
|
||||
<%# <li ng-click="invoice.reference.help = 'addWalletInfo.html'">{{ 'wallet' | translate }}</li> %>
|
||||
<li ng-click="invoice.reference.help = 'addRefundInfo.html'">{{ 'refund' | translate }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -279,6 +280,12 @@
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addWalletInfo.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>W[texte]</strong></td><td>{{ 'add_a_notice_regarding_the_wallet_only_if_the_invoice_is_concerned' | translate }} <mark translate>{{ 'this_will_never_be_added_when_a_refund_notice_is_present' }}</mark> {{ '(eg_W[/PM]_will_add_/PM_to_the_invoices_settled_with_wallet)' | translate }}</td></tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="addRefundInfo.html">
|
||||
<table class="invoice-element-legend">
|
||||
<tr><td><strong>R[texte]</strong></td><td>{{ 'add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned' | translate }}<mark translate>{{ 'this_will_never_be_added_when_an_online_sales_notice_is_present' }}</mark> {{ '(eg_R[/A]_will_add_/A_to_the_refund_invoices)' | translate }}</td></tr>
|
||||
|
39
app/assets/templates/admin/members/administrators.html.erb
Normal file
39
app/assets/templates/admin/members/administrators.html.erb
Normal file
@ -0,0 +1,39 @@
|
||||
<div class="col-md-5 m-t-lg">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'search_for_an_administrator' | translate }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" translate>{{ 'add_a_new_administrator' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="admin in admins | filter:searchFilter | orderBy: orderAdmin">
|
||||
<td class="text-c">{{ admin.profile_attributes.last_name }}</td>
|
||||
<td class="text-c">{{ admin.profile_attributes.first_name }}</td>
|
||||
<td>{{ admin.email }}</td>
|
||||
<td>{{ admin.profile_attributes.phone }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger" ng-if="admin.id != currentUser.id" ng-click="destroyAdmin(admins, admin)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
@ -9,7 +9,8 @@
|
||||
</div>
|
||||
<div class="col-md-8 b-l b-r">
|
||||
<section class="heading-title">
|
||||
<h1>{{ 'user' | translate }} {{ user.name }}</h1>
|
||||
<h1 class="inline">{{ 'user' | translate }} {{ user.name }}</h1>
|
||||
<span class="label label-danger text-white" ng-show="user.need_completion" translate>{{ 'incomplete_profile' }}</span>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -33,7 +34,14 @@
|
||||
|
||||
<uib-tabset justified="true" class="m-t">
|
||||
|
||||
<uib-tab heading="Profil utilisateur">
|
||||
<uib-tab heading="{{ 'user_profile' | translate }}">
|
||||
|
||||
<section class="panel panel-danger m-lg" ng-show="user.need_completion && activeProvider.providable_type !== 'DatabaseProvider'">
|
||||
<div class="panel-body m-r" translate>
|
||||
{{ 'warning_incomplete_user_profile_probably_imported_from_sso' }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<form role="form" name="userForm" class="form-horizontal col-md-8" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
|
||||
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
@ -138,11 +146,11 @@
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'courses_and_workshops' | translate }}">
|
||||
<uib-tab heading="{{ 'events' | translate }}">
|
||||
<div class="col-md-6">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b">
|
||||
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'next_courses_and_workshops' | translate }}</h4>
|
||||
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'next_events' | translate }}</h4>
|
||||
</div>
|
||||
<div class="widget-content bg-light wrapper r-b">
|
||||
<ul class="list-unstyled" ng-if="user.events_reservations.length > 0">
|
||||
@ -152,20 +160,20 @@
|
||||
<br/>
|
||||
<span translate translate-values="{ NUMBER: r.nb_reserve_places}" translate-interpolation="messageformat">{{ 'NUMBER_full_price_tickets_reserved' }}</span>
|
||||
</span>
|
||||
<span ng-if="r.nb_reserve_reduced_places > 0">
|
||||
<span ng-repeat="ticket in r.tickets">
|
||||
<br/>
|
||||
<span translate translate-values="{ NUMBER: r.nb_reserve_reduced_places}" translate-interpolation="messageformat">{{ 'NUMBER_reduced_rate_tickets_reserved' }}</span>
|
||||
<span translate translate-values="{ NUMBER: ticket.booked, NAME: ticket.price_category.name }" translate-interpolation="messageformat">{{ 'NUMBER_NAME_tickets_reserved' }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="(user.events_reservations | eventsReservationsFilter:'future').length == 0" translate>{{ 'no_upcomning_courses_or_workshops'}}</div>
|
||||
<div ng-if="(user.events_reservations | eventsReservationsFilter:'future').length == 0" translate>{{ 'no_upcoming_events' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="widget panel b-a m m-t-lg">
|
||||
<div class="panel-heading b-b">
|
||||
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'passed_courses_and_workshops' | translate }}</h4>
|
||||
<h4 class="text-u-c"><i class="fa fa-tag m-r-xs"></i> {{ 'passed_events' | translate }}</h4>
|
||||
</div>
|
||||
<div class="widget-content bg-light auto wrapper r-b">
|
||||
<ul class="list-unstyled" ng-if="user.events_reservations.length > 0">
|
||||
@ -173,7 +181,7 @@
|
||||
<span class="font-sbold">{{r.reservable.title}}</span> - <span class="label label-info text-white wrapper-sm">{{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div ng-if="(user.events_reservations | eventsReservationsFilter:'passed').length == 0" translate>{{ 'no_passed_courses_or_workshop' }}</div>
|
||||
<div ng-if="(user.events_reservations | eventsReservationsFilter:'passed').length == 0" translate>{{ 'no_passed_events' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -215,6 +223,22 @@
|
||||
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'wallet' | translate }}">
|
||||
<div class="col-md-12 m m-t-lg">
|
||||
<ng-include src="'<%= asset_path 'wallet/show.html' %>'"></ng-include>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div class="col-sm-4 text-center">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="createWalletCreditModal(user, wallet)" translate>{{ 'to_credit' }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 m m-t-lg">
|
||||
<ng-include src="'<%= asset_path 'wallet/transactions.html' %>'"></ng-include>
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
||||
</div>
|
||||
|
@ -20,111 +20,11 @@
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'members' | translate }}">
|
||||
<div class="col-md-5 m-t-lg">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<input type="text" ng-model="member.searchText" class="form-control" placeholder="{{ 'search_for_an_user' | translate }}" ng-change="updateTextSearch()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.members_new" translate>{{ 'add_a_new_member' }}</button>
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-default" ng-href="api/members/export_members.xls" target="_blank">
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'members' | translate }}
|
||||
</a>
|
||||
<a class="btn btn-default" ng-href="api/members/export_subscriptions.xls" target="_blank" ng-if="!fablabWithoutPlans">
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'subscriptions' | translate }}
|
||||
</a>
|
||||
<a class="btn btn-default" ng-href="api/members/export_reservations.xls" target="_blank">
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'reservations' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='last_name', 'fa fa-sort-alpha-desc': member.order=='-last_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='first_name', 'fa fa-sort-alpha-desc': member.order=='-first_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='email', 'fa fa-sort-alpha-desc': member.order=='-email', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderMember('phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': member.order=='phone', 'fa fa-sort-numeric-desc': member.order=='-phone', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:20%"><a href="" ng-click="setOrderMember('group')">{{ 'user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='group', 'fa fa-sort-alpha-desc': member.order=='-group', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('plan')">{{ 'subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='plan', 'fa fa-sort-alpha-desc': member.order=='-plan', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="m in members">
|
||||
<td class="text-c">{{ m.profile.last_name }}</td>
|
||||
<td class="text-c">{{ m.profile.first_name }}</td>
|
||||
<td>{{ m.email }}</td>
|
||||
<td>{{ m.profile.phone }}</td>
|
||||
<td class="text-u-c text-sm">{{ m.group.name }}</td>
|
||||
<td>{{ m.subscribed_plan | humanReadablePlanName }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.admin.members_edit({id: m.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-warning" ng-click="showNextMembers()" ng-hide="member.noMore"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'display_more_users' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-include src="'<%= asset_path 'admin/members/members.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'administrators' | translate }}">
|
||||
<div class="col-md-5 m-t-lg">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'search_for_an_administrator' | translate }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" translate>{{ 'add_a_new_administrator' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="admin in admins | filter:searchFilter | orderBy: orderAdmin">
|
||||
<td class="text-c">{{ admin.profile_attributes.last_name }}</td>
|
||||
<td class="text-c">{{ admin.profile_attributes.first_name }}</td>
|
||||
<td>{{ admin.email }}</td>
|
||||
<td>{{ admin.profile_attributes.phone }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger" ng-if="admin.id != currentUser.id" ng-click="destroyAdmin(admins, admin)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ng-include src="'<%= asset_path 'admin/members/administrators.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'groups' | translate }}">
|
||||
@ -136,7 +36,6 @@
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'authentication' | translate }}">
|
||||
|
||||
<div ui-view="authentification"></div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
59
app/assets/templates/admin/members/members.html.erb
Normal file
59
app/assets/templates/admin/members/members.html.erb
Normal file
@ -0,0 +1,59 @@
|
||||
<div class="col-md-5 m-t-lg">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<input type="text" ng-model="member.searchText" class="form-control" placeholder="{{ 'search_for_an_user' | translate }}" ng-change="updateTextSearch()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.members_new" translate>{{ 'add_a_new_member' }}</button>
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-default" ng-href="api/members/export_members.xlsx" target="export-frame" ng-click="alertExport('members')">
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'members' | translate }}
|
||||
</a>
|
||||
<a class="btn btn-default" ng-href="api/members/export_subscriptions.xlsx" target="export-frame" ng-if="!fablabWithoutPlans" ng-click="alertExport('subscriptions')">
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'subscriptions' | translate }}
|
||||
</a>
|
||||
<a class="btn btn-default" ng-href="api/members/export_reservations.xlsx" target="export-frame" ng-click="alertExport('reservations')">
|
||||
<i class="fa fa-file-excel-o"></i> {{ 'reservations' | translate }}
|
||||
</a>
|
||||
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='last_name', 'fa fa-sort-alpha-desc': member.order=='-last_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='first_name', 'fa fa-sort-alpha-desc': member.order=='-first_name', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
<th style="width:15%" class="hidden-xs"><a href="" ng-click="setOrderMember('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='email', 'fa fa-sort-alpha-desc': member.order=='-email', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
<th style="width:10%" class="hidden-xs hidden-sm hidden-md"><a href="" ng-click="setOrderMember('phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': member.order=='phone', 'fa fa-sort-numeric-desc': member.order=='-phone', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
<th style="width:20%" class="hidden-xs hidden-sm"><a href="" ng-click="setOrderMember('group')">{{ 'user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='group', 'fa fa-sort-alpha-desc': member.order=='-group', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
<th style="width:15%" class="hidden-xs hidden-sm hidden-md"><a href="" ng-click="setOrderMember('plan')">{{ 'subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='plan', 'fa fa-sort-alpha-desc': member.order=='-plan', 'fa fa-arrows-v': member.order }"></i></a></th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="m in members">
|
||||
<td class="text-c">{{ m.profile.last_name }}</td>
|
||||
<td class="text-c">{{ m.profile.first_name }}</td>
|
||||
<td class="hidden-xs">{{ m.email }}</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md">{{ m.profile.phone }}</td>
|
||||
<td class="text-u-c text-sm hidden-xs hidden-sm">{{ m.group.name }}</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md">{{ m.subscribed_plan | humanReadablePlanName }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.admin.members_edit({id: m.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
<span class="label label-danger text-white" ng-show="m.need_completion" translate>{{ 'incomplete_profile' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-warning" ng-click="showNextMembers()" ng-hide="member.noMore"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'display_more_users' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
@ -47,7 +47,7 @@
|
||||
<tbody>
|
||||
|
||||
<tr ng-repeat="price in plan.prices">
|
||||
<td style="width: 60%;">{{ price.priceable_name }} (id {{ price.priceable_id }}) *</td>
|
||||
<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}">
|
||||
<span class="input-group-addon">{{currencySymbol}}</span>
|
||||
|
@ -23,7 +23,7 @@
|
||||
<ng-include src="'<%= asset_path 'admin/plans/_form.html' %>'"></ng-include>
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit" value="Enregistrer" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="planForm.$invalid || !partnerIsValid()"/>
|
||||
<input type="submit" value="{{ 'save' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="planForm.$invalid || !partnerIsValid()"/>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
27
app/assets/templates/admin/pricing/coupons.html.erb
Normal file
27
app/assets/templates/admin/pricing/coupons.html.erb
Normal file
@ -0,0 +1,27 @@
|
||||
<h2 translate>{{ '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>
|
||||
<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></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="coupon in coupons">
|
||||
<td>{{coupon.name}}</td>
|
||||
<td>{{coupon.percent_off}} %</td>
|
||||
<td>{{coupon.usages}}</td>
|
||||
<td translate>{{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>
|
||||
<button type="button" class="btn btn-danger" ng-click="deleteCoupon(coupons, coupon.id)"><i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
97
app/assets/templates/admin/pricing/credits.html.erb
Normal file
97
app/assets/templates/admin/pricing/credits.html.erb
Normal file
@ -0,0 +1,97 @@
|
||||
<h2 class="m-t-lg" translate>{{ '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%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr ng-repeat="(planId, trainingIds) in trainingCreditsGroups" ng-init="plan = getPlanFromId(planId)">
|
||||
<td>
|
||||
{{ plan | humanReadablePlanName: groups }}
|
||||
</td>
|
||||
<td>
|
||||
<span editable-text="plan.training_credit_nb" e-form="rowform" e-name="training_credits" e-required>
|
||||
{{ plan.training_credit_nb }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-checklist="trainingIds" e-form="rowform" e-name="training_ids" e-ng-options="t.id as t.name for t in trainings" e-required>
|
||||
{{ showTrainings(trainingIds) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form editable-form name="rowform" onbeforesave="saveTrainingCredits($data, planId)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == trainingIds">
|
||||
<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="cancelTrainingCredit(rowform)" 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>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="m-t-lg" translate>{{ '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>
|
||||
</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%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="mc in machineCredits">
|
||||
<td>
|
||||
<span editable-select="mc.creditable_id" e-name="creditable_id" e-form="rowform" e-ng-options="m.id as m.name+' ( id. '+m.id+' )' for m in machines" e-required>
|
||||
{{ showCreditableName(mc) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-number="mc.hours" e-name="hours" e-form="rowform" e-required>
|
||||
{{ mc.hours }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-select="mc.plan_id" e-ng-options="p.id as humanReadablePlanName(p, groups, 'short') for p in plans" e-name="plan_id" e-form="rowform">
|
||||
{{ getPlanFromId(mc.plan_id) | humanReadablePlanName: groups: 'short' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form editable-form name="rowform" onbeforesave="saveMachineCredit($data, mc.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == mc">
|
||||
<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="cancelMachineCredit(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="removeMachineCredit($index)">
|
||||
<i class="fa fa-trash-o"></i> {{ 'delete' | translate }} (!)
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -22,195 +22,23 @@
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'subscriptions' | translate }}">
|
||||
<h2 translate>{{ '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 }}
|
||||
</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>
|
||||
<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><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></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="plan in plans | orderBy:orderPlans">
|
||||
<td>{{getPlanType(plan.type)}}</td>
|
||||
<td>{{plan.base_name}}</td>
|
||||
<td>{{ plan.interval | planIntervalFilter:plan.interval_count }}</td>
|
||||
<td>{{getGroupFromId(groups, plan.group_id).name}}</td>
|
||||
<td>{{plan.ui_weight}}</td>
|
||||
<td>{{plan.amount | currency}}</td>
|
||||
<td><button type="button" class="btn btn-default" ui-sref="app.admin.plans.edit({id:plan.id})"><i class="fa fa-pencil-square-o"></i></button> <button type="button" class="btn btn-danger" ng-click="deletePlan(plans, plan.id)"><i class="fa fa-trash"></i></button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/subscriptions.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'trainings' | translate }}">
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'trainings' }}</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="training in trainings">
|
||||
<td>
|
||||
{{ training.name }}
|
||||
</td>
|
||||
<td ng-repeat="group in groups">
|
||||
<span editable-number="findTrainingsPricing(trainingsPricings, training.id, group.id).amount"
|
||||
onbeforesave="updateTrainingsPricing($data, findTrainingsPricing(trainingsPricings, training.id, group.id))">
|
||||
{{ findTrainingsPricing(trainingsPricings, training.id, group.id).amount | currency}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/trainings.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'machine_hours' | translate }}">
|
||||
<div class="alert alert-warning m-t">
|
||||
{{ 'these_prices_match_machine_hours_rates_' | translate }} <span class="font-bold" translate>{{ '_without_subscriptions' }}</span>.
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'machines' }}</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="machine in machines">
|
||||
<td>
|
||||
{{ machine.name }}
|
||||
</td>
|
||||
<td ng-repeat="group in groups">
|
||||
<span editable-number="findPriceBy(machinesPrices, machine.id, group.id).amount"
|
||||
onbeforesave="updatePrice($data, findPriceBy(machinesPrices, machine.id, group.id))">
|
||||
{{ findPriceBy(machinesPrices, machine.id, group.id).amount | currency}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/machine_hours.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'credits' | translate }}">
|
||||
<h2 class="m-t-lg" translate>{{ '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%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/credits.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<tr ng-repeat="(planId, trainingIds) in trainingCreditsGroups" ng-init="plan = getPlanFromId(planId)">
|
||||
<td>
|
||||
{{ plan | humanReadablePlanName: groups }}
|
||||
</td>
|
||||
<td>
|
||||
<span editable-text="plan.training_credit_nb" e-form="rowform" e-name="training_credits" e-required>
|
||||
{{ plan.training_credit_nb }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-checklist="trainingIds" e-form="rowform" e-name="training_ids" e-ng-options="t.id as t.name for t in trainings" e-required>
|
||||
{{ showTrainings(trainingIds) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form editable-form name="rowform" onbeforesave="saveTrainingCredits($data, planId)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == trainingIds">
|
||||
<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="cancelTrainingCredit(rowform)" 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>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="m-t-lg" translate>{{ '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>
|
||||
</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%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="mc in machineCredits">
|
||||
<td>
|
||||
<span editable-select="mc.creditable_id" e-name="creditable_id" e-form="rowform" e-ng-options="m.id as m.name+' ( id. '+m.id+' )' for m in machines" e-required>
|
||||
{{ showCreditableName(mc) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-number="mc.hours" e-name="hours" e-form="rowform" e-required>
|
||||
{{ mc.hours }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-select="mc.plan_id" e-ng-options="p.id as humanReadablePlanName(p, groups, 'short') for p in plans" e-name="plan_id" e-form="rowform">
|
||||
{{ getPlanFromId(mc.plan_id) | humanReadablePlanName: groups: 'short' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form editable-form name="rowform" onbeforesave="saveMachineCredit($data, mc.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == mc">
|
||||
<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="cancelMachineCredit(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="removeMachineCredit($index)">
|
||||
<i class="fa fa-trash-o"></i> {{ 'delete' | translate }} (!)
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<uib-tab heading="{{ 'coupons' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/pricing/coupons.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
|
26
app/assets/templates/admin/pricing/machine_hours.html.erb
Normal file
26
app/assets/templates/admin/pricing/machine_hours.html.erb
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="alert alert-warning m-t">
|
||||
{{ 'these_prices_match_machine_hours_rates_' | translate }} <span class="font-bold" translate>{{ '_without_subscriptions' }}</span>.
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'machines' }}</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="machine in machines">
|
||||
<td>
|
||||
{{ machine.name }}
|
||||
</td>
|
||||
<td ng-repeat="group in groups">
|
||||
<span editable-number="findPriceBy(machinesPrices, machine.id, group.id).amount"
|
||||
onbeforesave="updatePrice($data, findPriceBy(machinesPrices, machine.id, group.id))">
|
||||
{{ findPriceBy(machinesPrices, machine.id, group.id).amount | currency}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
30
app/assets/templates/admin/pricing/sendCoupon.html.erb
Normal file
30
app/assets/templates/admin/pricing/sendCoupon.html.erb
Normal file
@ -0,0 +1,30 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="text-center red" translate>{{ '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>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" ng-disabled="ctrl.member == null" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
33
app/assets/templates/admin/pricing/subscriptions.html.erb
Normal file
33
app/assets/templates/admin/pricing/subscriptions.html.erb
Normal file
@ -0,0 +1,33 @@
|
||||
<h2 translate>{{ '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 }}
|
||||
</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>
|
||||
<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></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="plan in plans | orderBy:orderPlans">
|
||||
<td>{{getPlanType(plan.type)}}</td>
|
||||
<td>{{plan.base_name}}</td>
|
||||
<td>{{ plan.interval | planIntervalFilter:plan.interval_count }}</td>
|
||||
<td>{{getGroupFromId(groups, plan.group_id).name}}</td>
|
||||
<td class="hidden-xs">{{plan.ui_weight}}</td>
|
||||
<td>{{plan.amount | currency}}</td>
|
||||
<td><button type="button" class="btn btn-default" ui-sref="app.admin.plans.edit({id:plan.id})"><i class="fa fa-pencil-square-o"></i></button> <button type="button" class="btn btn-danger" ng-click="deletePlan(plans, plan.id)"><i class="fa fa-trash"></i></button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
23
app/assets/templates/admin/pricing/trainings.html.erb
Normal file
23
app/assets/templates/admin/pricing/trainings.html.erb
Normal file
@ -0,0 +1,23 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%" translate>{{ 'trainings' }}</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="training in trainings">
|
||||
<td>
|
||||
{{ training.name }}
|
||||
</td>
|
||||
<td ng-repeat="group in groups">
|
||||
<span editable-number="findTrainingsPricing(trainingsPricings, training.id, group.id).amount"
|
||||
onbeforesave="updateTrainingsPricing($data, findTrainingsPricing(trainingsPricings, training.id, group.id))">
|
||||
{{ findTrainingsPricing(trainingsPricings, training.id, group.id).amount | currency}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -21,130 +21,13 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
<uib-tab heading="{{ 'materials' | translate }}">
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addComponent()" translate>{{ 'add_a_material' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="component in components">
|
||||
<td>
|
||||
<span editable-text="component.name" e-cols="100" e-name="name" e-form="rowform" e-required>
|
||||
{{ component.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveComponent($data, component.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == component">
|
||||
<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="cancelComponent(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeComponent($index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-include src="'<%= asset_path 'admin/project_elements/materials.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
<uib-tab heading="{{ 'themes' | translate }}">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTheme()" translate>{{ 'add_a_new_theme' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="theme in themes">
|
||||
<td>
|
||||
<span editable-text="theme.name" e-name="name" e-form="rowform" e-required>
|
||||
{{ theme.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveTheme($data, theme.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == theme">
|
||||
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelTheme(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeTheme($index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-include src="'<%= asset_path 'admin/project_elements/themes.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
<uib-tab heading="{{ 'licences' | translate }}">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addLicence()" translate>{{ 'add_a_new_licence' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30%" translate>{{ 'name' }}</th>
|
||||
<th style="width:50%" class="hidden-xs" translate>{{ 'description' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="licence in licences">
|
||||
<td>
|
||||
<span editable-textarea="licence.name" e-rows="5" e-cols="100" e-name="name" e-form="rowform" e-required>
|
||||
{{ licence.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="hidden-xs">
|
||||
<span editable-textarea="licence.description" e-rows="5" e-cols="100" e-name="description" e-form="rowform" e-required>
|
||||
<div class="text-sm">{{ licence.description }}</div>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveLicence($data, licence.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == licence">
|
||||
<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="cancelLicence(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeLicence($index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-include src="'<%= asset_path 'admin/project_elements/licences.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
|
@ -0,0 +1,44 @@
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addLicence()" translate>{{ 'add_a_new_licence' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:30%" translate>{{ 'name' }}</th>
|
||||
<th style="width:50%" class="hidden-xs" translate>{{ 'description' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="licence in licences">
|
||||
<td>
|
||||
<span editable-textarea="licence.name" e-rows="5" e-cols="100" e-name="name" e-form="rowform" e-required>
|
||||
{{ licence.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="hidden-xs">
|
||||
<span editable-textarea="licence.description" e-rows="5" e-cols="100" e-name="description" e-form="rowform" e-required>
|
||||
<div class="text-sm">{{ licence.description }}</div>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveLicence($data, licence.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == licence">
|
||||
<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="cancelLicence(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeLicence($index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -0,0 +1,38 @@
|
||||
<button type="button" class="btn btn-warning m-b m-t" ng-click="addComponent()" translate>{{ 'add_a_material' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="component in components">
|
||||
<td>
|
||||
<span editable-text="component.name" e-cols="100" e-name="name" e-form="rowform" e-required>
|
||||
{{ component.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveComponent($data, component.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == component">
|
||||
<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="cancelComponent(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeComponent($index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
38
app/assets/templates/admin/project_elements/themes.html.erb
Normal file
38
app/assets/templates/admin/project_elements/themes.html.erb
Normal file
@ -0,0 +1,38 @@
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTheme()" translate>{{ 'add_a_new_theme' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80%" translate>{{ 'name' }}</th>
|
||||
<th style="width:20%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="theme in themes">
|
||||
<td>
|
||||
<span editable-text="theme.name" e-name="name" e-form="rowform" e-required>
|
||||
{{ theme.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- form -->
|
||||
<form editable-form name="rowform" onbeforesave="saveTheme($data, theme.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == theme">
|
||||
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelTheme(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'edit' }}</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeTheme($index)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
35
app/assets/templates/admin/settings/about.html
Normal file
35
app/assets/templates/admin/settings/about.html
Normal file
@ -0,0 +1,35 @@
|
||||
<div class="panel panel-default m-t-md">
|
||||
<div class="panel-body">
|
||||
|
||||
<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>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutTitleSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"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>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutContactsSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
297
app/assets/templates/admin/settings/general.html
Normal file
297
app/assets/templates/admin/settings/general.html
Normal file
@ -0,0 +1,297 @@
|
||||
<div class="panel panel-default m-t-md">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ '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>
|
||||
<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 }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(fablabName)" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<form role="form" novalidate>
|
||||
<h4 class="control-label m-r" translate>{{ '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>
|
||||
<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>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(nameGenre)" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ '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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(subscriptionExplicationsAlert)" 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>
|
||||
</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' }}
|
||||
</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>
|
||||
<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">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{cgvFile.custom_asset_file_attributes.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"
|
||||
id="tnc_file"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
accept=".pdf"
|
||||
required />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" ng-class="{'btn-loading':loader.cgv}" ng-disabled="cgvForm.$invalid" class="btn btn-warning" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row m-t-xl">
|
||||
<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>
|
||||
<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">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{cguFile.custom_asset_file_attributes.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"
|
||||
id="tos_file"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
accept=".pdf"
|
||||
required />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" ng-class="{'btn-loading':loader.cgu}" ng-disabled="cguForm.$invalid" class="btn btn-warning" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ '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/>
|
||||
<br/>
|
||||
<span translate>{{ '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>
|
||||
<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">
|
||||
<img ng-src="{{customLogo.custom_asset_file_attributes.attachment_url}}" alt="{{customLogo.custom_asset_file_attributes.attachment}}" ng-show="customLogo && customLogo.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
ng-model="customLogo"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="logoForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<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>
|
||||
<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">
|
||||
<img ng-src="{{customLogoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{customLogoBlack.custom_asset_file_attributes.attachment}}" ng-show="customLogoBlack && customLogoBlack.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
ng-model="customLogoBlack"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="logoBlackForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<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>
|
||||
<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">
|
||||
<img ng-src="{{customFavicon.custom_asset_file_attributes.attachment_url}}" alt="{{customFavicon.custom_asset_file_attributes.attachment}}" ng-show="customFavicon && customFavicon.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_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]"
|
||||
ng-model="customFavicon"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="faviconForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-t m-l-xs">
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ '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}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button name="button" class="btn btn-warning" ng-click="save(mainColorSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ '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}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button name="button" class="btn btn-warning" ng-click="save(secondColorSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-t">
|
||||
<div class="col-md-4">
|
||||
<form class="custom-profile-image-container" method="post" action="{{actionUrl.profileImage}}" novalidate name="profileImageForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="profile-image-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.profileImage">
|
||||
<h3 class="m-l" translate>{{ 'background_picture_of_the_profile_banner' }}</h3>
|
||||
<div class="custom-profile-image" style="background-image: url({{profileImage}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!profileImage" class="img-responsive">
|
||||
<img base-sixty-four-image="profileImage" ng-show="profileImage && profileImage.base64">
|
||||
<img ng-src="{{profileImage.custom_asset_file_attributes.attachment_url}}" alt="{{profileImage.custom_asset_file_attributes.attachment}}" ng-show="profileImage && profileImage.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_profile_banner' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
ng-model="profileImage"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="profileImageForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
29
app/assets/templates/admin/settings/home_page.html
Normal file
29
app/assets/templates/admin/settings/home_page.html
Normal file
@ -0,0 +1,29 @@
|
||||
<div class="panel panel-default m-t-md">
|
||||
<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 }}",
|
||||
"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>
|
||||
<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>
|
||||
<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 }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button name="button" class="btn btn-warning" ng-click="save(twitterSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,491 +0,0 @@
|
||||
<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">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'customize_the_application' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="m-lg">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'general' | translate }}">
|
||||
<div class="panel panel-default m-t-md">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ '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>
|
||||
<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 }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(fablabName)" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
<form role="form" novalidate>
|
||||
<h4 class="control-label m-r" translate>{{ '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>
|
||||
<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>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(nameGenre)" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ '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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(subscriptionExplicationsAlert)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<h4 translate>{{ 'message_of_the_event_page_relative_to_the_reduced_rate_availability_conditions' }}</h4>
|
||||
<div ng-model="eventReducedAmountAlert.value" medium-editor options='{"placeholder": "{{ "type_the_message_content" | translate }}",
|
||||
"buttons": ["bold", "italic", "unorderedlist", "header2" ]
|
||||
}'>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(eventReducedAmountAlert)" 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>
|
||||
</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' }}
|
||||
</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="moveDelay" class="control-label m-r" translate>{{ '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">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{cgvFile.custom_asset_file_attributes.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="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
accept=".pdf"
|
||||
required />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" ng-class="{'btn-loading':loader.cgv}" ng-disabled="cgvForm.$invalid" class="btn btn-warning" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row m-t-xl">
|
||||
<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="moveDelay" class="control-label m-r" translate>{{ '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">
|
||||
<i class="glyphicon glyphicon-file fileinput-exists"></i> <span class="fileinput-filename">{{cguFile.custom_asset_file_attributes.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="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
accept=".pdf"
|
||||
required />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" ng-class="{'btn-loading':loader.cgu}" ng-disabled="cguForm.$invalid" class="btn btn-warning" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ '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/>
|
||||
<br/>
|
||||
<span translate>{{ '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>
|
||||
<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">
|
||||
<img ng-src="{{customLogo.custom_asset_file_attributes.attachment_url}}" alt="{{customLogo.custom_asset_file_attributes.attachment}}" ng-show="customLogo && customLogo.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
ng-model="customLogo"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="logoForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<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>
|
||||
<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">
|
||||
<img ng-src="{{customLogoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{customLogoBlack.custom_asset_file_attributes.attachment}}" ng-show="customLogoBlack && customLogoBlack.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_logo' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
ng-model="customLogoBlack"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="logoBlackForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<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>
|
||||
<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">
|
||||
<img ng-src="{{customFavicon.custom_asset_file_attributes.attachment_url}}" alt="{{customFavicon.custom_asset_file_attributes.attachment}}" ng-show="customFavicon && customFavicon.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_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]"
|
||||
ng-model="customFavicon"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="faviconForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-t m-l-xs">
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ '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}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button name="button" class="btn btn-warning" ng-click="save(mainColorSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 translate>{{ '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}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button name="button" class="btn btn-warning" ng-click="save(secondColorSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-t">
|
||||
<div class="col-md-4">
|
||||
<form class="custom-profile-image-container" method="post" action="{{actionUrl.profileImage}}" novalidate name="profileImageForm" ng-upload="submited(content)" upload-options-enable-rails-csrf="true" unsaved-warning-form>
|
||||
<input type="hidden" name="custom_asset[name]" value="profile-image-file">
|
||||
<input name="_method" type="hidden" ng-value="methods.profileImage">
|
||||
<h3 class="m-l" translate>{{ 'background_picture_of_the_profile_banner' }}</h3>
|
||||
<div class="custom-profile-image" style="background-image: url({{profileImage}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon-xs" bs-holder ng-show="!profileImage" class="img-responsive">
|
||||
<img base-sixty-four-image="profileImage" ng-show="profileImage && profileImage.base64">
|
||||
<img ng-src="{{profileImage.custom_asset_file_attributes.attachment_url}}" alt="{{profileImage.custom_asset_file_attributes.attachment}}" ng-show="profileImage && profileImage.custom_asset_file_attributes" />
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'change_the_profile_banner' | translate }}
|
||||
<input type="file"
|
||||
accept="image/png,image/x-png"
|
||||
name="custom_asset[custom_asset_file_attributes][attachment]"
|
||||
ng-model="profileImage"
|
||||
base-sixty-four-input
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" type="submit" class="btn btn-warning m-t m-l" ng-disabled="profileImageForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'home_page' | translate }}">
|
||||
<div class="panel panel-default m-t-md">
|
||||
<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 }}",
|
||||
"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>
|
||||
<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>
|
||||
<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 }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button name="button" class="btn btn-warning" ng-click="save(twitterSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'about' | translate }}">
|
||||
<div class="panel panel-default m-t-md">
|
||||
<div class="panel-body">
|
||||
|
||||
<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>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutTitleSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<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 }}",
|
||||
"buttons": ["bold", "italic", "anchor", "header1", "header2" ]
|
||||
}'>
|
||||
|
||||
</div>
|
||||
<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 }}",
|
||||
"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>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(aboutContactsSetting)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
<uib-tab heading="{{ 'reservations' | translate }}">
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'reservations_parameters' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'confine_the_booking_agenda' }}</h3>
|
||||
<div class="col-md-2">
|
||||
<h4 translate>{{ '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>
|
||||
<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">
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(windowEnd)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ '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>
|
||||
<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-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">
|
||||
<div class="col-md-4">
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="moveDelay" ng-model="moveDelay.value">
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(moveDelay)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ '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>
|
||||
<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-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">
|
||||
<div class="col-md-4">
|
||||
<label for="moveDelay" class="control-label m-r" translate>{{ 'prior_period_(hours)' }}</label>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="cancelDelay" ng-model="cancelDelay.value">
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(cancelDelay)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
41
app/assets/templates/admin/settings/index.html.erb
Normal file
41
app/assets/templates/admin/settings/index.html.erb
Normal file
@ -0,0 +1,41 @@
|
||||
<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">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'customize_the_application' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="m-lg">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
|
||||
<uib-tab heading="{{ 'general' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/general.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'home_page' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/home_page.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'about' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/about.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
<uib-tab heading="{{ 'reservations' | translate }}">
|
||||
<ng-include src="'<%= asset_path 'admin/settings/reservations.html' %>'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
124
app/assets/templates/admin/settings/reservations.html
Normal file
124
app/assets/templates/admin/settings/reservations.html
Normal file
@ -0,0 +1,124 @@
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'reservations_parameters' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ 'confine_the_booking_agenda' }}</h3>
|
||||
<div class="col-md-2">
|
||||
<h4 translate>{{ '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>
|
||||
<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">
|
||||
<button name="button" class="btn btn-warning m-l" ng-click="save(windowEnd)" translate>{{ 'save' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ '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>
|
||||
<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-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>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="moveDelay" ng-model="moveDelay.value" min="0" ng-required="enableMove.value">
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(moveDelay)" ng-disabled="moveDelayForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ '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>
|
||||
<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-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>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="cancelDelay" ng-model="cancelDelay.value" min="0" ng-required="enableCancel.value">
|
||||
</div>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(cancelDelay)" ng-disabled="cancelDelayForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default m-t-lg">
|
||||
<div class="panel-heading">
|
||||
<span class="font-sbold" translate>{{ 'reservations_reminders' }}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<h3 class="m-l" translate>{{ '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>
|
||||
<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-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>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<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 }}
|
||||
</span>
|
||||
</div>
|
||||
<button name="button" class="btn btn-warning" ng-click="save(reminderDelay)" ng-disabled="reminderDelayForm.$invalid" translate>{{ 'save' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
76
app/assets/templates/admin/statistics/export.html.erb
Normal file
76
app/assets/templates/admin/statistics/export.html.erb
Normal file
@ -0,0 +1,76 @@
|
||||
<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>{{ 'export_statistics_to_excel' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="radio">
|
||||
<label><input type="radio" name="scope" ng-model="export.type" value="global" ng-change="setRequest()">{{ 'export_all_statistics' | translate }}</label>
|
||||
</div>
|
||||
<div ng-show="export.type == 'global'">
|
||||
<ul class="list-unstyled">
|
||||
<li class="row">
|
||||
<span class="col-md-4" translate>{{ 'start' }}</span>
|
||||
<div class="input-group black col-md-7 m-r" id="date_pick_start">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
uib-datepicker-popup="{{exportEnd.format}}"
|
||||
ng-model="dates.start"
|
||||
name="startDate"
|
||||
is-open="exportStart.opened"
|
||||
min-date="exportStart.minDate"
|
||||
max-date="exportStart.maxDate"
|
||||
datepicker-options="exportStart.options"
|
||||
show-button-bar="false"
|
||||
placeholder="{{ 'start' | translate }}"
|
||||
ng-click="toggleStartDatePicker($event)"
|
||||
ng-change="setRequest()"
|
||||
required="required"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default btn-search-datepicker" ng-click="toggleStartDatePicker($event)">
|
||||
<i class="glyphicon glyphicon-calendar"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="row">
|
||||
<span class="col-md-4" translate>{{ 'end' }}</span>
|
||||
<div class="input-group black col-md-7 m-r" id="date_pick_end">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
uib-datepicker-popup="{{exportEnd.format}}"
|
||||
ng-model="dates.end"
|
||||
name="endDate"
|
||||
is-open="exportEnd.opened"
|
||||
min-date="exportEnd.minDate"
|
||||
max-date="exportEnd.maxDate"
|
||||
datepicker-options="datePickerEnd.options"
|
||||
show-button-bar="false"
|
||||
placeholder="{{ 'end' | translate }}"
|
||||
ng-click="toggleEndDatePicker($event)"
|
||||
ng-change="setRequest()"
|
||||
required="required"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default btn-search-datepicker" ng-click="toggleEndDatePicker($event)">
|
||||
<i class="glyphicon glyphicon-calendar"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label><input type="radio" name="scope" ng-model="export.type" value="current" ng-change="setRequest()">{{ 'export_the_current_search_results' | translate }}</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form role="form" ng-submit="exportData()" name="exportForm" method="post" action="{{ actionUrl }}" class="inline">
|
||||
<input name="authenticity_token" type="hidden" ng-value="csrfToken"/>
|
||||
<input name="_method" type="hidden" ng-value="method"/>
|
||||
<input name="type_key" type="hidden" ng-value="typeKey"/>
|
||||
<input name="body" type="hidden" ng-value="query"/>
|
||||
<input type="submit" class="btn btn-info" value="{{ 'export' | translate }}" formtarget="export-frame"/>
|
||||
</form>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
@ -12,6 +12,8 @@
|
||||
</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-default rounded m-t-sm text-sm" ng-click="exportToExcel()"><i class="fa fa-file-excel-o"></i></a>
|
||||
<iframe name="export-frame" height="0" width="0" class="none" id="stats-export-frame"></iframe>
|
||||
<a class="btn btn-lg btn-warning bg-white b-2x rounded m-t-sm upper text-sm" ui-sref="app.admin.stats_graphs" role="button"><i class="fa fa-line-chart"></i> {{ 'evolution' | translate }}</a>
|
||||
</section>
|
||||
</div>
|
||||
@ -26,7 +28,7 @@
|
||||
<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)">
|
||||
<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">
|
||||
<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">
|
||||
<i class="fa fa-birthday-cake"></i>
|
||||
<span ng-show="agePicker.start || agePicker.end">
|
||||
@ -67,7 +69,7 @@
|
||||
<select ng-model="type.selected" ng-options="type.label for type in stat.types" class="form-control"> </select>
|
||||
</div>
|
||||
|
||||
<div id="customFilterPane" class="form-group datepicker-container m-l-md">
|
||||
<div id="customFilterPane" class="form-group datepicker-container m-l-md" style="z-index:101;">
|
||||
<button id="customFilterExpand" class="btn btn-default customMenuButton" type="button" ng-click="customFilter.show = !customFilter.show">
|
||||
<i class="fa fa-filter"></i>
|
||||
<span ng-show="!customFilter.criterion.key" class="text-gray" translate>{{ 'custom_filter' }}</span>
|
||||
@ -170,7 +172,7 @@
|
||||
<i class="fa fa-caret-up" ng-show="datePicker.show"></i>
|
||||
</button>
|
||||
<div class="datepicker-dropdown" ng-show="datePicker.show">
|
||||
<ul class="list-unstyled">
|
||||
<ul class="list-unstyled p-xs">
|
||||
<li class="row">
|
||||
<span class="col-md-4" translate>{{ 'start' }}</span>
|
||||
<div class="input-group black col-md-7 m-r" id="date_pick_start">
|
||||
@ -233,6 +235,7 @@
|
||||
<li ng-show="selectedIndex.ca">{{ 'revenue_' | translate }} {{sumCA | currency}}</li>
|
||||
<li>{{ 'average_age' | translate }} {{averageAge}} {{ 'years_old' | translate }}</li>
|
||||
<li ng-if="!type.active.simple">{{ 'total' | translate }} {{type.active.label}} : {{sumStat}}</li>
|
||||
<li ng-repeat="custom in type.active.custom_aggregations">{{ custom.field | translate }} {{customAggs[custom.field]}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -287,4 +290,3 @@
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
122
app/assets/templates/admin/trainings/_form.html.erb
Normal file
122
app/assets/templates/admin/trainings/_form.html.erb
Normal file
@ -0,0 +1,122 @@
|
||||
<form role="form"
|
||||
name="trainingForm"
|
||||
class="form-horizontal"
|
||||
ng-attr-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">
|
||||
|
||||
<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': trainingForm['training[name]'].$dirty && trainingForm['training[name]'].$invalid}">
|
||||
<label for="name" class="col-sm-2 control-label">{{ 'name' | translate }} *</label>
|
||||
<div class="col-sm-4">
|
||||
<input name="training[name]"
|
||||
ng-model="training.name"
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="training_name"
|
||||
placeholder="{{'name' | translate}}"
|
||||
required/>
|
||||
<span class="help-block" ng-show="trainingForm['training[name]'].$dirty && trainingForm['training[name]'].$error.required" translate>{{ 'name_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-lg">
|
||||
<label for="training_image" class="col-sm-2 control-label">{{ 'illustration' | translate }} *</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="fileinput" data-provides="fileinput" ng-class="fileinputClass(training.training_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="!training.training_image">
|
||||
</div>
|
||||
<div class="fileinput-preview fileinput-exists thumbnail" style="max-width: 334px;">
|
||||
<img ng-src="{{ training.training_image }}" alt="" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="btn btn-default btn-file">
|
||||
<span class="fileinput-new">{{ 'add_an_illustration' | translate }} <i class="fa fa-upload fa-fw"></i></span>
|
||||
<span class="fileinput-exists" translate>{{ 'change' }}</span>
|
||||
<input type="file"
|
||||
ng-model="training.training_image"
|
||||
name="training[training_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': trainingForm['training[description]'].$dirty && trainingForm['training[description]'].$invalid}">
|
||||
<label for="training_description" class="col-sm-2 control-label">{{ 'description' | translate }} *</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="hidden" name="training[description]" ng-value="training.description" />
|
||||
<summernote ng-model="training.description" id="training_description" placeholder="" config="summernoteOpts" name="training[description]" required></summernote>
|
||||
<span class="help-block" ng-show="trainingForm['training[description]'].$dirty && trainingForm['training[description]'].$error.required" translate>{{ 'description_is_required' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-lg" ng-class="{'has-error': trainingForm['training[machine_ids]'].$dirty && trainingForm['training[machine_ids]'].$invalid}">
|
||||
<label for="training_machines" class="col-sm-2 control-label">{{ 'associated_machines' | translate }}</label>
|
||||
<div class="col-sm-4">
|
||||
<ui-select multiple ng-model="training.machine_ids" class="form-control" id="training_machines">
|
||||
<ui-select-match>
|
||||
<span ng-bind="$item.name"></span>
|
||||
<input type="hidden" name="training[machine_ids][]" value="{{$item.id}}" />
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="m.id as m in (machines | filter: $select.search)">
|
||||
<span ng-bind-html="m.name | highlight: $select.search"></span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-b-lg" ng-class="{'has-error': trainingForm['training[nb_total_places]'].$dirty && trainingForm['training[nb_total_places]'].$invalid}">
|
||||
<label for="training_nb_total_places" class="col-sm-2 control-label">{{ 'number_of_tickets' | translate }}</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="training.nb_total_places"
|
||||
type="number"
|
||||
min="0"
|
||||
name="training[nb_total_places]"
|
||||
class="form-control"
|
||||
id="training_nb_total_places">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="training[public_page]" class="control-label col-sm-2" translate>
|
||||
{{ 'public_page' }}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input bs-switch
|
||||
ng-model="training.public_page"
|
||||
name="training[public_page]"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<input type="hidden" name="training[public_page]" value="{{training.public_page}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- ./panel-body -->
|
||||
|
||||
<div class="panel-footer no-padder">
|
||||
<input type="submit"
|
||||
value="{{ 'validate_your_training' | translate }}"
|
||||
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
|
||||
ng-disabled="trainingForm.$invalid"/>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
27
app/assets/templates/admin/trainings/edit.html.erb
Normal file
27
app/assets/templates/admin/trainings/edit.html.erb
Normal file
@ -0,0 +1,27 @@
|
||||
<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 ng-click="cancel()"><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>{{ 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">
|
||||
<div class="btn btn-lg btn-block btn-default rounded m-t-xs" ng-click="cancel()" translate>{{ 'cancel' }}</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter">
|
||||
<div class="col-sm-12 col-md-12 col-lg-9 b-r-lg nopadding">
|
||||
<ng-include src="'<%= asset_path 'admin/trainings/_form.html' %>'"></ng-include>
|
||||
</div>
|
||||
</div>
|
@ -21,11 +21,7 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true">
|
||||
<uib-tab heading="{{ 'trainings' | translate }}">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="addTraining()" translate>{{ 'add_a_new_training' }}</button>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{{ 'beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero' | translate }}
|
||||
{{ 'dont_forget_to_change_them_before_creating_slots_for_this_training' | translate }}
|
||||
</div>
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.trainings_new" translate>{{ 'add_a_new_training' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
@ -38,35 +34,12 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="training in trainings">
|
||||
<td>{{ training.name }}</td>
|
||||
<td>{{ showMachines(training) }}</td>
|
||||
<td>{{ training.nb_total_places }}</td>
|
||||
<td>
|
||||
<span editable-text="training.name" e-name="name" e-form="rowform" e-required>
|
||||
{{ training.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-checklist="training.machine_ids" e-ng-options="m.id as m.name for m in machines" e-name="machine_ids" e-form="rowform">
|
||||
{{ showMachines(training) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-number="training.nb_total_places" e-name="nb_total_places" e-form="rowform" e-required>
|
||||
{{ training.nb_total_places }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form editable-form name="rowform" onbeforesave="saveTraining($data, training.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == training">
|
||||
<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="cancelTraining(rowform, $index)" class="btn btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="buttons" ng-show="!rowform.$visible">
|
||||
<button ng-click="openModalToSetDescription(training)" class="btn btn-default">
|
||||
<i class="fa fa-comment-o"></i>
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.admin.trainings_edit({id:training.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-click="removeTraining($index, training)">
|
||||
|
35
app/assets/templates/admin/trainings/new.html.erb
Normal file
35
app/assets/templates/admin/trainings/new.html.erb
Normal file
@ -0,0 +1,35 @@
|
||||
<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="cancel()"><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>{{ 'add_a_new_training' }}</h1>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter" >
|
||||
|
||||
<div class="col-md-9 b-r nopadding">
|
||||
|
||||
<div class="alert alert-warning m-lg" role="alert">
|
||||
{{ 'beware_when_creating_a_training_its_reservation_prices_are_initialized_to_zero' | translate }}
|
||||
{{ 'dont_forget_to_change_them_before_creating_slots_for_this_training' | translate }}
|
||||
</div>
|
||||
|
||||
<ng-include src="'<%= asset_path 'admin/trainings/_form.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<!-- <button class="btn">TEST</button> -->
|
||||
</div>
|
||||
</div>
|
62
app/assets/templates/calendar/calendar.html.erb
Normal file
62
app/assets/templates/calendar/calendar.html.erb
Normal file
@ -0,0 +1,62 @@
|
||||
<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 hide-b-r-lg">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ '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 }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="row no-gutter">
|
||||
<div class="hidden-lg">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-9">
|
||||
<div ui-calendar="calendarConfig" ng-model="eventSources" calendar="calendar" class="wrapper-lg public-calendar"></div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<div class="widget-content no-bg auto wrapper calendar-filter">
|
||||
<ng-include src="'<%= asset_path 'calendar/filter.html' %>'"></ng-include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<script type="text/ng-template" id="filterAside.html">
|
||||
<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>
|
||||
</div>
|
||||
<div class="modal-body widget-content calendar-filter calendar-filter-aside">
|
||||
<ng-include src="'<%= asset_path 'calendar/filter.html' %>'"></ng-include>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user