Commit 8a840026 by Arturo Montejo Ráez

Merge branch 'issue611' into develop

parents b7139260 7b224042
......@@ -140,6 +140,7 @@ CREATE TABLE IF NOT EXISTS `office` (
`name` varchar(80) COLLATE utf8_unicode_ci NOT NULL,
`logo_url` varchar(240) COLLATE utf8_unicode_ci NOT NULL,
`address` varchar(180) COLLATE utf8_unicode_ci NOT NULL,
`postal_code` char(10) COLLATE utf8_unicode_ci NOT NULL,
`country` varchar(2) COLLATE utf8_unicode_ci NOT NULL,
`lang` varchar(5) COLLATE utf8_unicode_ci NOT NULL,
`contact_person` varchar(80) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Contact person, main responsible',
......@@ -308,6 +309,7 @@ CREATE TABLE IF NOT EXISTS `license` (
`number` varchar(16) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`creation_ts` datetime DEFAULT CURRENT_TIMESTAMP,
`activation_ts` datetime NULL,
`expiration_ts` datetime NULL,
`duration` int(11) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `number` (`number`),
......@@ -532,7 +534,7 @@ ALTER TABLE `student`
-- Filtros para la tabla `license`
--
ALTER TABLE `license`
ADD CONSTRAINT `license_fk_1` FOREIGN KEY (`id_stu`) REFERENCES `student` (`id`);
ADD CONSTRAINT `license_fk_1` FOREIGN KEY (`id_stu`) REFERENCES `student` (`id`) ON DELETE CASCADE;
--
-- Filtros para la tabla `supervisor`
--
......
......@@ -13,7 +13,8 @@ INSERT IGNORE INTO `office` (
`contact_person`,
`email`,
`phone1`,
`lang`
`lang`,
`postal_code`
) VALUES (
'Asociación Provincial de Autismo de Jaén',
'Avd. de Andalucía, 92 - bajo, 23006 Jaén',
......@@ -21,7 +22,8 @@ INSERT IGNORE INTO `office` (
'Belén Pérez Vílchez',
'belen.perez@autismojaen.es',
'+34 953 236 158',
'es-es'
'es-es',
'23006'
);
--
......
......@@ -14,7 +14,8 @@ INSERT INTO `office` (
`contact_person`,
`email`,
`phone1`,
`lang`
`lang`,
`postal_code`
) VALUES (
'Comunicación Aumentativa JAén (CAJA)',
'Paraje Las Lagunillas, Ed A3, primera plata, 23071. Jaén',
......@@ -22,7 +23,8 @@ INSERT INTO `office` (
'Fernando Martínez Santiago',
'dofer@ujaen.es',
'+34 953 21 28 88',
'es-es'
'es-es',
'23071'
);
--
......
......@@ -5,7 +5,8 @@ INSERT IGNORE INTO `office` (
`contact_person`,
`email`,
`phone1`,
`lang`
`lang`,
`postal_code`
) VALUES (
'Centro Destrezas',
'Avd. Eduardo García Maroto, 22, 1º Centro - Jaén',
......@@ -13,7 +14,8 @@ INSERT IGNORE INTO `office` (
'Centro Destrezas',
'centrodestrezas@gmail.com',
'+34 953 043 508',
'es-es'
'es-es',
'23007'
);
INSERT IGNORE INTO `supervisor` (
......
/* global Instruction, Method */
/**
* LicenseController manages the requests related to the License model.
* Read it's documentation for further information.
* @type {Object}
*/
module.exports = {
/**
* Create a new Instruction, which is associated to the given method
* @param {request} req
* {
* duration (number of months)
* }
* @param {response} res
* {
* code (instructionId)
* method
* name
* objective
* }
*/
create: function (req, res) {
var params = req.allParams();
function get_new_random_license (callback) {
function random_license () {
var length = 16;
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var result = '';
for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
var license;
var found = true;
var maxtries = 10;
async.doWhilst(
function (cb) {
license = random_license();
License.findOne({number: license})
.then((l) => {
if (!l)
found = false;
cb();
})
.catch((err) => {
found = false;
cb();
});
},
function () {
return found;
},
function () {
callback(license);
}
);
}
if (!params.duration || params.duration < 0)
return res.badRequest();
get_new_random_license(function (license) {
License.create({
number: license,
duration: params.duration
})
.then((l) => {
if (l) {
delete l.id;
return res.ok(l);
}
return res.badRequest();
})
.catch((err) => {
return res.serverError(err);
});
});
},
/**
* Activate an existing License
* @param {request} req (with license number as parameter ID)
* {
* id_stu: id of the student
* }
* @param {response} res
*/
activate: function (req, res) {
var params = req.allParams();
if (!params.number)
return res.badRequest();
License.activate(req.license, req.body.id_stu, function(err, license) {
if (err)
return res.badRequest(err);
return res.ok(license);
});
}
};
......@@ -462,7 +462,7 @@ module.exports = {
}
Supervisor.students(req.params.id, function (err, stus) {
if (err) throw err;
return res.json(stus);
return res.ok(stus);
});
},
......
/**
* License.js
*
* @description :: TODO: Write a short summary of how this model works and what it represents here.
* @docs :: http://sailsjs.org/#!documentation/models
*/
module.exports = {
tableName : 'license',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
attributes: {
id: {
type: "integer",
autoIncrement: true,
primaryKey: true,
unique: true
},
student: { // FK de Student. 1 a N
columnName: "id_stu",
type: "integer",
model: "Student",
unique: true
},
creation_ts: {
columnName: "creation_ts",
type: "datetime"
},
activation_ts: {
columnName: "activation_ts",
type: "datetime"
},
duration: {
columnName: "duration",
type: "integer"
},
expiration_ts: {
columnName: "expiration_ts",
type: "datetime"
},
number: {
type: "string",
columnName: "number",
size: 16,
unique: true
},
toJSON: function () {
var l = this.toObject();
delete l.id;
return l;
},
hasExpired: function () {
return (new Date(this.expiration_ts) - new Date() < 0);
}
},
/**
Class methods
*/
/**
Activates a license
@param {number} number License number
@param {ID} id_stu ID of the student the license is associated to
@param {function} callback Callback function with prototype: function(err, license)
*/
activate: function(number, id_stu, callback) {
// Check license
License.findOne({ number: number })
.then((l) => {
if (!l)
throw new Error("Invalid license: " + number);
if (l.activation_ts)
throw new Error("License in use");
// License ok, check student
Student.findOne(id_stu)
.then((s) => {
if (!s)
throw new Error("Student not found");
// Check if the student has a previous license
License.findOne({ student: s.id })
.then((l_old) => {
var left = 0;
if (l_old) {
// He had a license, if not expired, get remaining time
left = l_old.expiration_ts - new Date();
left = left < 0 ? 0 : left;
License.destroy({id: l_old.id}).exec((err) => {
if (err) throw err;
});
}
// Compute parameters for license to activate
var now = new Date();
l.student = s.id;
l.activation_ts = now;
l.expiration_ts = new Date(new Date(now.getFullYear(), now.getMonth()+l.duration, now.getDate()+1).getTime() + left);
l.save((err) => {
if (err)
throw err;
delete l.student;
delete l.id;
callback(null, l);
});
});
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
callback(err, null);
});
},
/**
* Determines whether a license is activable (available) or not
* Callback function gets instantiated error if not available
*/
isActivable: function(number, cb) {
License.findOne({number: number})
.then ((l) => {
if (!l)
throw new Error('Invalid license');
if (l.activation_ts)
throw new Error('License in use');
cb();
})
.catch((err) => {cb(err)});
}
}
......@@ -88,11 +88,16 @@ module.exports = {
collection: 'stupicto',
via: 'student'
},
// Relación con VStuLastInstruction [1 Student to 1 StuPicto]
// Relación con VStuLastInstruction [1 Student to 1 VStuLastInstruction]
lastInstruction: {
collection: 'vstulastinstruction',
via: 'student'
},
// Relación con licencias
license: {
collection: 'license',
via: 'student'
},
/**
* This "extra" property allow us to adapt the server to the student needs
......@@ -109,6 +114,8 @@ module.exports = {
toJSON: function () {
var student = this.toObject();
student.pic = sails.config.pictogram.urls.getStudentAvatarUrl(student.pic);
if (student.license)
student.license = student.license[0] ? student.license[0] : null;
student.attributes = Student.getValidAttributes(student.attributes);
delete student.password;
return student;
......@@ -238,6 +245,7 @@ module.exports = {
beforeCreate: function (attrs, next) {
attrs.attributes = Student.getValidAttributes(attrs.attributes);
attrs.password = bcrypt.hashSync(attrs.password, bcrypt.genSaltSync());
attrs.pic = sails.config.pictogram.urls.getStudentAvatarUrl();
next();
},
......@@ -249,7 +257,6 @@ module.exports = {
* to the function if necesary)
*/
beforeUpdate: function (attrs, next) {
delete attrs.username;
attrs.attributes = Student.getValidAttributes(attrs.attributes);
if (attrs.password) {
sails.log.debug('password changed');
......@@ -506,13 +513,14 @@ module.exports = {
logical_delete: function(id_stu, cb) {
Student.findOne(id_stu).exec(function(err, student) {
if (err || !student)
throw err;
student.office = null;
student.save(function(err, saved) {
if (err) return cb(err);
return cb();
});
return cb(new Error("Unable to remove student"));
Student.update(id_stu,
{
username: Math.floor((Math.random() * 100000000) + 1) + "_" + student.username,
id_off: null
})
.then((updated) => {cb()})
.catch((err) => {cb(err)});
});
}
};
......@@ -209,6 +209,7 @@ module.exports = {
},
students: function(id, callback) {
var l = [];
Supervisor.findOne(id)
.populate('office')
......@@ -236,37 +237,45 @@ module.exports = {
if (stuSup.student.office == null)
next_cb();
var student = stuSup.student;
student.supervision = sup.office ? 2 : 1;
// set current methdo and instruction if any
student.current_method = "no_method";
student.current_instruction = "no_instruction";
VStuLastInstruction.findOne({student: student.id})
.then(function (stu_last_inst) {
if (stu_last_inst) {
student.current_method = stu_last_inst.met_name;
student.current_instruction = stu_last_inst.ins_name;
// set current method and instruction if any
Student.findOne(stuSup.student.id)
.populate('lastInstruction')
.populate('license')
.then(function (s) {
s.current_method = s.lastInstruction[0] ? s.lastInstruction[0].met_name : "no_method";
s.current_instruction = s.lastInstruction[0] ? s.lastInstruction[0].ins_name : "no_instruction";
if (typeof(s.license[0]) != 'undefined') {
s.licenseIsValid = new Date(s.license[0].expiration_ts) - new Date() > 0 ? true : false;
s.license = s.license[0];
} else {
s.licenseIsValid = false;
s.license = null;
}
l.push(student);
next_cb();
})
.error(err => {
l.push(student);
s.supervision = sup.office ? 2 : 1; // if Supervisor has office, then is a therapist (2), a tutor (1) otherwise
l.push(s);
next_cb();
});
},
function (err) { // loop has end
// Get all students from the office if user is administrator
if (sup.office && sup.office.admin == sup.id) {
var officeStudents;
var officeStudents;
Student.find({ office: sup.office.id }).populate('lastInstruction')
Student.find({ office: sup.office.id })
.populate('lastInstruction')
.populate('license')
.then(function (officeStudents) {
officeStudents = officeStudents.map((student) => {
student.supervision = student.supervision || 0;
student.supervision = 0;
student.current_method = student.lastInstruction[0] ? student.lastInstruction[0].met_name : "no_method";
student.current_instruction = student.lastInstruction[0] ? student.lastInstruction[0].ins_name : "no_instruction";
if (typeof(student.license[0]) != 'undefined') {
student.licenseIsValid = new Date(student.license[0].expiration_ts) - new Date() > 0 ? true : false;
student.license = student.license[0];
} else {
student.licenseIsValid = false;
student.license = null;
}
return student;
});
l = l.concat(officeStudents);
......@@ -275,9 +284,9 @@ module.exports = {
.catch(function (err) {
callback(err, l);
});
} else {
callback(err, l);
}
} else {
callback(err, l);
}
}); // end async.eachSeries
})
.catch((err) => {
......
/* global sails, Student */
module.exports = function isSupervisorOfStudentOrIsSupAdmin(req, res, next) {
const supervisorId = req.token.id;
const studentId = req.params.id_stu;
if (!studentId || !supervisorId) {
sails.log.error('This request needs an id_stu parameter and a authenticated supervisor');
res.json(401, { error: 'Access denied' });
} else {
Student.findOne(studentId)
.then(function (s) {
if (s.office == req.token.office.id && req.token.isSupAdmin) {
next();
}
else {
Student.supervisors(studentId, function (err, sups) {
const studentSupervisorsIds = sups.map((studentSupervisor) => studentSupervisor.id);
if (err || studentSupervisorsIds.length === 0) {
sails.log.error(`Student ${studentId} has no supervisor assigned`);
res.json(401, { error: 'Access denied' });
} else if (studentSupervisorsIds.indexOf(supervisorId) < 0) {
sails.log.error(`Supervisor ${supervisorId} is not assigned to Student ${studentId}`);
sails.log.debug(`Student supervisors: ${studentSupervisorsIds}`);
res.json(401, { error: 'Access denied' });
} else {
sails.log.debug(`Supervisor ${supervisorId} is assigned to Student ${studentId}`);
next();
}
});
}
});
}
};
......@@ -24,7 +24,7 @@ module.exports = function serverError (data, options) {
// Log error to console
if (data !== undefined) {
sails.log.error('Sending 500 ("Server Error") response: \n',data);
sails.log.error('Sending 500 ("Server Error") response: \n', JSON.stringify(data));
}
else sails.log.error('Sending empty 500 ("Server Error") response');
......@@ -74,4 +74,3 @@ module.exports = function serverError (data, options) {
});
};
......@@ -27,7 +27,9 @@
"angular-animate": "~1.4.1",
"angular-sanitize": "~1.4.1",
"angular-chart.js": "latest",
"ng-lodash": "~0.3.0"
"ng-lodash": "~0.3.0",
"bootstrap-filestyle": "~1.2.1",
"ngMask": "angular-mask#~3.1.1"
},
"resolutions": {
"angular": ">=1 <1.3.0",
......
......@@ -57,6 +57,7 @@
"country": "Country",
"create_account": "Create account",
"create_an_account": "Create an account",
"credentials": "Credentials",
"crop_image": "Crop image",
"data_no_saved": "Data can't be saved",
"data_saved": "Data saved",
......@@ -72,6 +73,7 @@
"disabled": "Disabled. Clic for invisible",
"double_click": "Double click",
"down": "Down",
"duration_in_months": "Duration (in number of months)",
"drag": "Drag",
"edit": "Edit",
"email": "Email address",
......@@ -92,11 +94,12 @@
"error_only_support_images": "Only images are supported (JPG, PNG or GIF files)",
"error_on_request": "The request has not been processed. Please, check your fields",
"error_loading_pictos": "Error loading pictos information",
"error_general": "An error has been produced",
"expand_navigation": "Expand navigation",
"expand_navigation": "Expand navigation",
"expression": "Expression:",
"February": "February",
"feedback_picto": "Feedback when a pictogram is placed",
"feedback_picto": "Selection feedback",
"filter": "Filter",
"finish_session": "Finish session",
"finished": "Finished",
......@@ -124,15 +127,23 @@
"June": "June",
"language": "Language",
"large": "Large",
"large_picto": "Large pictograms",
"last_session": "Last session",
"legend": "Legend",
"legend_apply_all":"Apply to all pictograms",
"legend_none":"No legend",
"legend_normal":"Normal legend",
"legend_full":"Only legend",
"legend_size": "Legend size",
"legend_apply_all":"Apply to all pictograms",
"licenses": "Licenses (max. number of students)",
"licenses": "Licenses",
"license_already_activated": "License already activated",
"license_created": "License created",
"license_expires": "License expires on ",
"license_expired": "License expired on ",
"license_invalid": "Invalid license number",
"licenses_left": "{{number}} licenses left",
"license_missing": "Account without license",
"license_number": "License number",
"light_up": "Light up",
"link": "Link",
"loading_pictos": "Loading pictos",
......@@ -219,6 +230,7 @@
"press": "Long press",
"previous_actions": "Previous actions",
"previous_sessions": "Previous sessions",
"profile_picture": "Profile picture",
"read_picto": "Read picto",
"register": "Sign in",
"remember": "Remember me",
......@@ -250,6 +262,7 @@
"show": "Show",
"size": "Size",
"small": "Small",
"small_picto": "Small pictograms",
"sound_setup": "Sound setup",
"spanish": "Spanish",
"started": "Started",
......@@ -281,11 +294,12 @@
"supervisor_not_updated": "Supervisor not updated",
"supervisor_note": "If the parent aren't going to register in the platform, the administrator can use the notes field to store their information.",
"supervisor_updated": "Supervisor updated",
"supervisors": "Therapist",
"supervisors": "Supervisors",
"surname": "Surname",
"tag_deleted": "Tag deleted",
"tape_background": "Tape background",
"template_deleted": "Template deleted",
"therapists": "Therapists",
"time_hours": "Time: {{hours}} hours",
"time_instruction_method": "Time instructions of method",
"time_sessions_total": "Total sessions time",
......
......@@ -57,6 +57,7 @@
"country": "País",
"create_account": "Crear cuenta",
"create_an_account": "Crear una cuenta",
"credentials": "Credenciales",
"crop_image": "Recortar imagen",
"data_no_saved": "Los datos no se han podido guardar",
"data_saved": "Datos guardados",
......@@ -72,6 +73,7 @@
"disabled": "Desactivado. Clic para invisible",
"double_click": "Doble clic",
"down": "Abajo",
"duration_in_months": "Duración (en número de meses)",
"drag": "Arrastrar",
"edit": "Editar",
"email": "Correo electrónico",
......@@ -95,8 +97,9 @@
"error_only_support_images": "Sólo se soportan imágenes (ficheros JPG, PNG o GIF)",
"error_on_request": "Se ha producido un error. Por favor, compruebe los valores introducidos.",
"error_loading_pictos": "Error cargando información de los pictos",
"error_general": "Se ha producido un error",
"February": "Febrero",
"feedback_picto": "Feedback al colocar un pictograma",
"feedback_picto": "Efecto de selección",
"filter": "Filtrar",
"finish_session": "Terminar sesión",
"finished": "Completada",
......@@ -124,6 +127,7 @@
"June": "Junio",
"language": "Idioma",
"large": "Grande",
"large_picto": "Pictograms grandes",
"last_session": "Última sesión",
"legend": "Leyenda",
"legend_none":"Sin leyenda",
......@@ -131,8 +135,15 @@
"legend_full":"Sólo leyenda",
"legend_apply_all":"Aplicar a todos los pictogramas",
"legend_size": "Tamaño de la leyenda",
"licenses": "Licencias (número máximo de estudiantes)",
"licenses": "Licencias",
"licenses_left": "{{number}} licencias disponibles",
"license_already_activated": "Licencia ya activada previamente",
"license_expires": "La licencia expira el ",
"license_expired": "La licencia expiró el ",
"license_created": "Licencia creada",
"license_invalid": "Licencia inválida",
"license_number": "Número de licencia",
"license_missing": "Cuenta sin licencia",
"light_up": "Iluminar",
"link": "Vincular",
"loading_pictos": "Cargando pictos",
......@@ -219,6 +230,7 @@
"press": "Pulsación larga",
"previous_actions": "Acciones anteriores",
"previous_sessions": "Sesiones anteriores",
"profile_picture": "Foto del perfil",
"read_picto": "Leer picto",
"register": "Regístrate",
"register_button": "Registrar",
......@@ -251,6 +263,7 @@
"show": "Mostrar",
"size": "Tamaño",
"small": "Pequeño",
"small_picto": "Pictogramas pequeños",
"sound_setup": "Ajustes de sonido",
"spanish": "Español",
"started": "Iniciada",
......@@ -282,7 +295,8 @@
"supervisor_not_updated": "El supervisor no se ha podido actualizar",
"supervisor_note": "Si los padres no se van a dar de alta en la plataforma nunca, el administrador puede anotar la información de contacto en el campo notas.",
"supervisor_updated": "Supervisor actualizado",
"supervisors": "Terapeutas",
"therapists": "Terapeutas",
"supervisors": "Supervisores",
"surname": "Apellidos",
"tag_deleted": "Etiqueta borrada",
"tape_background": "Fondo de la cinta",
......
......@@ -17,7 +17,8 @@ var dashboardApp = angular.module('dashboardApp', [
'ngSanitize',
'ngToast',
'chart.js',
'ngLodash'
'ngLodash',
'ngMask'
]);
/* Main constants */
......@@ -149,14 +150,20 @@ dashboardApp.config(function ($stateProvider, $urlRouterProvider) {
controller: 'AdminCtrl',
abstract: true,
})
.state('licenses', {
url: '/admin/licenses',
parent: 'admin',
templateUrl: 'modules/admin/views/licenses.html',
controller: 'AdminLicensesCtrl',
})
.state('offices', {
url: '/offices',
url: '/admin/offices',
parent: 'admin',
templateUrl: 'modules/admin/views/offices.html',
controller: 'AdminOfficesCtrl',
})
.state('supervisors', {
url: '/supervisors',
url: '/admin/supervisors',
parent: 'admin',
templateUrl: 'modules/admin/views/supervisors.html',
controller: 'AdminSupervisorsCtrl',
......
'use strict';
//--------------------------
// Admin License Controller
//--------------------------
dashboardControllers.controller('AdminLicensesCtrl', function AdminLicensesCtrl($scope, $window, $http, config, $translate, ngToast) {
// The parameter 'config' is injected from config.js (defined in dashboardConfig module)
// Don't show the message at the begining
$scope.showmessagesupervisor = false;
$scope.formdatalicense = {
duration: ''
};
// This generates a new license and registers it in the database
$scope.create_license = function(supervisor){
$http
.post(config.backend+'/license', $scope.formdatalicense)
.success(function(data, status, headers, config) {
$scope.formdatalicense.duration = '';
$scope.new_number = data.number.substr(0,4) +
"-" + data.number.substr(4,4) +
"-" + data.number.substr(8,4) +
"-" + data.number.substr(12,4);
console.log($scope.new_number);
$scope.duration_registered = data.duration;
})
.error(function(data, status, headers, config) {
ngToast.danger({content: $translate.instant('error_general')});
console.log("Error from API: " + data.error);
});
};
});
......@@ -4,15 +4,15 @@
<header-admin></header-admin>
<!-- Tab menu -->
<ul class="nav nav-pills nav-justified" ng-init="selectedTab = 'supervisors'">
<li role="presentation" ng-click="selectedTab = 'supervisors'" ng-class="{'active':selectedTab === 'supervisors'}">
<a href="#supervisors">{{ 'supervisors' | translate }}</a>
<ul class="nav nav-pills nav-justified" ng-init="selectedTab = 'licenses'">
<li role="presentation" ng-click="selectedTab = 'licenses'" ng-class="{'active':selectedTab === 'licenses'}">
<a href="/app/#/admin/licenses">{{ 'licenses' | translate }}</a>
</li>
<li role="presentation" ng-click="selectedTab = 'offices'" ng-class="{'active':selectedTab === 'offices'}">
<a href="#offices">{{ 'offices' | translate }}</a>
<a href="/app/#/admin/offices">{{ 'offices' | translate }}</a>
</li>
<li role="presentation" ng-click="selectedTab = 'supervisors'" ng-class="{'active':selectedTab === 'supervisors'}">
<a href="#supervisors">{{ 'supervisors' | translate }}</a>
<a href="/app/#/admin/supervisors">{{ 'supervisors' | translate }}</a>
</li>
</ul>
......
<!-- Admin Licenses -->
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-4">
<h3 translate>licenses</h3>
<form role="form" ng-submit="create_license()">
<div class="form-group">
<input type="number" class="form-control" id="setup_duration" placeholder="{{ 'duration_in_months' | translate }}" required ng-model="formdatalicense.duration" required/>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" translate>generate</button>
</div>
</form>
</div>
<div class="col-md-4">
<div ng-show="new_number.length > 16" class="alert alert-info">
<p>{{ 'license_created' | translate }}: <strong>{{ new_number }}</strong></p>
<p>{{ 'duration_in_months' | translate }}: {{ duration_registered }}</p>
</div>
</div>
<div class="col-md-2">
</div>
</div>
......@@ -17,8 +17,9 @@ dashboardControllers.controller('LoginAdminCtrl', function LoginAdminCtrl($scope
.success(function(data, status, headers, config) {
// Save token and user data y sessionStorage
$window.sessionStorage.token = data.token;
// Redirect to admin panel
$location.path('/devices');
$location.path('/admin/licenses');
$translate('login_success').then(function(translation) {
ngToast.success({ content: translation });
......
......@@ -17,6 +17,14 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// For tab navigation (here too, if the user refresh the page...)
$scope.nav.tab = 'setup';
// Set upload button to filestyle
$(":file").filestyle({
buttonText: "&nbsp;" + $translate.instant('change_picture'),
input: false
});
$scope.supsForm = {};
/**
* Updates the student picture
* @param {Angular file array} $files Image to be uploaded
......@@ -82,14 +90,11 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
if ($scope.formUser.password_confirm === $scope.formUser.password) {
password = $scope.formUser.password;
} else {
$translate('password_match').then(function (translation) {
ngToast.danger({ content: translation });
});
ngToast.danger({ content: $translate.instant('password_match') });
return;
}
}
attrs = {
birthdate: $scope.formUser.birthdate,
country: $scope.formUser.country,
......@@ -99,16 +104,17 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
notes: $scope.formUser.notes,
surname: $scope.formUser.surname,
username: $scope.formUser.username,
license_number: $scope.formUser.license_number
};
if (password)
attrs.password = password;
// Update student data
$http.put(config.backend + '/stu/' + $scope.studentData.id, attrs)
.success(function (data) {
$translate('student_updated').then(function (translation) {
ngToast.success({ content: translation });
});
ngToast.success({ content: $translate.instant('student_updated') });
$scope.formUser.birthdate = data.birthdate;
$scope.formUser.country = data.country;
$scope.formUser.gender = data.gender;
......@@ -117,17 +123,25 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
$scope.formUser.notes = data.notes;
$scope.formUser.surname = data.surname;
$scope.formUser.username = data.username;
$scope.formUser.license_number = data.license ? data.license.number : '';
$scope.studentData.license = data.license ? data.license : null;
$scope.updateLicenseExpiration();
// websocket emit vocabulary delete action
// websocket emit update action
delete data.license;
io.socket.post('/stu/config', {
action: 'update',
attributes: data
}, function () {});
})
.error(function () {
$translate.danger('student_not_updated', function (translation) {
ngToast.danger({ content: translation });
});
.error(function (err) {
console.log(err);
if (err.message.search('nvalid license'))
ngToast.danger({ content: $translate.instant('license_invalid') });
else if (err.message.search('in use'))
ngToast.danger({ content: $translate.instant('license_already_activated') });
else
ngToast.danger({ content: $translate.instant('student_not_updated') });
});
};
......@@ -136,8 +150,9 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
* The email used for search is fetched from $scope.email_sup.
*/
$scope.search_sup = function () {
console.log("--> " + $scope.supsForm.email_sup);
// Find tutor by email
$http.get(config.backend + '/sup/email/' + $scope.email_sup)
$http.get(config.backend + '/sup/email/' + $scope.supsForm.email_sup)
.success(function (data) {
if (data) {
$scope.supToAdd = data;
......@@ -173,7 +188,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// Add to the list of tutors in view
$scope.studentSupervisors.push($scope.supToAdd);
// Delete the email form field
$scope.email_sup = '';
$scope.supsForm.email_sup = '';
// Hide the message of supervisor founded
$scope.showmessagesupfound = false;
})
......@@ -214,7 +229,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// Search tutor by email
$scope.search_tutor = function () {
// Find tutor by email
$http.get(config.backend + '/sup/email/' + $scope.email_tutor)
$http.get(config.backend + '/sup/email/' + $scope.supsForm.email_tutor)
.success(function (data) {
// If it found the length is > 0
if (data) {
......@@ -252,7 +267,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// Add to the list of tutors in view
$scope.studentTutors.push($scope.tutorToAdd);
// Delete the email form field
$scope.email_tutor = '';
$scope.supsForm.email_tutor = '';
// Hide the message of tutor founded
$scope.showmessagetutorfound = false;
......
......@@ -28,6 +28,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
lang: '',
notes: '',
attributes: {},
license: {
number: ''
},
office: {
id: '',
name: ''
......@@ -74,6 +77,15 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
});
});
$scope.updateLicenseExpiration = function () {
if (!$scope.studentData.license)
return;
$scope.studentData.license_expired = new Date($scope.studentData.license.expiration_ts) - new Date() < 0;
moment.locale($translate.use().substr(0, 2));
$scope.studentData.expiration_date = moment($scope.studentData.license.expiration_ts).format('L');
};
// ----------------------------------------------------------------------
//
// Load student account information
......@@ -99,6 +111,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
$scope.studentData.current_method = data.current_method;
$scope.studentData.current_instruction = data.current_instruction;
$scope.studentData.supervision = data.supervision; // supervision level on student: 0->admin, 1->tutor, 2->therapist
$scope.studentData.license = data.license;
$scope.updateLicenseExpiration();
// Setup section: Fill formUser (data able to be modified) from studentData parent object
// It must go here to assign the values when studentData is recovered
......@@ -111,6 +126,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
$scope.formUser.gender = $scope.studentData.gender;
$scope.formUser.lang = $scope.studentData.lang;
$scope.formUser.notes = $scope.studentData.notes;
if ($scope.studentData.license) {
$scope.formUser.license_number = $scope.studentData.license.number;
}
// Subscribe to student's socket room
io.socket.post('/stu/subscribe', {
......
......@@ -20,11 +20,10 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
name: '',
surname: '',
birthdate: '',
country: '',
gender: 'F',
country: 'ES',
gender: 'M',
lang: 'es-es',
notes: '',
pic: 'defaultAvatar.jpg',
office: $scope.user.office || { name: '' }
};
......@@ -39,15 +38,6 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
console.log("currentStudents: " + $scope.user.office.currentStudents);
console.log("maxStudents: " + $scope.user.office.maxStudents);
// Compute number of licenses left
if ($scope.user.office.currentStudents >= $scope.user.office.maxStudents) {
$scope.num_licenses_left = 0;
} else {
$scope.num_licenses_left =
$scope.user.office.maxStudents -
$scope.user.office.currentStudents;
}
} else {
$scope.user.office = { name: '' };
}
......@@ -74,13 +64,14 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
name: '',
surname: '',
birthdate: '',
country: '',
gender: 'F',
country: 'ES',
gender: 'M',
lang: 'es-es',
notes: '',
office: $scope.user.office || { name: '' },
current_method: 'no_method',
current_instruction: 'no_instruction'
current_instruction: 'no_instruction',
license_number: ''
};
// Hide the form
......@@ -113,34 +104,26 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
data.supervision = 0; // by default, only related to office administrator
data.current_method = $translate.instant('no_method');
data.current_instruction = $translate.instant('no_instruction');
data.licenseIsValid = new Date(data.license.expiration_ts) - new Date() > 0 ? true : false;
// Add to the list of students in view
$scope.students.push(data);
// Delete the fields of the form to avoid data binding
// between the new element created and the form fields
$scope.resetForm();
// Hide the add form to new adding
$scope.hidestudentadd = true;
// Update counters
$scope.user.office.currentStudents += 1;
$scope.num_licenses_left -= 1;
})
.error(function (err) {
var errorMessage = 'student_not_added';
console.log(err);
if (typeof err == "string" && err.search("Maximum number of enrolments reached") > 0)
if (err.message && err.message.search('nvalid license') > 0)
errorMessage = 'license_invalid';
else if (err.message && err.message.search('in use') > 0)
errorMessage = 'license_already_activated';
else if (typeof err == "string" && err.search("Maximum number of enrolments reached") > 0)
errorMessage = 'max_licenses_reached';
if (typeof err == "string" && err.search("already exists") > 0)
errorMessage = 'student_already_exists';
else if (typeof err == "string" && err.search("already exists") > 0)
errorMessage = 'student_already_exists';
else if (err && err.status === 400)
errorMessage = 'invalid_fields';
ngToast.danger({ content: $translate.instant(errorMessage) });
$scope.hidestudentadd = true;
});
};
......@@ -148,32 +131,25 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
* Delete Student
*/
$scope.delete_student = function (student) {
$translate('confirmation').then(t => {
if ($window.confirm(t))
$http.delete(config.backend + '/stu/' + student.id)
.success(function () {
var i;
for (i = 0; i < $scope.students.length; i++) {
if (student.id === $scope.students[i].id) {
$scope.students.splice(i, 1);
}
if ($window.confirm($translate.instant('confirmation'))) {
$http.delete(config.backend + '/stu/' + student.id)
.success(function () {
var i;
for (i = 0; i < $scope.students.length; i++) {
if (student.id === $scope.students[i].id) {
$scope.students.splice(i, 1);
}
$translate('student_deleted').then(function (translation) {
ngToast.success({ content: translation });
});
IOService.post('/stu/unsubscribe', {
action: 'unsubscribe'
});
// Update counters
$scope.user.office.currentStudents -= 1;
$scope.num_licenses_left += 1;
})
.error(function () {
ngToast.danger({ content: $translate.instant('student_not_deleted') });
}
ngToast.success({ content: $translate.instant('student_deleted') });
IOService.post('/stu/unsubscribe', {
action: 'unsubscribe'
});
});
})
.error(function () {
ngToast.danger({ content: $translate.instant('student_not_deleted') });
});
}
};
// When a new student is added to the supervisor, we should update
......
......@@ -28,6 +28,9 @@
<!-- Fin .panel-body -->
<!-- Table -->
<div ng-show="students.length == 0">
{{ no_students_for_user | translate }}
</div>
<table id="table_students" class="table table-hover">
<tr ng-repeat="student in students | filter:search_students | orderBy: ['surname', 'name']">
<td>
......@@ -36,6 +39,9 @@
</div>
</td>
<td>
<span ng-show="!student.licenseIsValid" class="license-warning text-danger glyphicon glyphicon-exclamation-sign" aria-hidden="true" popover="{{ 'license_invalid' | translate}}" popover-trigger="mouseenter"></span>
</td>
<td>
<h4>{{student.surname}}, {{student.name}}</h4>
</td>
<td>
......@@ -43,7 +49,6 @@
</td>
<td> <!-- BUTTONS -->
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/collections" alt="{{ 'collections' | translate}}" popover="{{ 'collections' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 0"><span class="glyphicon glyphicon-th" aria-hidden="true"></span></a>
<span class="btn btn-default btn-lg" role="button" alt="{{ 'collections' | translate}}" popover="{{ 'collections' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 0"><span class="glyphicon glyphicon-th" style="color: #bbb" aria-hidden="true"></span></span>
......@@ -65,7 +70,7 @@
</td> <!-- /BUTTONS -->
<td>
<a ng-click="delete_student(student)" class="delete_stu" title="{{ 'delete' | translate}}">
<a ng-if="user.isSupAdmin" ng-click="delete_student(student)" class="delete_stu" title="{{ 'delete' | translate}}">
<span class="color_red glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
</a>
</td>
......
......@@ -18,61 +18,65 @@
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdatastudent.password_confirm" />
</div>
<legend translate>personal_data</legend>
<div class="form-group">
<input type="text" class="form-control" id="student_name" placeholder="{{ 'name' | translate }}" required ng-model="formdatastudent.name" />
{{ 'language' | translate }}: <select class="form-control" name="student_language" id="student_language" ng-model="formdatastudent.lang">
<option value="es-es" selected>Español</option>
<option value="en-us">English</option>
</select>
</div>
<div class="form-group">
<input type="text" class="form-control" id="student_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdatastudent.surname" />
<input type="text" id="student_license" mask="9999-9999-9999-9999" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdatastudent.license_number" required>
</div>
<div class="form-group">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="{{ 'day_format' | translate }}" ng-model="formdatastudent.birthdate" placeholder="{{ 'birthdate' | translate }}" is-open="opened_cal_student" close-text="{{'close' | translate}}" required />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open_calendar($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
<div class="form-group">
<select class="form-control" name="student_gender" id="student_gender" ng-model="formdatastudent.gender" required>
<option value="F" selected translate>woman</option>
<option value="M" translate>man</option>
</select>
</div>
<div class="form-group">
<legend translate>country</legend>
<select class="form-control" name="student_country" id="student_country" ng-model="formdatastudent.country" required>
<option value="ES" selected>España</option>
<option value="US">United States</option>
<option value="UK">United Kingdom</option>
<option value="IE">Ireland</option>
</select>
</div>
</fieldset>
</div>
<div class="col-sm-6">
<fieldset>
<legend translate>language</legend>
<div class="form-group">
<select class="form-control" name="student_language" id="student_language" ng-model="formdatastudent.lang">
<option value="es-es" selected>Español</option>
<option value="en-us">English</option>
</select>
</div>
</fieldset>
<fieldset>
<legend translate>notes</legend>
<div class="form-group">
<textarea class="form-control" name="student_notes" id="student_notes" ng-model="formdatastudent.notes" rows="5"></textarea>
<legend translate>personal_data</legend>
<div class="form-group">
<input type="text" class="form-control" id="student_name" placeholder="{{ 'name' | translate }}" required ng-model="formdatastudent.name" />
</div>
<div class="form-group">
<input type="text" class="form-control" id="student_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdatastudent.surname" />
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="{{ 'day_format' | translate }}" ng-model="formdatastudent.birthdate" placeholder="{{ 'birthdate' | translate }}" is-open="opened_cal_student" close-text="{{'close' | translate}}" required />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open_calendar($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div>
</fieldset>
<div class="col-sm-6">
<div class="form-group">
<select class="form-control" name="student_gender" id="student_gender" ng-model="formdatastudent.gender" required>
<option value="F" selected translate>woman</option>
<option value="M" translate>man</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<span translate>country</span>:
<select class="form-control" name="student_country" id="student_country" ng-model="formdatastudent.country" required>
<option value="ES" selected>España</option>
<option value="US">United States</option>
<option value="UK">United Kingdom</option>
<option value="IE">Ireland</option>
</select>
</div>
</div>
</div>
<!-- Fin de row -->
......
......@@ -774,8 +774,8 @@ img.profile{
margin-top: 4px;
}
.delete_stu{
font-size: 24px;
.delete_stu, .license-warning{
font-size: 18px;
margin-top: 10px;
}
......
......@@ -7,7 +7,7 @@ var UPLOAD_PATH = path.join(__dirname, '..', '..', 'upload');
module.exports.pictogram = {
admin: {
email: 'amontejo@ujaen.es',
password: '$2a$10$omHwM62JNI5O1hbkfFGncOYwq3mZATmh8NnTQhN3f7JV3Q1/S9fGG'
password: '$2a$06$flEEOc15SerMeYWARrN9w.KSpJuM.jDsaTgrtD0ESzbxKHPl0f/zq' //y00ttaa!!
},
serialSize: 10, // number of characters in generated serial numbers
pageLimit: 10, // number of elements per "page"
......@@ -33,7 +33,7 @@ module.exports.pictogram = {
*/
getStudentAvatarUrl: function (filename) {
if (!filename)
filename = sails.config.pictogram.paths.deafultAvatarFileName;
filename = sails.config.pictogram.paths.defaultAvatarFileName;
return `/upload/studentAvatar/${filename}`;
},
......
......@@ -83,12 +83,12 @@ module.exports.policies = {
StudentController: {
eternal: true,
getInfo: ['tokenAuth'],
getInfo: ['tokenAuth', 'isSupervisorOfStudentOrIsSupAdmin'],
supervisors: ['tokenAuth'],
therapists: ['tokenAuth'],
tutors: ['tokenAuth'],
link_supervisor: ['tokenAuth'],
pictos: ['tokenAuth'],
link_supervisor: ['tokenAuth', 'isSupAdmin'],
pictos: ['tokenAuth', 'isSupervisorOfStudent'],
methods: ['tokenAuth'],
lasttries: ['tokenAuth'],
tries: ['tokenAuth'],
......@@ -98,7 +98,7 @@ module.exports.policies = {
update_picto: ['tokenAuth', 'isSupervisorOfStudent'],
update_legend: ['tokenAuth'],
login: true,
create: ['tokenAuth'],
create: ['tokenAuth', 'isSupAdmin'],
upload: ['tokenAuth'],
add_picto: ['tokenAuth', 'isSupervisorOfStudent'],
subscribe: ['tokenAuth'],
......@@ -107,16 +107,16 @@ module.exports.policies = {
action: true,
config: true,
actions_batch: ['tokenAuth'],
delete: ['tokenAuth'],
unlink_supervisor: ['tokenAuth'],
delete: ['tokenAuth', 'isSupAdmin'],
unlink_supervisor: ['tokenAuth', 'isSupAdmin'],
delete_picto: ['tokenAuth', 'isSupervisorOfStudent']
},
LicenseController: {
/* create: ['tokenAuth', 'isAdmin'],
activate: ['tokenAuth']*/
create: true,
activate: true
// create: ['tokenAuth', 'isAdmin'],
// activate: ['tokenAuth']
create: true,
activate: true
},
SupervisorController: {
......
......@@ -25,6 +25,8 @@ module.exports = function (grunt) {
'assets/app/bower_components/angular-chart.js/dist/angular-chart.min.js',
'assets/app/bower_components/ng-lodash/build/ng-lodash.js',
'assets/app/bower_components/ng-file-upload/angular-file-upload-shim.js',
'assets/app/bower_components/bootstrap-filestyle/src/bootstrap-filestyle.min.js',
'assets/app/bower_components/ngMask/dist/ngMask.min.js',
'assets/scripts/lib/sails.io.js'
];
......@@ -39,6 +41,7 @@ module.exports = function (grunt) {
'assets/scripts/modules/login/controllers/signin.js',
'assets/scripts/modules/login/controllers/login_admin.js',
'assets/scripts/modules/admin/controllers/admin.js',
'assets/scripts/modules/admin/controllers/licenses.js',
'assets/scripts/modules/admin/controllers/offices.js',
'assets/scripts/modules/admin/controllers/supervisors.js',
'assets/scripts/modules/supervisor/controllers/supervisor.js',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment