new student creation system implemented, not tested

parent 919dcf1c
......@@ -15,14 +15,15 @@ ALTER TABLE student MODIFY gender CHAR(1) DEFAULT NULL;
ALTER TABLE student MODIFY country CHAR(2) DEFAULT NULL;
ALTER TABLE student MODIFY lang VARCHAR(5) DEFAULT NULL;
ALTER TABLE license ADD type enum('trial', 'official') NOT NULL DEFAULT 'official';
CREATE TABLE IF NOT EXISTS sup_off (
id_sup int(11) NOT NULL,
id_sup int(11) NOT NULL,ALTER TABLE license ADD type enum('trial', 'official') NOT NULL DEFAULT 'official';
id_off int(11) DEFAULT NULL,
CONSTRAINT fk_sup_off FOREIGN KEY (id_sup) REFERENCES supervisor (id),
CONSTRAINT fk_off_sup FOREIGN KEY (id_off) REFERENCES supervisor (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
UPDATE supervisor
SET role = 'office'
WHERE id IN (SELECT admin FROM office WHERE admin IS NOT NULL);
......@@ -35,6 +36,10 @@ UPDATE supervisor
SET role = 'tutor'
WHERE id_off IS NULL;
UPDATE license
SET type = 'official'
WHERE type is not 'trial';
DELIMITER $$
DROP PROCEDURE IF EXISTS supervisor_adapt $$
CREATE PROCEDURE supervisor_adapt()
......
......@@ -43,46 +43,10 @@ module.exports = {
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.genLicenseNumber(function (license) {
License.create({
number: license,
duration: params.duration,
......
......@@ -190,31 +190,73 @@ module.exports = {
});
},
//
// Adds a new student into the database
//
/**
* Adds a new student into the database
* It inmediatly related to the supervisor who created it
* If no license is specified, a test one will be created
* @param {request} req {
* username,
* password,
* id_sup,
* lang
* license (optional)
* }
*/
create: function (req, res) {
var params = req.params.all();
License.isActivable(params.license_number, function(err) {
if (err)
return res.serverError(err);
var params = req.allParams();
Student.create(params)
.then(function(created) {
sails.log.debug('Student ' + created.id + ' created: ' + JSON.stringify(created));
License.activate(params.license_number, created.id, function(err, license) {
//
// License number, if passed is used, otherwise, a trial one is generated
//
Promise(function(resolve, reject) {
if (params.license)
resolve(params.license);
else {
License.newTrial(params.id_sup, function(license, err) {
if (err)
return res.serverError(err);
created = created.toJSON();
created.license = license.toObject();
return res.ok(created);
reject(err);
else
resolve(license.number);
});
}
})
.then((license) => {
// Check license
License.isActivable(params.license, function(err) {
if (err)
return res.serverError(err);
// Create student
Student.create(params)
.then(function(created) {
sails.log.debug('Student ' + created.id + ' created: ' + JSON.stringify(created));
// Activate license
License.activate(params.license, created.id, function(err, license) {
if (err)
return res.serverError(err);
created = created.toJSON();
created.license = license.toObject();
// Link to supervisor
StuSup.create({id_stu: created.id, id_sup: params.id_sup})
.then((stu_sup) => {
if (!stu_sup)
throw new Error("Unable to link to supervisor");
return res.ok(created);
})
.catch((err) => {throw err});
})
.catch((err) => {throw err});
})
.catch(function(err) {
sails.log.debug(err.message);
return res.serverError(err.message);
});
})
.error(function(err) {
sails.log.debug(err.message);
return res.serverError(err.message);
});
});
})
.catch((err) => {res.serverError()};
},
/**
......@@ -409,71 +451,95 @@ module.exports = {
* Creates a relation between the student and a given supervisor.
* It broadcasts the event linkSupervisorToStudent to both the student room
* and the supervisor room.
* @param {request} { (with id_stu and id_sup as url parameters)
* asTherapist: true/false (optional) // assigns supervisor to student's office is true, set id_off to null otherwise
* @param {request} { (with id_stu and id_sup as url parameters or with license and id_sup)
* }
* @param {response} {}
*/
link_supervisor: function (req, res) {
StuSup.create({
student: req.param('id_stu'),
supervisor: req.param('id_sup')
})
.then(function (stuSup) {
if (!stuSup)
throw new Error('stusup not created');
const socketToOmit = (req.isSocket) ? req.socket : undefined;
const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
stuSup.supervisor,
stuSup.student
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.supervisor(stuSup.supervisor),
linkSupervisorToStudentEvent,
socketToOmit
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stuSup.student),
linkSupervisorToStudentEvent,
socketToOmit
);
var params = req.allParams();
return stuSup;
// Check input parameters coherence
if (!params.id_sup || !params.id_stu && !params.license)
return res.badRequest("Invalid params");
//
// Get student ID either because we got it or through a license
// As it can be resolved through two possibilities, better to user promises!
//
Promise(funcion(reject, resolve) {
if (!params.id_stu && params.license) {
// get student, license attribute will be completed
License.getStudent(params.license, function(err, stu) {
if (err)
reject(err);
else
resolve(stu);
});
} else
Student.findOne(params.id_stu)
.populate('license')
.then((stu) => {
if (!stu)
throw new Error("Student not found");
resolve(stu);
})
.catch((err) => {reject(err)});
})
.catch((err) => {
StuSup.findOne({ student: req.param('id_stu'), supervisor: req.param('id_sup') })
.then((stu) => {
//
// Let's check it is not already linked
//
StuSup.findOne({ student: stu.id, supervisor: params.id_sup })
.then((stuSup) => {
// It was already there!
if (stuSup)
return stuSup;
else
throw err;
});
})
.then((stuSup) => {
// update supervisor office if it is linked as therapist
Supervisor.findOne({id: req.param('id_sup')})
.then((sup) => {
if (sup && !sup.office) {
Student.findOne({id: req.param('id_stu')})
.then((stu) => {
if (stu) {
if (req.body.asTherapist)
sup.office = stu.office;
else
sup.office = null;
delete sup.password;
sup.save();
}
});
throw new Error("Already linked");
//
// Non existing relation, let's create it!
//
else {
StuSup.create({
student: id_stu,
supervisor: params.id_sup
})
.then(function (stuSup) {
if (!stuSup)
throw new Error('stusup not created');
//
// Send sockets messages
//
const socketToOmit = (req.isSocket) ? req.socket : undefined;
const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
stuSup.supervisor,
stuSup.student
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.supervisor(stuSup.supervisor),
linkSupervisorToStudentEvent,
socketToOmit
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stuSup.student),
linkSupervisorToStudentEvent,
socketToOmit
);
//
// Done!
//
return res.ok(stu);
}
});
return res.ok();
})
.catch((err) => {throw err});
})
.catch((err) => {
return res.serverError("Error: " + err);
res.serverError(err);
});
},
......
......@@ -48,6 +48,11 @@ module.exports = {
size: 16,
unique: true
},
type: {
type: "string",
enum: ['trial', 'official'],
columnName: 'type'
},
creator: {
columnName: "creator",
type: "string",
......@@ -66,6 +71,105 @@ module.exports = {
*/
/**
* Generates a new trial license
* @param {ID} id_sup ID of the supervisor who creates the license
* @param {function} callback Callback function: license, err
*/
newTrial: function(id_sup, callback) {
var now = new Date();
License.genLicenseNumber(function(number) {
if (!number)
callback(null, new Error("Unable to generate new license"));
License.create({
type: 'trial',
creation_ts: now,
duration: sails.config.pictogram.trial_license_duration,
number: number
})
.then((l) => {
if (!l) throw new Error("Unable to create license");
callback(l, null);
})
.catch((err) => {
callback(null, err);
});
});
},
/**
* Generates a random license number (checking that is not duplicated)
* @param {function} callback Prototype: function(license) returns the create number
*/
genLicenseNumber: function(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);
}
);
}
/**
* Get the student associated to the license
* If the license is not active or doesn't exist, and error is returned
* @param {number} number License number
* @param {function} callback Callback function: err, student
*/
getStudent: function(number, callback) {
License.findOne({ number: number })
.then((l) => {
if (!l || !l.activation_ts || !l.id_stu)
throw new Error("Invalid license: " + number);
// License ok, check student
Student.findOne(l.id_stu)
.then((s) => {
if (!s)
throw new Error("Student not found");
s.license = l;
callback(null, s);
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
callback(err, null);
});
},
/**
Activates a license
@param {number} number License number
@param {ID} id_stu ID of the student the license is associated to
......
......@@ -31,21 +31,17 @@ module.exports = {
size: 40
},
name: {
required: true,
type: 'string',
size: 40
},
surname: {
required: true,
type: 'string',
size: 60
},
birthdate: {
required: true,
type: 'date'
},
gender: {
required: true,
type: 'string',
size: 1
},
......@@ -63,16 +59,9 @@ module.exports = {
},
lang: {
columnName: 'lang',
required: true,
type: 'string',
size: 2
},
office: {
columnName: 'id_off',
type: 'integer',
required: false,
model: 'Office'
},
id_active_scene: {
columnName: 'id_active_scene',
type: 'integer',
......
......@@ -17,9 +17,11 @@
"activation": "Activation",
"add": "Add",
"add_existing": "Add existing account",
"add_existing_desc": "Link your profile to an already registered student's account to start working with him/her. You'll be asked for the current active license of the student.",
"add_expression": "Add expression",
"add_instruction": "Add instruction",
"add_new_official": "New official account",
"add_new_official_desc": "Create a new student account with a new official license.",
"add_office": "Add office",
"add_picto": "Add pictogram",
"add_pictos": "Add pictograms",
......@@ -244,6 +246,7 @@
"new_scene_without_categories": "Create scene without categories",
"new_session": "New session",
"new_test_account": "New test account",
"new_test_account_desc": "Create a test account to try Pictogram for free. You will be able to keep the account beyond the three months trial by activating an official license on the account.",
"next": "Next",
"next_actions": "Next actions",
"next_sessions": "Next sessions",
......@@ -393,6 +396,7 @@
"student_account_confirm": "The new account is now available from the students list.",
"student_added": "Student added",
"student_already_exists": "A student with that username already exists, please try with another one",
"student_already_linked": "The student is already linked to your account",
"student_deleted": "Student deleted",
"student_not_added": "Student not added",
"student_not_deleted": "Student not deleted",
......
......@@ -17,9 +17,11 @@
"activation": "Activación",
"add": "Añadir",
"add_existing": "Asociar cuenta existente",
"add_existing_desc": "Asocie a su perfil la cuenta de un/a alumno/a ya registrado/a para poder gestionarla. Necesitará el número de licencia en uso del estudiante.",
"add_expression": "Añadir expresión",
"add_instruction": "Añadir instrucción",
"add_new_official": "Nueva cuenta oficial",
"add_new_official_desc": "Alta de una nueva cuenta de alumno/a con licencia oficial.",
"add_office": "Añadir gabinete",
"add_picto": "Añadir pictograma",
"add_pictos": "Añadir pictogramas",
......@@ -244,6 +246,7 @@
"new_scene_without_categories": "Crear escena sin categorías",
"new_session": "Nueva sesión",
"new_test_account": "Crear cuenta de prueba",
"new_test_account_desc": "Cree una cuenta temporal para probar gratuitamente Pictogram. Podrá activar una licencia en esta cuenta para mantenerla más allá de los tres meses de prueba.",
"next": "Siguiente",
"next_actions": "Acciones posteriores",
"next_sessions": "Sesiones posteriores",
......@@ -393,6 +396,7 @@
"student_account_confirm": "La nueva cuenta ahora está disponible en su lista de alumnos.",
"student_added": "Estudiante añadido",
"student_already_exists": "Ya existe un estudiante con ese nombre de usuario. Por favor, inténtelo de nuevo con algo diferente.",
"student_already_linked": "El estudiante ya está vinculado a su cuenta",
"student_deleted": "Estudiante eliminado",
"student_not_added": "Estudiante no añadido",
"student_not_deleted": "No se ha podido eliminar el estudiante",
......
......@@ -16,19 +16,37 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
$timeout,
IOService) {
$scope.formdatastudent = {
// Flags for showing buttons according to role
$scope.user = JSON.parse($window.sessionStorage.user);
// --------------------------------------------------------
// Create new account
// --------------------------------------------------------
var formdata_empty = {
username: '',
password: '',
name: '',
surname: '',
birthdate: '',
name: $translate.instant('name'),
surname: $translate.instant('surname'),
birthdate: Date(),
country: 'ES',
gender: 'M',
lang: 'es-es',
notes: '',
office: $scope.user.office || { name: '' }
current_method: 'no_method',
current_instruction: 'no_instruction',
license_number: null,
id_sup: -1
};
// Hide new student form
$scope.hidestudentadd = true;
// forms container
$scope.forms = {};
// slider object
$scope.slide = {
state: 'accounts',
prev: 'accounts',
......@@ -51,49 +69,17 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
}
};
// Flags for showing buttons according to role
$scope.user = JSON.parse($window.sessionStorage.user);
// Identify if the user is office administrator
if ($scope.user.office) {
if ($scope.user.office.admin === $scope.user.id) {
$scope.user.isAdmin = true;
}
} else {
$scope.user.office = { name: '' };
}
// Hide new student form
$scope.hidestudentadd = true;
// Get list of supervisor's students
$http.get(config.backend + '/sup/' + $scope.user.id + '/students')
.success(function (data) {
$scope.students = data;
})
.error(function () {
ngToast.danger({ content: $translate.instant('error_fetching_students') });
});
// calendar function
$scope.open_calendar = function ($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.opened_cal_student = true;
};
// Reset form Student
// Show student form
$scope.showForm = function () {
// Empty the form
$scope.formdatastudent = {
username: '',
password: '',
password_confirm: '',
name: '',
surname: '',
birthdate: '',
country: 'ES',
gender: 'M',
lang: 'es-es',
notes: '',
office: $scope.user.office || { name: '' },
current_method: 'no_method',
current_instruction: 'no_instruction',
license_number: ''
};
// Reset the form
$scope.formdata = formdata_empty;
$scope.slide.state ='accounts';
$scope.slide.show = true;
......@@ -108,10 +94,46 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
}
/**
* Add Student
* Add existing account
*/
function addExisting () {
// Send link call to server
$http.post(config.backend + '/stu/license/id_sup/' + $scope.user.id, {license: $scope.formdata.license})
.success(function (data) {
ngToast.success({ content: $translate.instant('student_added') });
// default values
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;
$scope.students.push(data);
$scope.slide.rightTo('confirmation');
})
.error(function (err) {
var errorMessage = 'student_not_added';
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("lready linked") > 0)
errorMessage = 'student_already_linked';
else if (err && err.status === 400)
errorMessage = 'invalid_fields';
ngToast.danger({ content: $translate.instant(errorMessage) });
});
}
/**
* Add new account
*/
$scope.add_student = function () {
var student = $scope.formdatastudent;
function addNew(type) {
// set language according to interface settings
$scope.formdata.lang = $translate.use();
// Validate password match
if (student.password_confirm.length && student.password !== student.password_confirm) {
......@@ -119,13 +141,11 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
return;
}
// password not changed (don't send it to DB)
if (!student.password_confirm.length) {
delete student.password;
delete student.password_confirm;
}
// Select API call according to type of account to create: office or test
var apicall = type == 'test' ? '/stu/test' : '/stu';
$http.post(config.backend + '/stu', student)
// Send creating call to server
$http.post(config.backend + apicall, $scope.formdata)
.success(function (data) {
ngToast.success({ content: $translate.instant('student_added') });
......@@ -136,7 +156,7 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
data.licenseIsValid = new Date(data.license.expiration_ts) - new Date() > 0 ? true : false;
$scope.students.push(data);
$scope.resetForm();
$scope.slide.rightTo('confirmation');
})
.error(function (err) {
var errorMessage = 'student_not_added';
......@@ -144,8 +164,6 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
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';
else if (typeof err == "string" && err.search("already exists") > 0)
errorMessage = 'student_already_exists';
else if (err && err.status === 400)
......@@ -153,14 +171,28 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
ngToast.danger({ content: $translate.instant(errorMessage) });
});
};
}
// --------------------------------------------------------
// Students list
// --------------------------------------------------------
// Get list of supervisor's students
$http.get(config.backend + '/sup/' + $scope.user.id + '/students')
.success(function (data) {
$scope.students = data;
})
.error(function () {
ngToast.danger({ content: $translate.instant('error_fetching_students') });
});
/**
* Delete Student
* Unlink Student
*/
$scope.delete_student = function (student) {
$scope.unlink_student = function (student) {
if ($window.confirm($translate.instant('confirmation'))) {
$http.delete(config.backend + '/stu/' + student.id)
$http.delete(config.backend + '/stu/' + student.id + '/sup/' + $scope.user.id)
.success(function () {
var i;
for (i = 0; i < $scope.students.length; i++) {
......@@ -180,6 +212,11 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
}
};
/**
* WEBSOCKETS
*/
// When a new student is added to the supervisor, we should update
// the student list if necesary
IOService.on('linkSupervisorToStudent', function (eventData) {
......@@ -205,14 +242,3 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
}
});
});
/**
* StudentAddCtrl
*/
dashboardControllers.controller('StudentAddCtrl', function StudentsCtrl($scope) {
$scope.open_calendar = function ($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.opened_cal_student = true;
};
});
......@@ -50,28 +50,54 @@
</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>
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/instructions" alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 2"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span></a>
<span class="btn btn-default btn-lg" role="button" alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 2"><span class="glyphicon glyphicon-tasks" aria-hidden="true" style="color: #bbb" ></span></span>
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/session" alt="{{ 'session' | translate}}" popover="{{ 'session' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 2"><span class="glyphicon glyphicon-transfer" aria-hidden="true"></span></a>
<a
class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/collections"
alt="{{ 'collections' | translate}}" popover="{{ 'collections' | translate}}" popover-trigger="mouseenter">
<span class="glyphicon glyphicon-th" aria-hidden="true"></span>
</a>
<span class="btn btn-default btn-lg" role="button" alt="{{ 'session' | translate}}" popover="{{ 'session' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 2"><span class="glyphicon glyphicon-transfer" aria-hidden="true" style="color: #bbb"></span></span>
<a
class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/instructions"
alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="!user.isTutor">
<span class="glyphicon glyphicon-tasks" aria-hidden="true"></span>
</a>
<span
class="btn btn-default btn-lg" role="button"
alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="user.isTutor">
<span class="glyphicon glyphicon-tasks" aria-hidden="true" style="color: #bbb" ></span>
</span>
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/reports" alt="{{ 'reports' | translate}}" popover="{{ 'reports' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 1"><i class="fa fa-bar-chart" aria-hidden="true"></i></a>
<a
class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/session"
alt="{{ 'session' | translate}}" popover="{{ 'session' | translate}}" popover-trigger="mouseenter" ng-if="!user.isTutor">
<span class="glyphicon glyphicon-transfer" aria-hidden="true"></span>
</a>
<span
class="btn btn-default btn-lg" role="button"
alt="{{ 'session' | translate}}" popover="{{ 'session' | translate}}" popover-trigger="mouseenter" ng-if="user.isTutor">
<span class="glyphicon glyphicon-transfer" aria-hidden="true" style="color: #bbb"></span>
</span>
<span class="btn btn-default btn-lg" role="button" alt="{{ 'reports' | translate}}" popover="{{ 'reports' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 1"><i class="fa fa-bar-chart" aria-hidden="true" style="color: #bbb"></i></span>
<a
class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/reports"
alt="{{ 'reports' | translate}}" popover="{{ 'reports' | translate}}" popover-trigger="mouseenter" ng-if="!user.isTutor">
<i class="fa fa-bar-chart" aria-hidden="true"></i>
</a>
<span class="btn btn-default btn-lg" role="button"
alt="{{ 'reports' | translate}}" popover="{{ 'reports' | translate}}" popover-trigger="mouseenter" ng-if="user.isTutor">
<i class="fa fa-bar-chart" aria-hidden="true" style="color: #bbb"></i>
</span>
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/setup" alt="{{ 'setup' | translate}}" popover="{{ 'setup' | translate}}" popover-trigger="mouseenter"><span class="glyphicon glyphicon-cog" aria-hidden="true"></span></a>
<a
class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/setup"
alt="{{ 'setup' | translate}}" popover="{{ 'setup' | translate}}" popover-trigger="mouseenter">
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span>
</a>
</td> <!-- /BUTTONS -->
<td>
<a ng-if="user.isSupAdmin" ng-click="delete_student(student)" class="delete_stu" title="{{ 'delete' | translate}}">
<a ng-click="unlink_student(student)" class="delete_stu" title="{{ 'unlink' | translate}}">
<span class="glyphicon glyphicon-remove-circle text-danger" aria-hidden="true"></span>
</a>
</td>
......
......@@ -16,6 +16,7 @@
<!--
SLIDE 1: Account type selection
-->
<div ng-class="slide.back ? 'switch-animation-back' : 'switch-animation'" ng-switch-when="accounts">
<div class="row">
<div class="col-md-4">
......@@ -24,7 +25,7 @@
<a ng-click="slide.rightTo('existing')"><img src="img/child-existing.png" alt="{{'parents_tutor' | translate}}" title="{{'parents_tutor' | translate}}" /></a>
</div>
<div>
<p>Asocie a su perfil la cuenta de un/a alumno/a ya registrado/a para poder gestionarla. Necesitará el número de licencia en uso del estudiante.</p>
<p translate>add_existing_desc</p>
</div>
</div>
<div class="col-md-4">
......@@ -33,7 +34,7 @@
<a ng-click="slide.rightTo('new')"><img src="img/child-new.png" alt="{{'therapist' | translate}}" title="{{'therapist' | translate}}" /></a>
</div>
<div>
<p>Alta de una nueva cuenta de alumno/a con licencia oficial.</p>
<p translate>add_new_official_desc</p>
</div>
</div>
<div class="col-md-4">
......@@ -42,7 +43,7 @@
<a ng-click="slide.rightTo('test')"><img src="img/child-test.png" alt="{{'office_center' | translate}}" title="{{'office_center' | translate}}" /></a>
</div>
<div>
<p>Cree una cuenta temporal para probar gratuitamente Pictogram. Podrá activar una licencia en esta cuenta para mantenerla más allá de los tres meses de prueba.</p>
<p translate>new_test_account_desc</p>
</div>
</div>
</div>
......@@ -51,97 +52,114 @@
<!--
SLIDE 2: Associate existing account
-->
<div ng-class="slide.back ? 'switch-animation-back' : 'switch-animation'" ng-switch-when="existing">
<legend translate>add_existing</legend>
<div class="row">
<div class="col-md-4 col-md-offset-2 text-center">
<img src="img/child-existing.png" alt="{{'parents_tutor' | translate}}" title="{{'parents_tutor' | translate}}" />
</div>
<div class="form-group col-md-4">
<label translate>license_number</label>
<input class="form-control" type="text" id="student_license" mask="wwww-wwww-wwww-wwww" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdatastudent.license_number" required>
</div>
</div>
<div class="row form-group">
<div class="col-md-4 col-md-offset-2 ">
<button class="btn btn-default" ng-click="slide.leftTo('accounts')">&lt;&lt; {{ 'back' | translate }} </button>
<form name="forms.existing" role="form" ng-submit="addExisting()" novalidate>
<div ng-class="slide.back ? 'switch-animation-back' : 'switch-animation'" ng-switch-when="existing">
<legend translate>add_existing</legend>
<div class="row">
<div class="col-md-4 col-md-offset-2 text-center">
<img src="img/child-existing.png" alt="{{'parents_tutor' | translate}}" title="{{'parents_tutor' | translate}}" />
</div>
<div class="form-group col-md-4">
<label translate>license_number</label>
<input class="form-control" type="text" id="student_license" mask="wwww-wwww-wwww-wwww" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdata.license_number" required>
</div>
</div>
<div class="col-md-4">
<button class="btn btn-primary float-right" ng-click="slide.rightTo('confirmation')">{{ 'Asociar' | translate }} &gt;&gt; </button>
<div class="row form-group">
<div class="col-md-4 col-md-offset-2 ">
<button class="btn btn-default" ng-click="slide.leftTo('accounts')">&lt;&lt; {{ 'back' | translate }} </button>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary float-right" ng-disabled="forms.existing.$invalid">{{ 'link' | translate }} &gt;&gt; </button>
</div>
</div>
</div>
</div>
</form>
<!--
SLIDE 3: New official account
-->
<div ng-class="slide.back ? 'switch-animation-back' : 'switch-animation'" ng-switch-when="new">
<legend translate>add_new_official</legend>
<div class="row">
<div class="col-md-4 col-md-offset-2 text-center">
<img src="img/child-new.png" alt="{{'therapist' | translate}}" title="{{'therapist' | translate}}" />
</div>
<div class="form-group col-md-4">
<div class="form-group">
<label translate>username</label>
<input type="username" class="form-control" id="setup_username" placeholder="{{ 'username_default' | translate }}" required ng-model="formdatastudent.username" />
<form name="forms.new" role="form" ng-submit="addNew('new')" novalidate>
<div ng-class="slide.back ? 'switch-animation-back' : 'switch-animation'" ng-switch-when="new">
<legend translate>add_new_official</legend>
<div class="row">
<div class="col-md-4 col-md-offset-2 text-center">
<img src="img/child-new.png" alt="{{'therapist' | translate}}" title="{{'therapist' | translate}}" />
</div>
<div class="form-group">
<label translate>password</label>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formdatastudent.password" required />
<div class="form-group col-md-4">
<div class="form-group">
<label translate>username</label>
<input type="username" class="form-control" id="setup_username" placeholder="{{ 'username_default' | translate }}" required ng-model="formdata.username" />
</div>
<div class="form-group">
<label translate>password</label>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formdata.password" required />
<span class="color_red text_sm pull-right" ng-show="formdata.password.length < minlength && forms.new.password.$dirty && forms.new.password_confirm.$dirty"> {{ 'password_short' | translate:'{ minlength: minlength }' }}</span>
</div>
<div class="form-group">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdata.password_confirm" required />
<span class="color_red text_sm pull-right" ng-show="formdata.password != formdata.password_confirm && forms.new.password.$dirty && forms.new.password_confirm.$dirty" translate>password_match</span>
</div>
<div class="form-group">
<label translate>license_number</label>
<input class="form-control" type="text" id="student_license" mask="wwww-wwww-wwww-wwww" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdata.license_number" required>
</div>
</div>
<div class="form-group">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdatastudent.password_confirm" required />
</div>
<div class="row form-group">
<div class="col-md-4 col-md-offset-2 ">
<button class="btn btn-default" ng-click="slide.leftTo('accounts')">&lt;&lt; {{ 'back' | translate }} </button>
</div>
<div class="form-group">
<label translate>license_number</label>
<input class="form-control" type="text" id="student_license" mask="wwww-wwww-wwww-wwww" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdatastudent.license_number" required>
<div class="col-md-4">
<button type="submit" class="btn btn-primary float-right" ng-disabled="forms.new.$invalid">{{ 'create_account' | translate }} &gt;&gt; </button>
</div>
</div>
</div>
<div class="row form-group">
<div class="col-md-4 col-md-offset-2 ">
<button class="btn btn-default" ng-click="slide.leftTo('accounts')">&lt;&lt; {{ 'back' | translate }} </button>
</div>
<div class="col-md-4">
<button class="btn btn-primary float-right" ng-click="slide.rightTo('confirmation')">{{ 'create_account' | translate }} &gt;&gt; </button>
</div>
</div>
</div>
</form>
<!--
SLIDE 5: Office account form
-->
<div ng-class="slide.back ? 'switch-animation-back' : 'switch-animation'" ng-switch-when="test">
<legend translate>new_test_account</legend>
<div class="row">
<div class="col-md-4 col-md-offset-2 text-center">
<img src="img/child-test.png" alt="{{'office_center' | translate}}" title="{{'office_center' | translate}}" />
</div>
<div class="form-group col-md-4" id="office_form">
<div class="form-group">
<label translate>username</label>
<input type="username" class="form-control" id="setup_username" placeholder="{{ 'username_default' | translate }}" required ng-model="formdatastudent.username" />
</div>
<div class="form-group">
<label translate>password</label>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formdatastudent.password" required />
<form name="forms.test" role="form" ng-submit="addNew('test')" novalidate>
<div ng-class="slide.back ? 'switch-animation-back' : 'switch-animation'" ng-switch-when="test">
<legend translate>new_test_account</legend>
<div class="row">
<div class="col-md-4 col-md-offset-2 text-center">
<img src="img/child-test.png" alt="{{'office_center' | translate}}" title="{{'office_center' | translate}}" />
</div>
<div class="form-group">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdatastudent.password_confirm" required />
<div class="form-group col-md-4" id="office_form">
<div class="form-group">
<label translate>username</label>
<input type="username" class="form-control" id="setup_username" placeholder="{{ 'username_default' | translate }}" required ng-model="formdata.username" />
</div>
<div class="form-group">
<label translate>password</label>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formdata.password" required />
<span class="color_red text_sm pull-right" ng-show="formdata.password.length < minlength && forms.test.password.$dirty && forms.test.password_confirm.$dirty"> {{ 'password_short' | translate:'{ minlength: minlength }' }}</span>
</div>
<div class="form-group">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdata.password_confirm" required />
<span class="color_red text_sm pull-right" ng-show="formdata.password != formdata.password_confirm && forms.test.password.$dirty && forms.test.password_confirm.$dirty" translate>password_match</span>
</div>
</div>
</div>
</div>
<div class="row form-group">
<div class="col-md-4 col-md-offset-2 ">
<button class="btn btn-default" ng-click="slide.leftTo('accounts')">&lt;&lt; {{ 'back' | translate }} </button>
</div>
<div class="col-md-4">
<button class="btn btn-primary float-right" ng-click="slide.rightTo('confirmation')">{{ 'create_account' | translate }} &gt;&gt; </button>
<div class="row form-group">
<div class="col-md-4 col-md-offset-2 ">
<button class="btn btn-default" ng-click="slide.leftTo('accounts')">&lt;&lt; {{ 'back' | translate }} </button>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary float-right" ng-disabled="forms.test.$invalid">{{ 'create_account' | translate }} &gt;&gt; </button>
</div>
</div>
</div>
</div>
</form>
<!--
......
......@@ -21,7 +21,7 @@ module.exports.pictogram = {
],
serialSize: 10, // number of characters in generated serial numbers
pageLimit: 10, // number of elements per "page"
trial_license_duration: 3, // number of moths the trial license is valid
password_minlength: 8, // minimal size for the password string
urls: {
......@@ -54,11 +54,11 @@ module.exports.pictogram = {
*/
getSupervisorCustomPictoUrl: function (filename) {
return `/upload/supervisorCustomPicto/${filename}`;
},
},
/**
* Gets the public url of a sound for a given picto
* @param {String} filename filename of sound
* @return {String} Public url of the picto sound
* @return {String} Public url of the picto sound
*/
getSoundUrl: function (filename) {
return `/upload/pictoSound/${filename}`;
......@@ -72,7 +72,7 @@ module.exports.pictogram = {
supervisorAvatarDirectory: path.join(UPLOAD_PATH, 'supervisorAvatar'),
studentAvatarDirectory: path.join(UPLOAD_PATH, 'studentAvatar'),
supervisorCustomPictoDirectory: path.join(UPLOAD_PATH, 'supervisorCustomPicto'),
pictoSoundDirectory: path.join(UPLOAD_PATH, 'pictoSound'),
pictoSoundDirectory: path.join(UPLOAD_PATH, 'pictoSound'),
/**
* Get a random name used for uploaded file names
* @param {string} randomString String used for generating the name
......
......@@ -90,6 +90,7 @@ module.exports.routes = {
'GET /stu/:id_stu/therapists': 'StudentController.therapists',
'GET /stu/:id_stu/tutors': 'StudentController.tutors',
'POST /stu/:id_stu/sup/:id_sup': 'StudentController.link_supervisor',
'POST /stu/license/sup/:id_sup': "StudentController.link_supervisor",
'GET /stu/:id_stu/pictos': 'StudentController.pictos',
'GET /stu/:id_stu/activeScene': 'StudentController.getActiveScene',
'GET /stu/:id_stu/scenes': 'StudentController.getScenes',
......@@ -104,9 +105,10 @@ module.exports.routes = {
'PUT /stu/:id_stu/legend/:legend_value': 'StudentController.update_legend',
'PUT /stu/:id_stu/picto': 'StudentController.update_picto',
'PUT /stu/:id_stu/cat': 'StudentController.update_category',
'PUT /stu/:id_stu/activeScene/:id_scene': 'StudentController.updateActiveScene',
'PUT /stu/:id_stu/activeScene/:id_scene': 'StudentController.updateActiveScene',
'POST /stu/login': 'StudentController.login',
'POST /stu': 'StudentController.create',
'POST /stu/license': 'StudentController.createTest',
'POST /stu/upload': 'StudentController.upload',
'POST /stu/:id_stu/upload_sound/:id_picto': 'StudentController.upload_sound',
'POST /stu/:id_stu/picto/:id_picto': 'StudentController.add_picto',
......
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