Commit c5e73e2d by Arturo Montejo Ráez

conflicts fixed

parents 5bee4510 f781c996
Showing with 1179 additions and 996 deletions
......@@ -62,13 +62,6 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
......@@ -76,22 +69,23 @@
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotations" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
......
......@@ -84,27 +84,26 @@ END;;
-- Integrity rule 3: office.current_enrolments and supervisor assigments updating.
DROP TRIGGER IF EXISTS TRG_MODIFY_STUDENT_ENROLMENTS;
CREATE TRIGGER TRG_MODIFY_STUDENT_ENROLMENTS
AFTER UPDATE ON student
FOR EACH ROW
thisTrigger: BEGIN
IF ((@TRIGGER_CHECKS = FALSE)
OR (@TRIGGER_AFTER_UPDATE_CHECKS = FALSE))
AND (USER() = 'root@localhost')
THEN
LEAVE thisTrigger;
END IF;
IF NOT (old.id_off<=>new.id_off) THEN
IF (old.id_off IS NOT NULL) THEN
DELETE
stu_sup
FROM
stu_sup INNER JOIN supervisor ON (stu_sup.id_sup=supervisor.id)
WHERE
id_stu=new.id AND old.id_off=supervisor.id_off;
END IF;
END IF;
END;;
DELIMITER ;
-- CREATE TRIGGER TRG_MODIFY_STUDENT_ENROLMENTS
-- AFTER UPDATE ON student
-- FOR EACH ROW
-- thisTrigger: BEGIN
-- IF ((@TRIGGER_CHECKS = FALSE)
-- OR (@TRIGGER_AFTER_UPDATE_CHECKS = FALSE))
-- AND (USER() = 'root@localhost')
-- THEN
-- LEAVE thisTrigger;
-- END IF;
--
-- IF NOT (old.id_off<=>new.id_off) THEN
-- IF (old.id_off IS NOT NULL) THEN
-- DELETE
-- stu_sup
-- FROM
-- stu_sup INNER JOIN supervisor ON (stu_sup.id_sup=supervisor.id)
-- WHERE
-- id_stu=new.id AND old.id_off=supervisor.id_off;
-- END IF;
-- END IF;
-- END;;
SET foreign_key_checks = 0;
ALTER TABLE supervisor ADD role enum('tutor','therapist','office','admin') NOT NULL;
ALTER TABLE supervisor MODIFY name VARCHAR(40) DEFAULT NULL;
ALTER TABLE supervisor MODIFY surname VARCHAR(60) DEFAULT NULL;
ALTER TABLE supervisor MODIFY gender CHAR(1) DEFAULT NULL;
ALTER TABLE supervisor MODIFY postal_code CHAR(10) DEFAULT NULL;
ALTER TABLE supervisor MODIFY lang VARCHAR(5) DEFAULT NULL;
ALTER TABLE student MODIFY name VARCHAR(40) DEFAULT NULL;
ALTER TABLE student MODIFY surname VARCHAR(60) DEFAULT NULL;
ALTER TABLE student MODIFY birthdate DATE DEFAULT NULL;
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 `sup_off` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_sup` int(11) NOT NULL,
`id_off` int(11) DEFAULT NULL,
KEY `fk_sup_off` (`id_sup`),
KEY `fk_off_sup` (`id_off`),
PRIMARY KEY(`id`),
UNIQUE KEY `idx_sup_off` (`id_sup`,`id_off`),
CONSTRAINT `fk_off_sup` FOREIGN KEY (`id_off`) REFERENCES `supervisor` (`id`),
CONSTRAINT `fk_sup_off` FOREIGN KEY (`id_sup`) 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);
UPDATE supervisor
SET role = 'therapist'
WHERE id NOT IN (SELECT admin FROM office WHERE admin IS NOT NULL);
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()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE id_admin INT;
DECLARE id_off_actual INT;
DECLARE name_sup VARCHAR(80);
DECLARE office CURSOR FOR SELECT id,name,admin FROM pictodb.office;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN office;
read_loop: LOOP
FETCH office INTO id_off_actual,name_sup,id_admin;
IF done THEN
LEAVE read_loop;
END IF;
UPDATE supervisor SET name = name_sup, surname = '',role = 'office'
WHERE id = id_admin;
UPDATE student SET id_off = id_admin WHERE id_off = id_off_actual;
UPDATE supervisor SET id_off = id_admin WHERE id_off = id_off_actual;
END LOOP;
CLOSE office;
END $$
DELIMITER ;
CALL supervisor_adapt();
INSERT IGNORE INTO stu_sup (id_stu,id_sup)
SELECT id,id_off
FROM student;
INSERT INTO sup_off (id_sup, id_off)
SELECT id, id_off
FROM supervisor;
ALTER TABLE student DROP FOREIGN KEY student_ibfk_1;
ALTER TABLE student DROP COLUMN id_off;
ALTER TABLE supervisor DROP FOREIGN KEY supervisor_ibfk_1;
ALTER TABLE supervisor DROP COLUMN id_off;
DROP TABLE office;
......@@ -5,10 +5,8 @@ the version of AngularJS used. In order to do show, type `bower install` from
*assets/app* directory.
## Database
Para actualizar instalaciones antiguas ejecutar en vagrant/roles/database/files
Create new tables
mysql -u root -p pictodb < upgrade.sql
Destroy *office* table: `drop table office`
Relanzar trigger-enrolments-integrity-constraints
......@@ -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,
......
......@@ -48,6 +48,11 @@ module.exports = {
size: 16,
unique: true
},
type: {
type: "string",
enum: ['trial', 'official'],
columnName: 'type'
},
creator: {
columnName: "creator",
type: "string",
......@@ -64,6 +69,110 @@ module.exports = {
/**
Class methods
*/
hasExpired: function (license) {
return (new Date(license.expiration_ts) - new Date() < 0);
},
/**
* Generates a new trial license
* @param {ID} id_sup ID of the supervisor who creates the license
* @param {function} callback Callback function: (err, license)
*/
newTrial: function(id_sup, callback) {
var now = new Date();
License.genLicenseNumber(function(number) {
if (!number)
callback(new Error("Unable to generate new license"), null);
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(null, l);
})
.catch((err) => {
callback(err, null);
});
});
},
/**
* 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.student)
throw new Error("Invalid license: " + number);
// License ok, check student
Student.findOne(l.student)
.then((s) => {
if (!s)
throw new Error("Student not found");
// convert model to normal object to be able to add attributes
s = s.toJSON();
s.license = l;
callback(null, s);
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
callback(err, null);
});
},
/**
Activates a license
......@@ -72,6 +181,10 @@ module.exports = {
@param {function} callback Callback function with prototype: function(err, license)
*/
activate: function(number, id_stu, callback) {
if (!number || number.length < 16)
callback(new Error("Invalid license number"));
// Check license
License.findOne({ number: number })
.then((l) => {
......@@ -127,6 +240,7 @@ module.exports = {
* Callback function gets instantiated error if not available
*/
isActivable: function(number, cb) {
License.findOne({number: number})
.then ((l) => {
if (!l)
......
/**
* office.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 = {
connection : 'localMysql',
tableName : 'office',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
attributes: {
id: {
type: "integer",
autoIncrement: true,
primaryKey: true,
unique: true
},
name: {
required: true,
type: "string",
size: 80
},
logoUrl: {
columnName: 'logo_url',
type: "string",
size: 240
},
address: {
required: true,
type: "string",
size: 80
},
country: {
type: "string",
size: 5,
required: true
},
lang: {
required: true,
type: "string",
size: 5
},
contactPerson: {
columnName: "contact_person",
required: true,
type: "string",
size: 80
},
email: {
required: true,
type: "string",
size: 80,
unique: true
},
phone1: {
required: true,
type: "string",
size: 20
},
phone2: {
type: "string",
size: 20
},
admin: {
columnName: 'admin',
type: 'integer',
model: 'Supervisor'
},
postalCode: {
columnName: 'postal_code',
required: true,
type: "string",
size: 10
},
// Relación con Teacher. [1 Office to N Teacher]
supervisors: {
collection: "Supervisor",
via: 'office'
},
// Relación con Student. [1 Office to N Student]
students: {
collection: "Student",
via: 'office'
},
toJSON: function() {
var office = this.toObject();
if (!office.logoUrl)
office.logoUrl = '/app/img/logo_pictogram.png';
return office;
}
},
beforeCreate: function (attrs, next) {
if (!attrs.logoUrl)
attrs.logoUrl = '/app/img/logo_pictogram.png';
next();
}
};
......@@ -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',
......@@ -120,8 +109,12 @@ module.exports = {
toJSON: function () {
var student = this.toObject();
student.pic = sails.config.pictogram.urls.getStudentAvatarUrl(student.pic);
if (student.license)
if (student.license) {
student.license = student.license[0] ? student.license[0] : null;
student.license.isValid = !License.hasExpired(student.license);
student.license.isTrial = student.license.type == 'trial';
student.license.isOfficial = student.license.type == 'official';
}
student.attributes = Student.getValidAttributes(student.attributes);
delete student.password;
return student;
......@@ -296,70 +289,56 @@ module.exports = {
},
//
// Class method for getting the list of supervisors (therapist + tutors) associated to a given
// student
supervisors: function(id_stu, callback) {
StuSup.find({id_stu: id_stu}).populate('supervisor').exec(function(err, stuSups) {
if (err)
return callback(err, []);
// Class method for getting the list of supervisors associated to a given
// student and where the requester is related office
validSupervisors: function(id_stu, id_sup, callback) {
// Get all supervisors
StuSup.find({id_stu: id_stu})
.populate('supervisor')
.then((stuSups) => {
if (!stuSups || stuSups.length == 0)
return callback(null, []);
throw new Error("No supervisors");
var sups = stuSups.map((st) => {return st.supervisor});
return callback(null, sups);
});
},
// return supervisors and related supervisors to the office
return [stuSups, SupOff.find({office: id_sup}).populate('supervisor')]
})
.spread((stuSups, supOffs) => {
//
// Class method for getting the list of therapists associated to a given
// student
// NOTE: A therapist is a supervisor assigned to an office
therapists: function(id_stu, callback) {
StuSup.find({id_stu: id_stu}).populate('supervisor').exec(function(err, stuSups) {
var l = [];
if (err || !stuSups || stuSups.length == 0)
return callback(err, l);
async.eachSeries(stuSups,
function(stuSup, next) {
// stuSup.supervisor.id > 0 for not retrieving the -1 default supervisor
if (stuSup.supervisor && stuSup.supervisor.office && stuSup.supervisor.id > 0) {
l.push(stuSup.supervisor);
}
next();
},
function (err) {
return callback(err, l);
}
);
if (!supOffs || supOffs.length == 0)
throw new Error("No supervisors");
// filter null entries and map them to the supervisor object
var ss = _.compact(_.compact(stuSups).map(x => x.supervisor));
var so = _.compact(_.compact(supOffs).map(x => x.supervisor));
// filter from the second list those found in the first list
var sups = so.filter(a => ss.findIndex(b => b.id == a.id) >= 0);
return callback(null, sups);
})
.catch((err) => {
return callback(err, []);
});
},
//
// Class method for getting the list of tutors associated to a given
// Class method for getting the list of all the supervisors associated to a given
// student
// NOTE: A tutor is a supervisor not assigned to any office
tutors: function(id_stu, callback) {
StuSup.find({id_stu: id_stu}).populate('supervisor').exec(function(err, stuSups) {
var l = [];
if (err || !stuSups || stuSups.length == 0)
return callback(err, l);
async.eachSeries(stuSups,
function(stuSup, next) {
// stuSup.supervisor.id > 0 for not retrieving the -1 default supervisor
if (stuSup.supervisor && !stuSup.supervisor.office && stuSup.supervisor.id > 0) {
l.push(stuSup.supervisor);
}
next();
},
function (err) {
return callback(err, l);
}
);
allSupervisors: function(id_stu, callback) {
// Get all supervisors
StuSup.find({id_stu: id_stu})
.populate('supervisor')
.then((stuSups) => {
if (!stuSups || stuSups.length == 0)
throw new Error("No supervisors");
var ss = _.compact(stuSups).map(x => x.supervisor);
return callback(null, ss);
})
.catch((err) => {
return callback(err, []);
});
},
......@@ -536,7 +515,10 @@ module.exports = {
});
},
// Removes logically a student
/**
* Removes logically a student
* The user name is set with a random prefix and the license is removed
*/
logical_delete: function(id_stu, cb) {
Student.findOne(id_stu).exec(function(err, student) {
if (err || !student)
......@@ -544,7 +526,6 @@ module.exports = {
Student.update(id_stu,
{
username: Math.floor((Math.random() * 100000000) + 1) + "_" + student.username,
id_off: null
})
.then((updated) => {
License.destroy({id_stu: id_stu}).exec(cb);
......
/**
* StuSup.js
*
* @description :: TODO: Write a short summary of how this model works and what it represents here.
* @docs :: http://sailsjs.org/#!documentation/models
*/
/**
* This model relates tutors and therapists with offices
*/
module.exports = {
tableName : 'sup_off',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
attributes: {
id: {
type: "integer",
autoIncrement: true,
primaryKey: true,
unique: true
},
supervisor: { // FK de Supervisor. 1 a N
columnName: "id_sup",
required: true,
type: "integer",
model: "Supervisor"
},
office: { // FK de Supervisor. 1 a N
columnName: "id_off",
type: "integer",
model: "Supervisor"
}
}
}
......@@ -30,17 +30,18 @@ module.exports = {
size: 40
},
name: {
required: true,
type: "string",
size: 40
},
surname: {
required: true,
type: "string",
size: 60
},
role: {
type: "string",
enum: ['tutor', 'therapist', 'office', 'admin']
},
gender: {
required: true,
type: "string",
size: 1
},
......@@ -88,12 +89,6 @@ module.exports = {
type: 'boolean',
columnName: 'arasaac_license'
},
office: {
columnName: 'id_off',
type: 'integer',
required: false,
model: 'Office'
},
// Relación con WorkingSession. [1 Supervisor to N WorkingSession]
workingSessions:{
collection: "WorkingSession",
......@@ -143,7 +138,6 @@ module.exports = {
//
beforeCreate: function (attrs, next) {
var async = require('async');
console.log("-->\n" + JSON.stringify(attrs));
async.series(
[
function (cb) {
......@@ -226,7 +220,6 @@ module.exports = {
var l = [];
Supervisor.findOne(id)
.populate('office')
.then(function (sup) {
if (!sup)
throw new Error("Not a valid supervisor")
......@@ -247,64 +240,76 @@ module.exports = {
})
.spread(function (sup, stuSups) {
async.eachSeries(stuSups, function(stuSup, next_cb) {
// Filter logically deleted students
if (stuSup.student.office == null)
return next_cb();
// set current method and instruction if any
Student.findOne(stuSup.student.id)
.populate('lastInstruction')
.populate('license')
.then(function (s) {
if (!s)
return next_cb();
s = s.toJSON();
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;
}
s.supervision = sup.office ? 2 : 1; // if Supervisor has office, then is a therapist (2), a tutor (1) otherwise
if (!s.license)
return next_cb();
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;
Student.find({ office: sup.office.id })
.populate('lastInstruction')
.populate('license')
.then(function (officeStudents) {
officeStudents = officeStudents.map((student) => {
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);
callback(err, lodash.uniq(l, false, 'id'));
})
.catch(function (err) {
callback(err, l);
});
} else {
callback(err, l);
}
}); // end async.eachSeries
function (err) {
callback(err, l);
});
})
.catch((err) => {
callback(err, l);
}); // end Supervisor.findOne
},
/***
* Return the list of students that are common to both supervisors
*/
commonStudents: function(id1, id2, callback) {
var l = [];
StuSup.find({ supervisor: id1 })
.then(function (stuSups) {
return [stuSups, StuSup.find({ supervisor: id2 })];
})
.spread((stuSups1, stuSups2) => {
if (!stuSups1 || !stuSups2)
throw new Error("No students");
// Common students
var stuSups = stuSups1.filter(a => stuSups2.findIndex(b => b.student == a.student) >= 0);
// Now, let's take full information about them
async.eachSeries(stuSups, function(stuSup, next_cb) {
// set current method and instruction if any
Student.findOne(stuSup.student)
.populate('lastInstruction')
.populate('license')
.then(function (s) {
if (!s)
return next_cb();
s = s.toJSON();
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 (!s.license)
return next_cb();
l.push(s);
next_cb();
});
},
function (err) {
return callback(err, l);
});
})
.catch((err) => {
return callback(err, l);
});
}
};
module.exports = function isAdmin (req, res, next) {
//
// Only if the user that has connected is global administrator (Yotta employee)
// Only if the user that has connected is global administrator (Yotta employee)
//
if (!req.token || !req.token.isAdmin)
if (!req.token || req.token.role !== 'admin')
res.json(401, {error: 'Access denied'});
// Finally, if the user has a clean record, we'll call the `next()` function
// to let them through to the next policy or our controller
next();
};
\ No newline at end of file
};
module.exports = function isAdmin (req, res, next) {
module.exports = function isAdminOrOffice (req, res, next) {
//
// Only if the user that has connected is global administrator (Yotta employee)
// Only if the user that has connected is global administrator (Yotta employee) or is an office
//
if (!req.token || !req.token.isAdmin && !req.token.isSupAdmin)
if (!req.token || req.token.role !== 'admin' && req.token.role !== 'office')
res.json(401, {error: 'Access denied'});
// Finally, if the user has a clean record, we'll call the `next()` function
......
......@@ -3,10 +3,10 @@ module.exports = function isSupAdmin (req, res, next) {
//
// A SupAdmin is a supervisor that is administrator of an office
// That means that we can find its id at the admin column in an entry at the office table
if (!req.token || !req.token.isSupAdmin)
if (!req.token || req.token.role !== 'office')
res.json(401, {error: 'Access denied'});
// Finally, if the user has a clean record, we'll call the `next()` function
// to let them through to the next policy or our controller
next();
};
\ No newline at end of file
};
module.exports = function isSupervisorOfStudentOrIsStudent (req, res, next) {
module.exports = function isStudentOrSupervisorOfStudent (req, res, next) {
// sails.log("TOKEN: " + JSON.stringify(req.token));
if (!req.params.id_stu)
return res.json(401, {error: 'Access denied 1'}); // If it is a student, then is ok
if (req.token && req.token.isStudent && req.token.id == req.params.id_stu)
return next();
// Get list of supervisors for the student
Student.supervisors(req.params.id_stu, function(err, sups) {
Student.allSupervisors(req.params.id_stu, function(err, sups) {
if (err)
return res.json(401, {error: err});
if (!sups || sups.length == 0)
return res.json(401, {error: "This student has no supervisors associated"});
// if supervisor is not in the list of supervisors
if (sups.map(function(e) {return e.id}).indexOf(req.token.id) < 0)
// if supervisor is not in the list of supervisors
if (_.compact(sups).map(function(e) {return e.id}).indexOf(req.token.id) < 0)
return res.json(401, {error: 'Access denied 3'});
// Finally, if the user has a clean record, we'll call the `next()` function
// to let them through to the next policy or our controller
next();
});
};
/* global sails, Student */
module.exports = function isSupervisorOfStudent(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.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();
}
});
}
if (!req.params.id_stu || !req.token.id)
return res.json(401, {error: 'Access denied'});
// Get list of supervisors for the student
Student.allSupervisors(req.params.id_stu, function(err, sups) {
if (err)
return res.json(401, {error: err});
if (!sups || sups.length == 0)
return res.json(401, {error: "This student has no supervisors associated"});
// if supervisor is not in the list of supervisors
if (_.compact(sups).map(function(e) {return e.id}).indexOf(req.token.id) < 0)
return res.json(401, {error: 'Access denied 3'});
// Finally, if the user has a clean record, we'll call the `next()` function
// to let them through to the next policy or our controller
next();
});
};
/* 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');
return res.json(401, { error: 'Access denied' });
} else {
Student.findOne(studentId)
.then(function (s) {
if (req.token.office && s.office == req.token.office.id && req.token.isSupAdmin)
next();
else {
Student.supervisors(studentId, function (err, sups) {
if (err)
return res.json(401, {error: 'Access denied'});
var supIds = sups.map((studentSupervisor) => studentSupervisor.id);
if (supIds.indexOf(supervisorId) >= 0)
return next();
sails.log.error(`Supervisor ${supervisorId} is not assigned to Student ${studentId}`);
return res.json(401, { error: 'Access denied' });
});
}
})
.catch((err) => {
sails.log.error(JSON.stringify(err));
return res.json(401, { error: "No student found" })
});
}
};
module.exports = function isSupervisorOfStudentOrIsSupAdminOrIsStudent (req, res, next) {
// sails.log("TOKEN: " + JSON.stringify(req.token));
if (!req.params.id_stu)
return res.json(401, {error: 'Access denied 1'});
Student.findOne(req.params.id_stu)
.then(function (s) {
if (req.token && req.token.isStudent && req.token.id == req.params.id_stu)
return next(); // Is Student
if (req.token.office && s.office == req.token.office.id && req.token.isSupAdmin)
return next(); // Is Office's administrator
Student.supervisors(req.params.id_stu, function(err, sups) {
if (err || !sups || sups.length == 0)
return res.json(401, {error: "This student has no supervisors associated"});
if (sups.map(function(e) {return e.id}).indexOf(req.token.id) >= 0)
return next(); // Is Supervisor of Student
return res.json(401, {error: "No valid credentials"});
});
})
.catch((err) => {
return res.json(401, {error: "Student not found"});
});
};
......@@ -27,7 +27,7 @@ module.exports = function badRequest(data, options) {
// Log error to console
if (data !== undefined) {
sails.log.verbose('Sending 400 ("Bad Request") response: \n',data);
sails.log.verbose('Sending 400 ("Bad Request") response: \n', data);
}
else sails.log.verbose('Sending 400 ("Bad Request") response');
......
......@@ -22,7 +22,7 @@
"humanize-duration": "~3.0.0",
"momentjs": "~2.10.3",
"ngtoast": "~1.5.4",
"angular-animate": "~1.4.1",
"angular-animate": "~1.2.2",
"angular-sanitize": "~1.4.1",
"angular-chart.js": "latest",
"ng-lodash": "~0.3.0",
......@@ -34,7 +34,6 @@
},
"resolutions": {
"angular": ">=1 <1.3.0",
"humanize-duration": "~3.0.0",
"momentjs": "~2.10.3"
"humanize-duration": "~3.0.0"
}
}
......@@ -32,8 +32,9 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $
id: '',
name: '',
surname: '',
email: '',
role: '',
pic: '',
office: '',
lang: ''
};
......@@ -74,7 +75,7 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $
// Returns the full name of the user
$scope.user.getFullName = function(){
return $scope.user.name + " " + $scope.user.surname;
return $scope.user.name + ($scope.user.isOffice ? "" : " " + $scope.user.surname);
};
// Logout
......
......@@ -8,6 +8,7 @@
dashboardControllers.controller('LoginCtrl',
function LoginCtrl(
$scope,
$rootScope,
$http,
$window,
$translate,
......@@ -15,82 +16,160 @@ function LoginCtrl(
$location,
config,
$stateParams,
ngToast) {
$timeout,
ngToast,
CONSTANTS,
vcRecaptchaService) {
/* Slider object */
$scope.slide = {
state: 'login',
prev: 'login',
back: false,
rightTo: function (state) {
$scope.slide.back = false;
$scope.slide.prev = $scope.slide.state;
$timeout(function () {
$scope.slide.state = state;
}, 0);
},
leftTo: function (state) {
$scope.slide.back = true;
$scope.slide.prev = $scope.slide.state;
$timeout(function () {
$scope.slide.state = state;
}, 0);
}
};
/*
LOGIN --------------------------
*/
/* Login credentials */
$scope.credentials = {
email: '',
password: '',
lang: 'es-es'
};
$scope.office = {
logoUrl : 'img/logo_pictogram.png',
name : 'Pictogram'
};
// Corporate login, office code has been passed
if ($stateParams.office) {
$http
.get(config.backend + '/office/' + $stateParams.office)
.success(function (data) {
$scope.office = data;
});
}
/* Login function */
$scope.login = function () {
$scope.submitted = true;
$http
.post(config.backend + '/sup/login', $scope.credentials)
.success(function (data) {
// default logo to Pictogram logo
if (!data.user.office) {
data.user.office = $scope.office;
data.user.isTutor = true;
} else
data.user.isTutor = false;
if (data.user.office.logoUrl.length < 5)
data.user.office.logoUrl = 'img/logo_pictogram.png';
$window.sessionStorage.token = data.token;
//User data correct
if (data.user) {
// Adapt language en-us to en-gb (the latter is the one supported for 'en')
if (data.user.lang === 'en-us') {
if (data.user.lang === 'en-us')
data.user.lang = 'en-gb';
}
//Update $scope
data.user.isTutor = data.user.role == 'tutor';
data.user.isTherapist = data.user.role == 'therapist';
data.user.isOffice = data.user.role == 'office';
data.user.isAdmin = data.user.role == 'admin';
$scope.lang = data.user.lang;
//Update $translate
$translate.use($scope.lang);
} else {
//No user data, use default lang
$translate.use($scope.lang);
$location.path('/sup/login');
}
// Change
$window.sessionStorage.user = JSON.stringify(data.user);
$translate('login_success').then(function (translation) {
ngToast.success({ content: translation });
});
// Name in login success message
$scope.name = data.user.name;
// Redirección
$location.path('/students');
})
.error(function (err) {
$scope.submitted = false;
delete $window.sessionStorage.token;
if (err.search("without students") > 0) {
ngToast.warning($translate.instant('no_students_for_user'));
} else if (err.search("not been activated") > 0) {
ngToast.danger($translate.instant('inactive_account'));
} else {
ngToast.danger($translate.instant("login_fail"));
}
if (err.search("not been activated") > 0)
ngToast.danger($translate.instant('inactive_account'));
else
ngToast.danger($translate.instant("login_fail"));
});
};
/*
SIGNUP ------------------------------
*/
/* form data */
var formdata_empty = {
name: '',
email: '',
password: '',
password_confirm: '',
lang: '',
role: '',
};
$scope.minlength = CONSTANTS.password_minlength;
/* Forms objects */
$scope.forms = {};
$scope.reset = function () {
$scope.formdata = formdata_empty;
};
$scope.reset();
// Signup form submit
$scope.signup = function (formName) {
var form;
// select form according to account
if (formName == 'tutorForm')
form = $scope.forms.tutorForm;
else if (formName == 'therapistForm')
form = $scope.forms.therapistForm;
else
form = $scope.forms.officeForm;
// set language according to interface settings
$scope.formdata.lang = $translate.use();
//
// check validity of fields
//
if (typeof $scope.formdata.email == 'undefined' || form.email.$invalid) {
ngToast.danger($translate.instant('email_invalid'));
return;
}
if ($scope.formdata.password.length < CONSTANTS.password_minlength) {
ngToast.danger($translate.instant('password_short', {minlength: CONSTANTS.password_minlength}));
return;
}
if ($scope.formdata.password !== $scope.formdata.password_confirm) {
ngToast.danger($translate.instant('password_match'));
return;
}
if (!$scope.formdata.disclaimer_accepted) {
ngToast.danger($translate.instant('disclaimer_requested'));
return;
}
if (form.$invalid)
return;
$http
.post(config.backend + '/sup', $scope.formdata)
.success(function () {
$scope.slide.rightTo('confirmation');
})
.error(function () {
ngToast.danger({ content: $translate.instant('user_exists', {email: $scope.formdata.email}) });
});
};
});
/* global dashboardControllers */
'use strict';
//-------------------
// SignIn Controller
//-------------------
dashboardControllers.controller('SignInCtrl',
function SignInCtrl($scope,
$http,
$window,
$translate,
$rootScope,
config,
CONSTANTS,
ngToast,
vcRecaptchaService)
{
$scope.reset = function () {
$scope.formdata = {
name: '',
surname: '',
address: '',
postalCode: '',
country: '00',
phone: '',
gender: 'F',
email: '',
email_confirm: '',
password: '',
password_confirm: '',
lang: '00',
role: 'therapist_office',
office: {
name: '',
address: '',
postalCode: '',
country: '00',
contactPerson: '',
phone1: '',
email: '',
logoUrl: ''
},
id_off: -1
};
};
$scope.reset();
// Get the list of offices
$http
.get(config.backend + '/office/get_all')
.success(function (offices) {
$scope.offices = offices;
})
.error(function () {
ngToast.danger({ content: $translate.instant('server_error') });
});
// Copy fields from supervisor to office
$scope.copyFields = function () {
$scope.formdata.office.address = $scope.formdata.address;
$scope.formdata.office.postalCode = $scope.formdata.postalCode;
$scope.formdata.office.country = $scope.formdata.country;
$scope.formdata.office.lang = $scope.formdata.lang;
$scope.formdata.office.email = $scope.formdata.email;
$scope.formdata.office.phone1 = $scope.formdata.phone;
$scope.formdata.office.contactPerson = $scope.formdata.name + " " + $scope.formdata.surname;
};
// Form submit
$scope.signin = function () {
// Validate email match
if ($scope.formdata.email !== $scope.formdata.email_confirm) {
ngToast.danger({ content: $translate.instant('email_match') });
return;
}
// Validate password match
if ($scope.formdata.password !== $scope.formdata.password_confirm) {
ngToast.danger({ content: $translate.instant('password_match') });
return;
}
if ($scope.formdata.password.length < CONSTANTS.password_minlength) {
ngToast.danger({ content: $translate.instant('password_short', {minlength: CONSTANTS.password_minlength}) });
return;
}
if ($scope.formdata.country == '00') {
ngToast.danger({ content: $translate.instant('country_requested') });
return;
}
if (($scope.formdata.role == 'therapist_office' || $scope.formdata.role == 'tutor_office') && $scope.formdata.id_off == -1) {
ngToast.danger({ content: $translate.instant('select_office') });
return;
}
if ($scope.formdata.role == 'therapist_nooffice' && $scope.formdata.office.country == '00') {
ngToast.danger({ content: $translate.instant('country_office_requested') });
return;
}
if (!$scope.formdata.disclaimer_accepted) {
ngToast.danger({ content: $translate.instant('disclaimer_requested') });
return;
}
if (!$scope.formdata.role) {
ngToast.danger({ content: $translate.instant('case_requested') });
return;
}
if (!$scope.signInForm.$valid)
return;
$scope.showdialog = true;
if ($scope.formdata.id_off == -1)
delete $scope.formdata.id_off;
if ($scope.formdata.role === 'tutor_nooffice') {
$scope.formdata.office.name = 'no_office';
$scope.formdata.office.address = $scope.formdata.address;
$scope.formdata.office.postalCode = $scope.formdata.postalCode;
$scope.formdata.office.country = $scope.formdata.country;
$scope.formdata.office.contactPerson = $scope.formdata.name + " " + $scope.formdata.surname;
$scope.formdata.office.phone1 = $scope.formdata.phone;
$scope.formdata.office.email = $scope.formdata.email;
$scope.formdata.office.lang = $scope.formdata.lang;
}
$http
.post(config.backend + '/sup', $scope.formdata)
.success(function () {
ngToast.success({ content: $translate.instant('user_created', { name: $scope.formdata.name, surname: $scope.formdata.surname }) });
$scope.reset();
})
.error(function () {
ngToast.danger({ content: $translate.instant('user_exists', {email: $scope.formdata.email}) });
});
};
});
......@@ -26,7 +26,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
});*/
$scope.supsForm = {};
$scope.section = 'account';
$scope.section = {val: 'account'};
$scope.changeImg = function () {
......@@ -148,11 +148,11 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
.error(function (err) {
console.log(err);
if (err.message && err.message.search('nvalid license') > 0)
ngToast.danger({ content: $translate.instant('license_invalid') });
ngToast.danger($translate.instant('license_invalid'));
else if (err.message && err.message.search('in use') > 0)
ngToast.danger({ content: $translate.instant('license_already_activated') });
ngToast.danger($translate.instant('license_already_activated'));
else
ngToast.danger({ content: $translate.instant('student_not_updated') });
ngToast.danger($translate.instant('student_not_updated'));
});
};
......@@ -161,26 +161,21 @@ 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.supsForm.email_sup)
$http.get(config.backend + '/sup/off/email/' + $scope.supsForm.email_sup)
.success(function (data) {
if (data) {
$scope.supToAdd = data;
$scope.showmessagesupfound = true;
$scope.showmessagesupnotfound = false;
} else {
$translate('sup_not_found').then(function (translation) {
ngToast.danger({ content: translation });
});
ngToast.danger($translate.instant('sup_not_in_office'));
// Hide the success message (if it exists by other query)
$scope.showmessagesupfound = false;
}
})
.error(function () {
$translate('sup_not_found').then(function (translation) {
ngToast.danger({ content: translation });
});
ngToast.danger($translate.instant('sup_not_in_office'));
});
};
......@@ -193,13 +188,13 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// Ensure supervisor is not already in the list
if ($scope.studentSupervisors.map((s) => s.id).includes($scope.supToAdd.id)) {
ngToast.danger({ content: $translate.instant('sup_already_added') });
ngToast.danger($translate.instant('sup_already_added'));
$scope.supsForm.email_sup = '';
$scope.showmessagesupfound = false;
return;
}
$http.post(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + $scope.supToAdd.id, {asTherapist: true})
$http.post(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + $scope.supToAdd.id)
.success(function (data) {
// Assign the info of supervisor to add
stusup.supervisor = $scope.supToAdd;
......@@ -214,7 +209,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
$scope.showmessagesupfound = false;
})
.error(function () {
ngToast.danger({ content: $translate.instant('sup_not_added') });
ngToast.danger($translate.instant('sup_not_added'));
});
};
......@@ -226,7 +221,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
var deleteSup = $window.confirm(
$translate.instant(
'confirm_unlink_therapist',
'confirm_unlink_student',
{name: $scope.studentSupervisors[i].name, student: $scope.studentData.name}
));
......@@ -243,95 +238,6 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
}
};
// Search tutor by email
$scope.search_tutor = function () {
// Find tutor by email
$http.get(config.backend + '/sup/email/' + $scope.supsForm.email_tutor)
.success(function (data) {
// If it found the length is > 0
if (data) {
$scope.tutorToAdd = data;
// Show message for validate
$scope.showmessagetutorfound = true;
$scope.showmessagetutornotfound = false;
} else {
$translate('tutor_not_found').then(function (translation) {
ngToast.danger({ content: translation });
});
// Hide the success message (if it exists by other query)
$scope.showmessagetutorfound = false;
}
})
.error(function () {
$translate('tutor_not_found').then(function (translation) {
ngToast.danger({ content: translation });
});
});
};
// Add tutor
$scope.add_tutor = function () {
var stusup = {
student: $scope.studentData.id,
supervisor: $scope.tutorToAdd.id
};
// Ensure supervisor is not already in the list
if ($scope.studentTutors.map((s) => s.id).includes($scope.tutorToAdd.id)) {
ngToast.danger({ content: $translate.instant('sup_already_added') });
$scope.supsForm.email_tutor = '';
$scope.showmessagetutorfound = false;
return;
}
$http.post(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + $scope.tutorToAdd.id)
.success(function (data) {
// Assign the info of supervisor to add
stusup.supervisor = $scope.tutorToAdd;
stusup.id = data.id;
// Add to the list of tutors in view
$scope.studentTutors.push($scope.tutorToAdd);
// Delete the email form field
$scope.supsForm.email_tutor = '';
// Hide the message of tutor founded
$scope.showmessagetutorfound = false;
$translate('tutor_added').then(function (translation) {
ngToast.success({ content: translation });
});
})
.error(function () {
$translate('tutor_not_added').then(function (translation) {
ngToast.danger({ content: translation });
});
});
};
// Delete tutor
$scope.delete_tutor = function (id_sup) {
//Se recorre el array de objetos json para buscarlo
var i;
for (i = 0; i < $scope.studentTutors.length && id_sup !== $scope.studentTutors[i].id; i++);
var deleteTutor = $window.confirm(
$translate.instant(
'confirm_unlink_tutor',
{name: $scope.studentTutors[i].name, student: $scope.studentData.name}
));
if (deleteTutor) {
$http.delete(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + id_sup)
.success(function () {
// Eliminar de la vista
$scope.studentTutors.splice(i, 1);
ngToast.success({ content: $translate.instant('tutor_deleted') });
})
.error(function () {
ngToast.danger({ content: $translate.instant('tutor_not_deleted') });
});
}
};
// *******************************************************
// Setup
// $scope.studentData.attributes
......@@ -342,14 +248,10 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
attributes: $scope.studentData.attributes
})
.success(function () {
$translate('attributes_updated').then(function (translation) {
ngToast.success({ content: translation });
});
ngToast.success($translate.instant('attributes_updated'));
})
.error(function () {
$translate('attributes_not_updated').then(function (translation) {
ngToast.danger({ content: translation });
});
ngToast.danger($translate.instant('attributes_not_updated'));
});
}, 300);
});
......@@ -32,10 +32,6 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
number: ''
},
license_expired: false,
office: {
id: '',
name: ''
},
stuSup: [],
pcb_count: 0,
pdb_count: 1
......@@ -84,15 +80,14 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
$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');
console.log("updateLicenseExpiration");
};
// ----------------------------------------------------------------------
// MAIN
//
// Load student account information
//
//
$http.get(config.backend + '/stu/' + $scope.studentData.id)
.success(function (data) {
$scope.studentData.id = data.id;
......@@ -105,8 +100,6 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
$scope.studentData.gender = data.gender;
$scope.studentData.lang = data.lang;
$scope.studentData.notes = data.notes;
$scope.studentData.office.id = data.office.id;
$scope.studentData.office.name = data.office.name;
$scope.studentData.stuSup = data.stuSup;
$scope.studentData.attributes = data.attributes;
$scope.studentData.current_method = data.current_method;
......@@ -148,27 +141,22 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
ngToast.danger({ content: $translate.instant('no_subscribed') });
});
//
// For tab navigation, initially blank goes to collections
// Defined as JSON object to be available in in children as the same scope
//
$scope.nav = {
tab: 'collections'
};
//
// Load student supervisors
//
$scope.studentSupervisors = [];
$http.get(config.backend + '/stu/' + $scope.studentData.id + '/therapists')
$http.get(config.backend + '/stu/' + $scope.studentData.id + '/supervisors')
.success(function (data) {
$scope.studentSupervisors = data;
})
.error(function () {
// TODO show error with ngToast
});
.error(function (err) {});
$scope.studentTutors = [];
$http.get(config.backend + '/stu/' + $scope.studentData.id + '/tutors')
.success(function (data) {
$scope.studentTutors = data;
})
.error(function () {
// TODO show error with ngToast
});
});
......@@ -25,7 +25,8 @@
ng-init="rowIndex = $index"
class="picto-grid__row">
<div
class="picto pull-left ng-class:{'picto-out': studentData.attributes.size == 'large' && (rowIndex > 3 || colIndex > 7)};"
ng-hide="studentData.attributes.size == 'large' && (rowIndex > 3 || colIndex > 7)"
class="picto pull-left"
data-row="{{ rowIndex }}"
data-column="{{ colIndex }}"
id="student-picto-{{
......@@ -117,7 +118,8 @@
ng-init="rowIndex = $index"
class="picto-grid__row">
<div
class="picto pull-left ng-class:{'picto-out': studentData.attributes.size == 'large' && (rowIndex > 3 || colIndex > 7)};"
ng-hide="studentData.attributes.size == 'large' && (rowIndex > 3 || colIndex > 7)"
class="picto pull-left"
ng-repeat="studentPicto in studentPictoRow track by $index"
ng-init="colIndex = $index"
popover="{{ studentPicto != emptyStudentPicto ? studentPicto.attributes.expression : ''}}"
......@@ -231,7 +233,8 @@
ng-init="rowIndex = $index"
class="picto-grid__row">
<div
class="picto pull-left ng-class:{'picto-out': studentData.attributes.size == 'large' && (rowIndex > 3 || colIndex > 7)};"
ng-hide="studentData.attributes.size == 'large' && (rowIndex > 3 || colIndex > 7)"
class="picto pull-left"
data-row="{{ rowIndex }}"
data-column="{{ colIndex }}"
id="student-picto-{{
......
......@@ -50,17 +50,17 @@
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs tabs_student">
<!-- 0: admin, 1: tutor, 2: therapist -->
<li role="presentation" ng-class="{'active' : nav.tab == 'collections'}" ng-if="studentData.supervision != 0">
<!-- 0: admin, 1: tutor, 2: therapist -->
<li role="presentation" ng-class="{'active' : nav.tab == 'collections'}" ng-if="studentData.license.isValid || !studentData.license.isTrial">
<a href="/app/#/student/{{studentData.id}}/collections" ng-click="nav.tab = ''"><span class="glyphicon glyphicon-th" aria-hidden="true"></span> {{ 'collections' | translate }}</a>
</li>
<li role="presentation" ng-class="{'active' : nav.tab == 'instructions'}" ng-if="studentData.supervision == 2">
<li role="presentation" ng-class="{'active' : nav.tab == 'instructions'}" ng-if="studentData.license.isValid && !user.isTutor">
<a href="/app/#/student/{{studentData.id}}/instructions" ng-click="nav.tab = 'instructions'"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{ 'instructions' | translate }}</a>
</li>
<li role="presentation" ng-class="{'active' : nav.tab == 'session'}" ng-if="studentData.supervision == 2">
<li role="presentation" ng-class="{'active' : nav.tab == 'session'}" ng-if="studentData.license.isValid && !user.isTutor">
<a href="/app/#/student/{{studentData.id}}/session" ng-click="nav.tab = 'session'"><span class="glyphicon glyphicon-transfer" aria-hidden="true"></span> {{ 'sessions' | translate }}</a>
</li>
<li role="presentation" ng-class="{'active' : nav.tab == 'reports'}" ng-if="studentData.supervision != 1">
<li role="presentation" ng-class="{'active' : nav.tab == 'reports'}" ng-if="studentData.license.isValid && !user.isTutor">
<a href="/app/#/student/{{studentData.id}}/reports" ng-click="nav.tab = 'reports'"><i class="fa fa-bar-chart" aria-hidden="true"></i> {{ 'reports' | translate }}</a>
</li>
<li role="presentation" ng-class="{'active' : nav.tab == 'setup'}">
......
......@@ -124,6 +124,8 @@ dashboardControllers.controller('SetupCtrl', function SetupCtrl(
user.phone = data.phone;
user.email = data.email;
user.lang = data.lang;
user.pic = data.pic;
user.role = data.role;
// Delete because at the beginning the variable user is bind
// with the form
......@@ -132,6 +134,9 @@ dashboardControllers.controller('SetupCtrl', function SetupCtrl(
$window.sessionStorage.user = JSON.stringify(user);
$location.path("/students");
//$scope.$apply();
})
.error(function () {
ngToast.danger({ content: $translate.instant('data_no_saved') });
......
......@@ -13,123 +13,167 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
$window,
$translate,
ngToast,
IOService) {
$scope.formdatastudent = {
username: '',
password: '',
name: '',
surname: '',
birthdate: '',
country: 'ES',
gender: 'M',
lang: 'es-es',
notes: '',
office: $scope.user.office || { name: '' }
};
$timeout,
IOService,
CONSTANTS) {
// 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: '' };
}
// --------------------------------------------------------
// Create new account
// --------------------------------------------------------
$scope.minlength = CONSTANTS.password_minlength;
// 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') });
});
// forms container
$scope.forms = {};
// slider object
$scope.slide = {
state: 'accounts',
prev: 'accounts',
back: false,
show: false,
rightTo: function (state) {
$scope.slide.back = false;
$scope.slide.prev = $scope.slide.state;
$timeout(function () {
$scope.slide.state = state;
}, 0);
},
leftTo: function (state) {
$scope.slide.back = true;
$scope.slide.prev = $scope.slide.state;
$timeout(function () {
$scope.slide.state = state;
}, 0);
}
};
// Reset form Student
$scope.resetForm = function () {
// Empty the form
$scope.formdatastudent = {
// Show student form
$scope.showForm = function () {
// Reset the form
$scope.formdata = {
username: '',
password: '',
password_confirm: '',
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: ''
license_number: null,
id_sup: $scope.user.id
};
// Hide the form
$scope.hidestudentadd = true;
$scope.slide.state ='accounts';
$scope.slide.show = true;
$('#addform').slideDown('slow', function() {});
};
$scope.hideForm = function () {
$('#addform').slideUp('slow', function() {
$scope.slide.show = false;
$scope.$apply();
});
}
/**
* Add existing account
*/
$scope.addExisting = function () {
// Send link call to server
$http.post(config.backend + '/stu/license/sup/' + $scope.user.id, {license: $scope.formdata.license_number})
.success(function (data) {
loadStudents();
$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 (err.message && err.message.search("lready linked") > 0)
errorMessage = 'student_already_linked';
else if (err && err.status === 400)
errorMessage = 'invalid_fields';
ngToast.danger($translate.instant(errorMessage));
});
}
/**
* Add Student
* Add new account
*/
$scope.add_student = function () {
var student = $scope.formdatastudent;
$scope.addNew = function (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) {
ngToast.danger({ content: $translate.instant('password_match') });
if ($scope.formdata.password_confirm.length && $scope.formdata.password !== $scope.formdata.password_confirm) {
ngToast.danger($translate.instant('password_match'));
return;
}
// password not changed (don't send it to DB)
if (!student.password_confirm.length) {
delete student.password;
delete student.password_confirm;
}
$http.post(config.backend + '/stu', student)
// Send creating call to server
$http.post(config.backend + '/stu', $scope.formdata)
.success(function (data) {
ngToast.success({ content: $translate.instant('student_added') });
// default values
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;
$scope.students.push(data);
$scope.resetForm();
$scope.hidestudentadd = true;
loadStudents();
$scope.slide.rightTo('confirmation');
})
.error(function (err) {
console.log(typeof err);
console.log(err);
var errorMessage = 'student_not_added';
if (err.message && err.message.search('nvalid license') > 0)
if (err.message && err.message.search('nvalid license') > 0 || typeof err == "string" && err.search('nvalid license') > 0)
errorMessage = 'license_invalid';
else if (err.message && err.message.search('in use') > 0)
else if (err.message && err.message.search('in use') > 0 || typeof err == "string" && err.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)
else if (err.message && err.message.search('already exists') > 0 || 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) });
});
};
}
// --------------------------------------------------------
// Students list
// --------------------------------------------------------
function loadStudents() {
// 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++) {
......@@ -149,6 +193,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) {
......@@ -173,15 +222,8 @@ 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;
};
// Main
loadStudents();
});
......@@ -24,15 +24,15 @@ dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl(
// Assign values this way (like an object) to ensure it's the parent scope
$scope.user.id = user.id;
$scope.user.name = user.name;
$scope.user.surname = user.surname;
$scope.user.role = user.role;
$scope.user.name = user.name || $translate.instant('name');
$scope.user.surname = user.surname || $translate.instant('surname');
$scope.user.pic = user.pic;
$scope.user.office = user.office;
if ($scope.user.office.name == 'no_office')
$scope.user.office.name = $translate.instant('no_office');
$scope.user.lang = user.lang;
$scope.user.isSupAdmin = user.isSupAdmin;
$scope.user.isOffice = user.isOffice;
$scope.user.isTutor = user.isTutor;
$scope.user.isAdmin = user.isAdmin;
$scope.user.isTherapist = user.isTherapist;
$scope.user.arasaacLicense = user.arasaacLicense;
// Link to setup
......
......@@ -5,16 +5,105 @@
//--------------------------
dashboardControllers.controller('SupervisorsCtrl', function SupervisorsCtrl($scope, $window, $http, config, $translate, ngToast) {
$http
.get(config.backend+'/office/get/' + $scope.user.office.id + '/supervisors')
.success(function(data, status, headers, config) {
$scope.supervisors_list = data;
console.log($scope.supervisors_list);
})
.error(function(data, status, headers, config) {
$translate('error_downloading_supervisors').then(function (translation) {
ngToast.danger({ content: translation });
$scope.inputs = {
search_str: '',
email: ''
};
$scope.supToAdd = {};
function loadSupervisors() {
$http
.get(config.backend+'/sup/' + $scope.user.id + '/supervisors')
.success(function(data, status, headers, config) {
$scope.supervisors_list = data;
console.log($scope.supervisors_list);
})
.error(function(data, status, headers, config) {
ngToast.danger($translate.instant('error_downloading_supervisors'));
});
}
/**
* Get a supervisor by their email and updates the $scope.supToAdd element.
* The email used for search is fetched from $scope.email_sup.
*/
$scope.searchSup = function () {
if (!$scope.user.isOffice) {
console.log("Forbidden action. You're not an office!");
return;
}
if ($scope.inputs.email.length == 0)
return;
// Find tutor by email
$http.get(config.backend + '/sup/email/' + $scope.inputs.email)
.success(function (data) {
if (data) {
$scope.supToAdd = data;
$scope.showmessagesupfound = true;
} else {
ngToast.danger($translate.instant('sup_not_found'));
// Hide the success message (if it exists by other query)
$scope.showmessagesupfound = false;
}
})
.error(function () {
ngToast.danger($translate.instant('sup_not_found'));
});
};
/**
* Links a new supervisor
*/
$scope.confirmLink = function () {
if (!$scope.user.isOffice) {
console.log("Forbidden action. You're not an office!");
return;
}
$scope.showmessagesupfound = false;
$http
.post(config.backend+'/sup/' + $scope.supToAdd.id + '/off/' + $scope.user.id)
.success(function(data, status, headers, config) {
loadSupervisors();
ngToast.success($translate.instant('supervisor_added_notified'));
})
.error(function(error) {
var message = typeof error == 'string' ? error : error.message;
if (message.search('already exists') > 0)
ngToast.danger($translate.instant('supervisor_already_linked'));
else if (message.search('yourself') > 0)
ngToast.danger($translate.instant('supervisor_yourself'));
else
ngToast.danger($translate.instant('no_supervisor_linked'));
});
};
/**
* Unlinks a supervisor
*/
$scope.unlinkSupervisor = function (id) {
if (!$scope.user.isOffice) {
console.log("Forbidden action. You're not an office!");
return;
}
$http
.delete(config.backend+'/sup/' + id + '/off/' + $scope.user.id)
.success(function(data, status, headers, config) {
loadSupervisors();
ngToast.success($translate.instant('supervisor_deleted'));
})
.error(function(data, status, headers, config) {
ngToast.danger($translate.instant('general_error'));
});
};
// MAIN
loadSupervisors();
});
......@@ -8,16 +8,39 @@
href="/app/#/students">
<img
class="topbar__logo__image"
ng-src="{{user.office.logoUrl}}"
alt="{{user.office.name}}"
title="{{user.office.name}}" />
src="/app/img/logo_pictogram.png"
alt="Pîctogram"
title="Pictogram" />
</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li ng-class="{active: $location.url() == '/students'}">
<a href="/app/#/students">
<i class="fa fa-users" aria-hidden="true"></i> {{ 'students' | translate }}
</a>
</li>
<li ng-if="user.isOffice" ng-class="{active: $location.url() == '/supervisor/list'}">
<a class="pointer" role="menuitem" tabindex="0" href="/app/#/supervisor/list">
<i class="fa fa-users" aria-hidden="true"></i> {{ 'supervisors' | translate }}
</a>
</li>
<li ng-if="!user.isTutor" ng-class="{active: $location.url() == '/instructions'}">
<a href="/app/#/instructions">
<i class="glyphicon glyphicon-tasks" aria-hidden="true"></i> {{ 'instructions' | translate }}
</a>
</li>
<li>
<a ng-click="own_pictos()">
<i class="glyphicon glyphicon-picture" aria-hidden="true"></i> {{ 'own_pictos' | translate }}
</a>
</li>
</ul>
<div class="topbar__supervisor nav navbar-nav navbar-right">
<div class="dropdown">
<div class="topbar__supervisor__name">
<div class="topbar__supervisor__name__fullname">{{user.getFullName()}}</div>
<div class="topbar__supervisor__name__office">{{user.office.name}}</div>
<div class="topbar__supervisor__name__role ng-binding">{{ user.role | translate }}</div>
</div>
<div
class="topbar__supervisor__avatar thumbnail"
......@@ -26,18 +49,18 @@
<img id="supervisor_profile" ng-src="{{user.pic}}" alt="Supervisor" title="Supervisor" />
</div>
<ul class="dropdown-menu" role="menu">
<li>
<!-- li>
<a class="pointer" role="menuitem" tabindex="0" href="/app/#/students">
<i class="fa fa-users" aria-hidden="true"></i>
{{ 'students' | translate }}
</a>
</li>
<li ng-if="user.isSupAdmin == true">
<li ng-if="user.isOffice">
<a class="pointer" role="menuitem" tabindex="0" href="/app/#/supervisor/list">
<i class="fa fa-users" aria-hidden="true"></i>
{{ 'supervisors' | translate }}
</a>
<li ng-if="user.isTutor == false">
<li ng-if="!user.isTutor">
<a class="pointer" role="menuitem" tabindex="0" href="/app/#/instructions">
<i class="glyphicon glyphicon-tasks" aria-hidden="true"></i>
{{ 'instructions' | translate }}
......@@ -48,7 +71,7 @@
<i class="glyphicon glyphicon-picture" aria-hidden="true"></i>
{{ 'own_pictos' | translate }}
</a>
</li>
</li -->
<li>
<a class="pointer" role="menuitem" tabindex="0" href="/app/#/setup">
<i class="glyphicon glyphicon-cog" aria-hidden="true"></i>
......@@ -65,4 +88,5 @@
</div>
</div>
</div>
</div>
</nav>
<!-- InstructionsCtrl controls here, see app.js -->
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title" translate>own_instructions</h3></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
<!-- Select to add new method -->
<div class="form-group">
<!-- Botón añadir método -->
<button ng-click="add_method()" class="btn btn-success btn-sm" popover="{{ 'add' | translate }}" popover-trigger="mouseenter">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <span translate>new_method</span>
</button>
</div>
</div>
<div class="col-sm-6 text-right" ng-if="!minimalMode">
<button class="btn btn-primary" ng-click="enable_minimal()"><i class="fa fa-eye-slash" aria-hidden="true"></i> Ver menos detalles</button>
</div>
<div class="col-sm-6 text-right" ng-if="minimalMode">
<button class="btn btn-primary" ng-click="enable_minimal()"><i class="fa fa-eye" aria-hidden="true"></i> Ver todo</button>
</div>
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<div class="row">
<div class="col-xs-4">
<h3 translate>own_instructions</h3>
</div>
<div class="col-xs-4 margin-top20">
<div class="form-group">
<!-- Botón añadir método -->
<button ng-click="add_method()" class="btn btn-success btn-sm" popover="{{ 'add' | translate }}" popover-trigger="mouseenter">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> <span translate>new_method</span>
</button>
</div>
</div>
</div>
</div>
<div class="panel-body">
<div class="row">
<div class="col-sm-12 text-right" ng-if="!minimalMode">
<button class="btn btn-primary" ng-click="enable_minimal()"><i class="fa fa-eye-slash" aria-hidden="true"></i> Ver menos detalles</button>
</div>
<div class="col-sm-12 text-right" ng-if="minimalMode">
<button class="btn btn-primary" ng-click="enable_minimal()"><i class="fa fa-eye" aria-hidden="true"></i> Ver todo</button>
</div>
</div>
<!-- Method instructions -->
<div class="method" ng-repeat="m in methods">
......
<!-- SetupCtrl controls here, see app.js -->
<div>
<div class="page-header">
<h2 translate>setup</h2>
<h3 translate>setup</h3>
</div>
<div id="signin">
<div>
<!-- Logo Pictogram -->
<!-- Formulario -->
<form name="setupForm" enctype="multipart/form-data" role="form" ng-submit="setup()">
<!-- Preview -->
<img class="thumbnail preview" ng-src="{{user.pic}}" />
<!-- Fin Cambiar imagen de perfil -->
<div class="form-group">
<!-- input type="file" ng-file-select="onFileSelect($files)" accept="image/*"></input -->
<button class="btn btn-default" ngf-select ng-model="picFile" accept="image/*" ngf-change="changeImg()">
<span class="glyphicon glyphicon-folder-open"></span> {{ 'change_picture' | translate }}
</button>
</div>
<fieldset>
<legend translate>personal_data</legend>
<div class="form-group">
<input type="text" class="form-control" id="signin_name" placeholder="{{ 'name' | translate }}" required ng-model="formdata.name" />
</div>
<div class="form-group">
<input type="text" class="form-control" id="signin_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdata.surname" />
</div>
<div class="form-group">
<input type="text" class="form-control" id="signin_address" placeholder="{{ 'address' | translate }}" ng-model="formdata.address" />
</div>
<div class="row">
<div class="col-md-3 col-md-offset-1">
<!-- Preview -->
<img class="thumbnail preview" ng-src="{{user.pic}}" />
<!-- Fin Cambiar imagen de perfil -->
<div class="form-group">
<input type="text" class="form-control" id="signin_phone" placeholder="{{ 'phone' | translate }}" ng-model="formdata.phone" />
<!-- input type="file" ng-file-select="onFileSelect($files)" accept="image/*"></input -->
<button class="btn btn-default" ngf-select ng-model="picFile" accept="image/*" ngf-change="changeImg()">
<span class="glyphicon glyphicon-folder-open"></span> {{ 'change_picture' | translate }}
</button>
</div>
</fieldset>
</div>
<div class="col-md-4">
<fieldset>
<legend translate>personal_data</legend>
<div class="form-group">
<input type="text" class="form-control" id="signin_name" placeholder="{{ 'name' | translate }}" required ng-model="formdata.name" />
</div>
<div class="form-group" ng-if="!user.isOffice">
<input type="text" class="form-control" id="signin_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdata.surname" />
</div>
<div class="form-group">
<input type="text" class="form-control" id="signin_address" placeholder="{{ 'address' | translate }}" ng-model="formdata.address" />
</div>
<div class="form-group">
<input type="text" class="form-control" id="signin_phone" placeholder="{{ 'phone' | translate }}" ng-model="formdata.phone" />
</div>
</fieldset>
<fieldset>
<legend translate>email</legend>
<div class="form-group">
<input type="email" class="form-control" id="signin_email1" placeholder="{{ 'email' | translate }}" required ng-model="formdata.email" />
</div>
</fieldset>
<fieldset>
<legend translate>language</legend>
<div class="form-group">
<select class="form-control" name="signin_language" id="signin_language" ng-model="formdata.lang">
<option value="es-es">Español</option>
<option value="en-gb">English</option>
</select>
</div>
</fieldset>
<fieldset>
<legend translate>change_password</legend>
<div class="form-group">
<input style="display:none" type="password" name="fakepasswordremembered"/>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formdata.password" />
</div>
<div class="form-group">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdata.password_confirm" />
</div>
<fieldset>
</div>
<fieldset>
<legend translate>language</legend>
<div class="form-group">
<select class="form-control" name="signin_language" id="signin_language" ng-model="formdata.lang">
<option value="es-es">Español</option>
<option value="en-gb">English</option>
</select>
</div>
</fieldset>
<div class="col-md-4">
<p class="text-center">
<button type="submit" class="btn btn-primary" translate>save</button>
</p>
</form>
<fieldset>
<legend translate>email</legend>
<div class="form-group">
<input type="email" class="form-control" id="signin_email1" placeholder="{{ 'email' | translate }}" required ng-model="formdata.email" />
</div>
</fieldset>
<fieldset>
<legend translate>change_password</legend>
<div class="form-group">
<input style="display:none" type="password" name="fakepasswordremembered"/>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formdata.password" />
</div>
<div class="form-group">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdata.password_confirm" />
</div>
<fieldset>
</div>
</div>
<!-- Fin signin -->
<div class="row">
<p class="text-right">
<button type="submit" class="btn btn-primary" translate>save</button>
</p>
</div>
</form>
</div>
<!-- Fin signin -->
<!-- DIV Modal cropper para recorte de imagen -->
<div class="modal fade" id="bootstrap-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
......
......@@ -4,37 +4,60 @@
<div class="panel-heading">
<div class="row">
<div class="col-xs-4">
<h5 translate>supervisors</h5>
<h3 translate>supervisors</h3>
</div>
<div class="col-xs-4">
<div class="col-xs-4 margin-top20">
<div class=" input-group">
<input type="text" ng-model="inputs.email" placeholder="{{ 'email' | translate }}" class="form-control">
<span class="input-group-btn" popover="{{ 'link_supervisor_desc' | translate }}" popover-trigger="mouseenter">
<button ng-click="searchSup()" class="btn btn-success btn-sm" role="button">
{{ 'link_supervisor' | translate }}
</button>
</span>
</div>
<!-- Alert and success messages for supervisor found -->
<div ng-show="{{ 'showmessagesupfound' }}" class="alert alert-info overlap-result">
<!-- Imagen de perfil del tutor -->
<img ng-src="{{supToAdd.pic}}" class="profile" alt="" title="" /> {{ supToAdd.name }} {{ supToAdd.surname }}
<a class="btn btn-default btn-sm pull-right" role="button" ng-click="confirmLink()" translate>add</a>
</div>
</div>
<div class="col-xs-4">
<div class="col-xs-4 margin-top20">
<div class=" input-group">
<input type="text" ng-model="search_sups" id="search_sups" placeholder="{{ 'filter' | translate }}" class="form-control" aria-describedby="basic-addon2">
<input type="text" ng-model="inputs.search_str" id="search_sups" placeholder="{{ 'filter' | translate }}" class="form-control" aria-describedby="basic-addon2">
<span class="input-group-addon"><span class="glyphicon glyphicon-search" id="basic-addon2" aria-hidden="true"></span></span>
</div>
</div>
</div>
</div>
</div>
<!-- Fin .panel-body -->
<div class="table-responsive">
<table class="table">
<table class="table" id="table_supervisors">
<thead class="thead-default">
<tr>
<th translate>supervisors</th>
<th translate>students</th>
<th class="col-xs-8" translate>supervisors</th>
<th class="col-xs-4" translate>students</th>
</tr>
</thead>
<tbody>
<tr class="active" ng-repeat="supervisor in supervisors_list | filter:search_sups | orderBy: 'name'">
<tr class="active" ng-repeat="supervisor in supervisors_list | filter:inputs.search_str | orderBy: 'name'">
<td>
<div>
<div class="row">
<div class="col-xs-2">
<div class="thumbnail">
<img ng-src="/upload/supervisorAvatar/{{supervisor.pic}}" alt="Supervisor" title="Supervisor" />
<img ng-src="{{supervisor.pic}}" alt="Supervisor" title="Supervisor" />
</div>
</div>
<div class="col-xs-10">
<div class="col-xs-1">
<a ng-click="unlinkSupervisor(supervisor.id)" class="delete_sup" title="{{ 'unlink' | translate }}">
<span class="glyphicon glyphicon-remove-circle text-danger" aria-hidden="true"></span>
</a>
</div>
<div class="col-xs-9">
<h4>{{supervisor.name}} {{supervisor.surname}}</h4>
<p><i class="fa fa-envelope" aria-hidden="true">&nbsp</i><a href="mailto:{{supervisor.email}}">{{supervisor.email}}</a></p>
<p><i class="fa fa-phone-square" aria-hidden="true"></i>&nbsp<a href="tel:{{supervisor.phone}}">{{supervisor.phone}}</a></p>
......@@ -52,6 +75,9 @@
</tr>
</tbody>
</table>
<div class="alert alert-warning" ng-if="supervisors_list.length == 0">
<strong translate>no_supervisors</strong> {{ 'no_supervisors_desc' | translate }}
</div>
</div>
</div>
......@@ -8,6 +8,7 @@ dashboardControllers.controller('TranslateController', function(
$scope,
$window,
$http,
$timeout,
config,
ngToast,
vcRecaptchaService
......@@ -44,7 +45,7 @@ dashboardControllers.controller('TranslateController', function(
//Server PUT
$http.put(config.backend + '/sup/' + $scope.user.id, { "lang": langKey })
.success(function (data) {
ngToast.success({ content: $translate.instant('data_saved') });
ngToast.success({ content: $translate.instant('language_change_warning') });
console.log("OK: Update supervisor language");
})
.error(function () {
......@@ -63,7 +64,5 @@ dashboardControllers.controller('TranslateController', function(
vcRecaptchaService.useLang(0, langKey.substr(0,2));
} catch (err) {}
// Reload page
$window.location.reload();
};
});
......@@ -58,10 +58,18 @@
margin-top: 15px;
}
.margin-top20 {
margin-top: 20px;
}
.margin-top7 {
margin-top: 7px;
}
.margin-top10 {
margin-top: 10px;
}
.table .table-striped .striped {
background-color: lightgray !important;
}
......@@ -870,7 +878,8 @@ img.profile{
#user_tutors .list-group-item:hover .delete_tutor,
#user_sups .list-group-item:hover .delete_sup,
#table_students tr:hover .delete_stu{
#table_supervisors tr:hover .delete_sup,
#table_students tr:hover .delete_stu {
opacity: 1;
}
......@@ -1098,3 +1107,82 @@ input.editable.scene-name {
font-size: 16px;
font-weight: 600;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
/* Cambiar tamaño imagen */
.img-200 {
margin-top: 25px;
width: 200px;
}
.img-120 {
margin-top: 25px;
width: 120px;
}
/* Estilos para ngSwitch */
.switch-panel-body {
position:relative;
width: 930px;
margin: auto;
overflow:hidden;
}
.height600 {
height: 600px;
}
.height400 {
height: 400px;
}
.switch-animation,
.switch-animation-back {
width: 900px;
}
.switch-animation.ng-enter,
.switch-animation.ng-leave,
.switch-animation-back.ng-enter,
.switch-animation-back.ng-leave {
-webkit-transition:0.5s linear all;
-moz-transition:0.5s linear all;
-o-transition:0.5s linear all;
transition: 0.5s linear all;
position:absolute;
}
.switch-animation.ng-enter,
.switch-animation-back.ng-leave.ng-leave-active {
left:100%;
}
.switch-animation.ng-leave,
.switch-animation.ng-enter.ng-enter-active,
.switch-animation-back.ng-leave,
.switch-animation-back.ng-enter.ng-enter-active {
left:0;
}
.switch-animation.ng-leave.ng-leave-active,
.switch-animation-back.ng-enter {
left:-100%;
}
.switch-panel {
overflow:hidden;
}
.overlap-result {
z-index:20;
position:absolute;
width:100%
}
......@@ -29,11 +29,13 @@
padding: 0.5em;
&__fullname,
&__office {
&__office,
&__role {
text-align: right;
}
&__office {
&__office,
&__role {
opacity: 0.75;
}
}
......
......@@ -8,5 +8,6 @@
"therapist_office_request": "{{ name }}, with email {{ email }}, is requesting to be linked as therapist to any of your students.",
"tutor_office_request": "{{ name }}, with email {{ email }}, is requesting to be linked as tutor/father/mother to any of your students.",
"welcome_msg1": "Welcome to Pictogram, {{ name }}!",
"welcome_msg2": "Your account is now active. You can proceed to"
"welcome_msg2": "Your account is now active. You can proceed to",
"office_link": "The office/center \"{{ name }}\" with email \"{{ email }}\" has added you as part of its team in Pictogram."
}
......@@ -8,5 +8,6 @@
"therapist_office_request": "El/la terapeuta {{ name }}, con correo electrónico {{ email }}, pide ser asociado a algún estudiante.",
"tutor_office_request": "El/la tutor/a/padre/madre {{ name }}, con correo electrónico {{ email }}, pide ser asociado a algún estudiante.",
"welcome_msg1": "¡Bienvenido a Pictogram, {{ name }}!",
"welcome_msg2": "Su cuenta está ahora activa, por lo que puede"
"welcome_msg2": "Su cuenta está ahora activa, por lo que puede",
"office_link": "El centro/gabinete \"{{ name }}\", con correo electrónico \"{{ email }}\" le ha añadido como parte de su equipo."
}
......@@ -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
......
......@@ -21,6 +21,8 @@ module.exports.policies = {
'*': false,
// TODO: habría que revisar estas políticas, porque creo que hay bastantes brechas abiertas
ActionController: {
create: ['tokenAuth'],
createlist: ['tokenAuth']
......@@ -44,11 +46,11 @@ module.exports.policies = {
},
MetaMethodController: {
supVisible: ['tokenAuth'],
supOwned: ['tokenAuth'],
create: ['tokenAuth'],
update: ['tokenAuth'],
destroy: ['tokenAuth']
supVisible: ['tokenAuth'],
supOwned: ['tokenAuth'],
create: ['tokenAuth'],
update: ['tokenAuth'],
destroy: ['tokenAuth']
},
MethodController: {
......@@ -59,13 +61,6 @@ module.exports.policies = {
destroy: ['tokenAuth'],
},
OfficeController: {
getAll: true,
get: ['tokenAuth'],
getBasic: true,
supervisors: ['tokenAuth', 'isAdminOrIsSupAdmin']
},
PictoController: {
upload: ['tokenAuth'],
add_tag: ['tokenAuth'],
......@@ -81,13 +76,13 @@ module.exports.policies = {
},
SceneController:{
create: ['tokenAuth', 'isSupervisorOfStudent'],
update: ['tokenAuth', 'isSupervisorOfStudent'],
destroy: ['tokenAuth', 'isSupervisorOfStudent'],
duplicate: ['tokenAuth', 'isSupervisorOfStudent'],
getScene: ['tokenAuth'],
getStudentScenes: ['tokenAuth'],
scene: true
create: ['tokenAuth', 'isSupervisorOfStudent'],
update: ['tokenAuth', 'isSupervisorOfStudent'],
destroy: ['tokenAuth', 'isSupervisorOfStudent'],
duplicate: ['tokenAuth', 'isSupervisorOfStudent'],
getScene: ['tokenAuth'],
getStudentScenes: ['tokenAuth'],
scene: true
},
ServerController: {
......@@ -97,11 +92,11 @@ module.exports.policies = {
StudentController: {
eternal: true,
getInfo: ['tokenAuth', 'isSupervisorOfStudentOrIsSupAdminOrIsStudent'],
getInfo: ['tokenAuth', 'isStudentOrSupervisorOfStudent'],
supervisors: ['tokenAuth'],
therapists: ['tokenAuth'],
tutors: ['tokenAuth'],
link_supervisor: ['tokenAuth', 'isSupAdmin'],
link_supervisor: ['tokenAuth'],
pictos: ['tokenAuth'],
methods: ['tokenAuth'],
lasttries: ['tokenAuth'],
......@@ -113,7 +108,7 @@ module.exports.policies = {
update_legend: ['tokenAuth'],
update_category: ['tokenAuth', 'isSupervisorOfStudent'],
login: true,
create: ['tokenAuth', 'isSupAdmin'],
create: ['tokenAuth'],
upload: ['tokenAuth'],
upload_sound: ['tokenAuth'],
add_picto: ['tokenAuth', 'isSupervisorOfStudent'],
......@@ -123,8 +118,8 @@ module.exports.policies = {
action: true,
config: true,
actions_batch: ['tokenAuth'],
delete: ['tokenAuth', 'isSupAdmin'],
unlink_supervisor: ['tokenAuth', 'isSupAdmin'],
delete: ['tokenAuth', 'isAdmin'],
unlink_supervisor: ['tokenAuth', 'isSupervisorOfStudent'],
delete_picto: ['tokenAuth', 'isSupervisorOfStudent'],
getActiveScene: ['tokenAuth'],
getScenes: ['tokenAuth'],
......@@ -143,7 +138,8 @@ module.exports.policies = {
list: ['tokenAuth', 'isAdmin'],
students: ['tokenAuth'],
pictos: ['tokenAuth'],
getByEmail: ['tokenAuth'],
getByEmail: ['tokenAuth', 'isOffice'],
getFromOfficeByEmail: ['tokenAuth', 'isOffice'],
update: ['tokenAuth'],
create: true,
login: true,
......@@ -154,7 +150,10 @@ module.exports.policies = {
upload: ['tokenAuth'],
subscribe: ['tokenAuth'],
unsubscribe: ['tokenAuth'],
delete: ['tokenAuth', 'isAdmin']
delete: ['tokenAuth', 'isAdmin'],
supervisors: ['tokenAuth', 'isOffice'],
link_supervisor: ['tokenAuth', 'isOffice'],
unlink_supervisor: ['tokenAuth', 'isOffice']
},
TryController: {
......
......@@ -55,11 +55,6 @@ module.exports.routes = {
'PUT /method/template/:id': 'MetaMethodController.update',
'DELETE /method/template/:id': 'MetaMethodController.destroy',
'GET /office/get_all': 'OfficeController.getAll',
'GET /office/:code': 'OfficeController.getBasic',
'GET /office/get/:id': 'OfficeController.get',
'GET /office/get/:id/supervisors': 'OfficeController.supervisors',
'GET /picto/:lang/pic_categories/:id_cat': 'PictoController.categories',
'GET /picto/:lang/pic_fromcategory/:id_cat': 'PictoController.fromcategory',
'GET /picto/:lang/pic_fromSymbolStx/page/:page/limit/:limit': 'PictoController.fromSymbolStx',
......@@ -87,8 +82,7 @@ module.exports.routes = {
'GET /stu/:id_stu': 'StudentController.getInfo',
'GET /stu/:id_stu/supervisors': 'StudentController.supervisors',
'GET /stu/:id_stu/therapists': 'StudentController.therapists',
'GET /stu/:id_stu/tutors': 'StudentController.tutors',
'POST /stu/license/sup/:id_sup': "StudentController.link_supervisor",
'POST /stu/:id_stu/sup/:id_sup': 'StudentController.link_supervisor',
'GET /stu/:id_stu/pictos': 'StudentController.pictos',
'GET /stu/:id_stu/activeScene': 'StudentController.getActiveScene',
......@@ -104,7 +98,7 @@ 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/upload': 'StudentController.upload',
......@@ -125,9 +119,13 @@ module.exports.routes = {
'GET /sup/all': 'SupervisorController.list',
'GET /sup/:id/students': 'SupervisorController.students',
'GET /sup/:id/supervisors': 'SupervisorController.supervisors',
'POST /sup/:id_sup/off/:id_off': 'SupervisorController.link_supervisor',
'DELETE /sup/:id_sup/off/:id_off': 'SupervisorController.unlink_supervisor',
'GET /sup/:id/pictos': 'SupervisorController.pictos',
'GET /sup/email/:email': 'SupervisorController.getByEmail',
'GET /sup/off/email/:email': 'SupervisorController.getFromOfficeByEmail',
'GET /sup/changepass/:email': 'SupervisorController.request_change_password',
'GET /sup/arasaac_license/:id': 'SupervisorController.accept_arasaac',
'PUT /sup/changepass': 'SupervisorController.change_password',
......
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