Commit 3120a70c by Jose Antonio

Merged branch develop into develop

parents 74758708 200f8d09
Showing with 481 additions and 309 deletions
......@@ -62,6 +62,10 @@ android/Pictogram/commonlibrary/build
android/Pictogram/tablet/build
android/Pictogram/watch/build
android/Pictogram/build
android/Pictogram/supervisor_tablet/supervisor_tablet.iml
android/Pictogram/tabletlibrary/tabletlibrary.iml
android/Pictogram/yotta_tablet/yotta_tablet.iml
android/Pictogram/watch/watch.iml
# OS generated files #
######################
......
......@@ -142,7 +142,7 @@ public class NetService implements Runnable, RestapiWrapper.iSilentLogin {
public void restart_app(boolean direct_login) {
for (iNetServiceStatus listener: listeners)
if (listener instanceof iNetServiceDevice) ((iNetServiceDevice)listener).restart_app(direct_login);
PCBcontext.unset_user();
PCBcontext.unset_user();
}
public void restart_app(Intent intent, boolean direct_login) {
for (iNetServiceStatus listener: listeners)
......@@ -155,66 +155,68 @@ public class NetService implements Runnable, RestapiWrapper.iSilentLogin {
*/
@Override
public void run() {
PCBcontext.getRestapiWrapper().ask(ping_session, new RestapiWrapper.iRestapiListener() {
@Override
public void preExecute() {
}
@Override
public void result(JSONArray result) {
}
try {
PCBcontext.getRestapiWrapper().ask(ping_session, new RestapiWrapper.iRestapiListener() {
@Override
public void preExecute() {
@Override
public void result(JSONObject result) {
try {
float version=Float.valueOf(result.getString("version")).floatValue();
if (PCBcontext.getActivityContext()!=null && version> DeviceHelper.getAppVersion(PCBcontext.getActivityContext())) {
Log.e(LOG_TAG,"New version is required! from v"+DeviceHelper.getAppVersion(PCBcontext.getContext())+" to v"+version);
newVersionAlert(PCBcontext.getActivityContext(),version);
}
} catch (JSONException e) {
Log.e(LOG_TAG,"PING JSON ERROR: "+result+" "+e.getMessage());
}
if (!updated) {
lastRestfullSynchro=new Date().getTime();
updated = true;
if (PCBcontext.is_user_logged()) //si el usuario aun no hizo login, en realidad no es necesario hacer nada
// Comprobar si hay usuario offline, para hacer login transparente
if (PCBcontext.is_user_offline()){
Log.i(LOG_TAG, "PCB online login from offline login");
login();
} else if (PCBcontext.is_user_online()){
Log.i(LOG_TAG, "PCB reconnect");
PCBcontext.getRoom().connect();
PCBcontext.getVocabulary().synchronize();
PCBcontext.getActionLog().batch();
}
notifyStatus();
@Override
public void result(JSONArray result) {
}
else {
//cada restfullSynchroTimming aprox. se fuerza sincronización de vocabulario y configuración de usuario
long now=new Date().getTime();
if (PCBcontext.is_user_logged()) {
if (restfullSynchroTimming>0 && (now - lastRestfullSynchro > restfullSynchroTimming)) {
Log.i(LOG_TAG, "Vocabulary request");
PCBcontext.getVocabulary().synchronize();
synchronizeStudentAttributes();
lastRestfullSynchro = now;
}
}
else lastRestfullSynchro=new Date().getTime();
}
}
@Override
public void result(JSONObject result) {
try {
float version = Float.valueOf(result.getString("version")).floatValue();
if (PCBcontext.getActivityContext() != null && version > DeviceHelper.getAppVersion(PCBcontext.getActivityContext())) {
Log.e(LOG_TAG, "New version is required! from v" + DeviceHelper.getAppVersion(PCBcontext.getContext()) + " to v" + version);
newVersionAlert(PCBcontext.getActivityContext(), version);
}
} catch (JSONException e) {
Log.e(LOG_TAG, "PING JSON ERROR: " + result + " " + e.getMessage());
}
if (!updated) {
lastRestfullSynchro = new Date().getTime();
updated = true;
if (PCBcontext.is_user_logged()) //si el usuario aun no hizo login, en realidad no es necesario hacer nada
// Comprobar si hay usuario offline, para hacer login transparente
if (PCBcontext.is_user_offline()) {
Log.i(LOG_TAG, "PCB online login from offline login");
login();
} else if (PCBcontext.is_user_online()) {
Log.i(LOG_TAG, "PCB reconnect");
PCBcontext.getRoom().connect();
PCBcontext.getVocabulary().synchronize();
PCBcontext.getActionLog().batch();
}
} else {
//cada restfullSynchroTimming aprox. se fuerza sincronización de vocabulario y configuración de usuario
long now = new Date().getTime();
if (PCBcontext.is_user_logged()) {
if (restfullSynchroTimming > 0 && (now - lastRestfullSynchro > restfullSynchroTimming)) {
Log.i(LOG_TAG, "Vocabulary request");
PCBcontext.getVocabulary().synchronize();
synchronizeStudentAttributes();
lastRestfullSynchro = now;
}
} else lastRestfullSynchro = new Date().getTime();
}
@Override
public void error(RestapiWrapper.HTTPException e) {
setOffline(e);
}
});
}
@Override
public void error(RestapiWrapper.HTTPException e) {
setOffline(e);
}
});
notifyStatus();
}catch(Exception e) {
Log.e(LOG_TAG,"THREAD NOT WORKING BECAUSE:"+e.getMessage());
this.restart_app(true);
}
}
private void synchronizeStudentAttributes() {
......
......@@ -39,13 +39,14 @@ public class StudentTalk implements Emitter.Listener {
Log.i(this.getClass().getName(), "raw Received message " +msg.toString());
int id=msg.getInt("id");
String username=msg.getString("username");
String name=msg.getString("name");
String surname=msg.getString("surname");
String gender=msg.getString("gender");
String pic=msg.getString("pic");
String lang=msg.getString("lang");
String attributes=msg.getString("attributes");
User user=PCBcontext.getPcbdb().getCurrentUser();
User updatedUser=new User(id, username, user.get_pwd_stu(), username, surname, pic, gender, lang, attributes,
User updatedUser=new User(id, username, user.get_pwd_stu(), name, surname, pic, gender, lang, attributes,
user.get_id_sup(), user.get_email_sup(), user.get_pwd_sup(), user.get_name_sup(), user.get_surname_sup(), user.get_url_img_sup(), user.get_gender_sup(),
user.get_lang_sup(), user.get_tts_engine_sup(), user.get_office());
Log.i(this.getClass().getName(), "Attributes" +attributes+" listeners:"+listeners.length);
......
......@@ -37,6 +37,11 @@ import com.yottacode.pictogram.tabletlibrary.cropper.util.PaintUtil;
public class CropImageView extends ImageView {
// Private Constants ///////////////////////////////////////////////////////////////////////////
private static final int CAMERA_PIC_REQUEST = 1;
private static final int GALLERY_PIC_REQUEST = 2;
private static final int VERTICAL_ORIENTATION = 200;
private static final int HORIZONTAL_ORIENTATION = 300;
@SuppressWarnings("unused")
private static final String TAG = CropImageView.class.getName();
......@@ -248,8 +253,10 @@ public class CropImageView extends ImageView {
* Gets the cropped image based on the current crop window.
*
* @return a new Bitmap representing the cropped image
* @param origin
* @param orientation
*/
public Bitmap getCroppedImage() {
public Bitmap getCroppedImage(int origin, int orientation) {
final Drawable drawable = this.getDrawable();
if (drawable == null || !(drawable instanceof BitmapDrawable)) {
return null;
......@@ -264,8 +271,8 @@ public class CropImageView extends ImageView {
final float scaleY = matrixValues[Matrix.MSCALE_Y]; //==1
// Extract the translation values.
final float transX = matrixValues[Matrix.MTRANS_X];
final float transY = matrixValues[Matrix.MTRANS_Y];
final float transX = (origin == CAMERA_PIC_REQUEST && orientation== HORIZONTAL_ORIENTATION ) ? matrixValues[Matrix.MTRANS_Y] : matrixValues[Matrix.MTRANS_X];
final float transY = (origin == CAMERA_PIC_REQUEST && orientation== HORIZONTAL_ORIENTATION ) ? matrixValues[Matrix.MTRANS_X] : matrixValues[Matrix.MTRANS_Y];
final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap();
/*Log.i("DETALLES","ScaleX: "+scaleX + "- ScaleY: "+scaleY);
......
......@@ -25,6 +25,8 @@ public class EditPictoActivity extends Activity {
public static final int EDIT_PICTO_REQUEST = 2288;
public static final String TRANSCRIPTION = "textPicto";
public static final String IMAGE_PICTO = "imagePicto";
public static final String IMAGE_ORIGIN = "imageOrigin";
public static final String IMAGE_ORIENTATION = "imageOrientation";
// Activity Methods ////////////////////////////////////////////////////////////////////////////
......@@ -47,6 +49,9 @@ public class EditPictoActivity extends Activity {
cropImageView.setGuidelines(2);
cropImageView.setAspectRatio(1,1);
final int orientation = getIntent().getExtras().getInt(EditPictoActivity.IMAGE_ORIENTATION);
final int origin = getIntent().getExtras().getInt(EditPictoActivity.IMAGE_ORIGIN);
String legendText = getIntent().getExtras().getString(EditPictoActivity.TRANSCRIPTION);
if(legendText != null) {
Log.i("DETALLES","Llega el intent al layout recortar, con texto: " + legendText);
......@@ -71,13 +76,14 @@ public class EditPictoActivity extends Activity {
okButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Bitmap croppedImage = cropImageView.getCroppedImage();
final Bitmap croppedImage = cropImageView.getCroppedImage(origin,orientation);
//Escalar y guardarla al server
croppedImage.createScaledBitmap(croppedImage,96,96,true);
Bitmap finalImage = null;
finalImage.createScaledBitmap(croppedImage,96,96,true);
Intent intent = getIntent();
intent.putExtra(EditPictoActivity.TRANSCRIPTION,legend.getText().toString());
Log.i("DETALLES","sale el intent al layout recortar, con texto: " + legend.getText().toString());
cropImageView.setImageBitmap(croppedImage);
cropImageView.setImageBitmap(finalImage);
setResult(RESULT_OK,intent);
finish();
}
......
......@@ -57,6 +57,7 @@ import com.yottacode.pictogram.tabletlibrary.net.NetServiceTablet;
import com.yottacode.pictogram.tools.Img;
import com.yottacode.pictogram.tools.PCBcontext;
import com.yottacode.pictogram.tts.TTSHelper;
import com.yottacode.tools.GUITools;
import org.json.JSONException;
import org.json.JSONObject;
......@@ -76,6 +77,9 @@ public class PictogramActivity extends Activity implements VocabularyTalk.iVocab
private static final int CAMERA_PIC_REQUEST = 1;
private static final int GALLERY_PIC_REQUEST = 2;
private static final int VERTICAL_ORIENTATION = 200;
private static final int HORIZONTAL_ORIENTATION = 300;
private static final int MAX_WIDTH = 700;
private static final int MAX_HEIGHT = 350;
......@@ -1052,16 +1056,22 @@ protected void showOnlyTape(boolean onlyTape) {
if (!student_view || !PCBcontext.getPcbdb().getCurrentUser().is_teacher()) {
PCBcontext.getPcbdb().getCurrentUser().get_Img_sup().update_id(student_view ? PCBcontext.getDevice().getLastSupId() : User.NO_SUPERVISOR);
nextActivity = intent;
} else {
PCBcontext.getPcbdb().getCurrentUser().get_Img_sup().update_id(PCBcontext.getDevice().getLastSupId());
nextActivity = new Intent(this, SessionActivity.class);
}
} else
if (!PCBcontext.getNetService().online())
GUITools.show_alert(PictogramActivity.this, R.string.session_noinet);
else {
PCBcontext.getPcbdb().getCurrentUser().get_Img_sup().update_id(PCBcontext.getDevice().getLastSupId());
nextActivity = new Intent(this, SessionActivity.class);
}
in = R.anim.rightin;
out = R.anim.rightout;
overridePendingTransition(R.anim.leftin, R.anim.leftout);
} else if (firstTouchX > event.getX() + 150) { //derecha a izquierda
if (!student_view && PCBcontext.getPcbdb().getCurrentUser().is_teacher() ) {
nextActivity = new Intent(this, SessionActivity.class);
if (!PCBcontext.getNetService().online())
GUITools.show_alert(PictogramActivity.this, R.string.session_noinet);
else
nextActivity = new Intent(this, SessionActivity.class);
} else {
PCBcontext.getPcbdb().getCurrentUser().get_Img_sup().update_id(student_view ? PCBcontext.getDevice().getLastSupId() : User.NO_SUPERVISOR);
nextActivity = intent;
......@@ -1098,7 +1108,7 @@ protected void showOnlyTape(boolean onlyTape) {
case CAMERA_PIC_REQUEST: //Captura de foto
if (data != null && resultCode==RESULT_OK) {
imagen = (Bitmap) data.getExtras().get("data");
this.launchActivity(imagen);
this.launchActivity(imagen,CAMERA_PIC_REQUEST);
//Log.i("DETALLES", "Llega el intent del menú, con texto: " + legend);
} else {
if(resultCode!=RESULT_OK) //Si no quieres la foto que has echado
......@@ -1122,7 +1132,7 @@ protected void showOnlyTape(boolean onlyTape) {
imagen = BitmapFactory.decodeFile(filePath);
/** Tras echar foto llamar a la actividad de recortar y le paso la leyenda para si tiene anteriormente o null, y la imagen a recortar */
//-->GERMAN: legend != null ? legend : null es lo mismo que simplemente legend
this.launchActivity(imagen);
this.launchActivity(imagen,GALLERY_PIC_REQUEST);
}else{
startActivity(new Intent(this, PictogramActivity.class));
}
......@@ -1138,9 +1148,11 @@ protected void showOnlyTape(boolean onlyTape) {
/**Para cambiar la activity de PictogramActivity a EditPictoActivity
* @param image
*/
public void launchActivity(Bitmap image){
public void launchActivity(Bitmap image, int originImage){
Intent intent = new Intent(this, EditPictoActivity.class);
if(image!=null) {
float aspectFactor = 1;
float bWidth = image.getWidth();
......@@ -1167,6 +1179,9 @@ protected void showOnlyTape(boolean onlyTape) {
byte[] byteArray = stream.toByteArray();
intent.putExtra(EditPictoActivity.IMAGE_PICTO, byteArray);
intent.putExtra(EditPictoActivity.IMAGE_ORIGIN, originImage == CAMERA_PIC_REQUEST ? CAMERA_PIC_REQUEST : GALLERY_PIC_REQUEST);
intent.putExtra(EditPictoActivity.IMAGE_ORIENTATION, bWidth > bHeight ? HORIZONTAL_ORIENTATION : VERTICAL_ORIENTATION);
if(picto!=null) {
intent.putExtra(EditPictoActivity.TRANSCRIPTION, picto.get_translation());
picto = null;
......
package com.yottacode.pictogram.tabletlibrary.net;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import com.yottacode.pictogram.dao.User;
......@@ -22,11 +24,12 @@ public class NetServiceTablet implements NetService.iNetServiceDevice {
private static NotificationCompat.Builder builder;
private PictogramActivity pictogramActivity;
int notifyID = 666;
public void build() {
this.builder = new NotificationCompat.Builder(PCBcontext.getContext());
/* Intent resultIntent = new Intent(PCBcontext.getContext(), PictogramActivity.class);
this.builder = new NotificationCompat.Builder(PCBcontext.getContext()).setAutoCancel(true);
/*Intent resultIntent = new Intent(PCBcontext.getContext(), PictogramActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(PCBcontext.getContext());
stackBuilder.addParentStack(PictogramActivity.class);
stackBuilder.addNextIntent(resultIntent);
......@@ -36,8 +39,8 @@ public class NetServiceTablet implements NetService.iNetServiceDevice {
}
public void notifyStatus(boolean updated) {
int notifyID = 1;
String user="";
if (PCBcontext.getPcbdb()!=null) {
user=PCBcontext.getPcbdb().getCurrentUser().get_name_stu()+" "+PCBcontext.getPcbdb().getCurrentUser().get_surname_stu();
if (PCBcontext.getPcbdb().getCurrentUser().is_supervisor())
......@@ -57,7 +60,6 @@ public class NetServiceTablet implements NetService.iNetServiceDevice {
mNotificationManager.notify(notifyID, builder.build());
}
public void closeNotifyStatus(){
int notifyID = 1;
NotificationManager mNotificationManager =
(NotificationManager) PCBcontext.getContext().getSystemService(PCBcontext.getContext().NOTIFICATION_SERVICE);
mNotificationManager.cancel(notifyID);
......
......@@ -64,14 +64,13 @@
android:orientation="horizontal"
android:adjustViewBounds="true"
android:scaleType="centerInside"
android:layout_marginLeft="5dp"
android:maxWidth="700px"
android:maxHeight="350px"
android:layout_below="@+id/title"
android:layout_alignParentStart="true"
android:layout_marginTop="55dp"
android:minHeight="125dp"
android:layout_gravity="center_horizontal" />
android:layout_gravity="center_horizontal"
android:maxHeight="350px"
android:maxWidth="700px" />
<EditText
android:layout_width="wrap_content"
......
......@@ -15,7 +15,7 @@
<string name="session_closed_ok">Sesión grabada correctamente. Hora</string>
<string name="session_closed_fail">Sesión no cerrada. Por favor cierre la sesión en el panel de control de Pictogram Tablet</string>
<string name="session_pause_error">Error pausando la sesión</string>
<string name="session_noinet">No hay conexión con el servidor. Por favor, asegúrese que tiene conexión a Internet</string>
<string name="session_noinet">Conexión a Internet necesaria para grabar sesiones. Por favor, asegúrese que tiene conexión</string>
<string name="session_inetok">Conexión con el servidor restablecida</string>
<string name="session_log_startingsession">iniciando sesión</string>
<string name="session_log_closingsession">cerrando sesión</string>
......
......@@ -17,7 +17,7 @@
<string name="session_closed_ok">Sesión grabada correctamente</string>
<string name="session_closed_fail">Sesión no cerrada. Por favor intente cerrarla desde</string>
<string name="session_pause_error">Error pausando la sesión</string>
<string name="session_noinet">No hay conexión con el servidor. Por favor, asegúrese que tiene conexión a Internet</string>
<string name="session_noinet">Conexión a Internet necesaria para grabar sesiones. Por favor, asegúrese que tiene conexión</string>
<string name="session_inetok">Conexión con el servidor restablecida</string>
<string name="session_log_startingsession">iniciando sesión</string>
<string name="session_log_closingsession">cerrando sesión</string>
......
......@@ -22,6 +22,7 @@ public class AppContext extends Application {
instance = this;
registerKioskModeScreenOffReceiver();
startKioskService(); // Service for restarting the app when another app go to foreground
}
private void registerKioskModeScreenOffReceiver() {
......
......@@ -366,6 +366,7 @@ CREATE TABLE IF NOT EXISTS `supervisor` (
`gender` char(1) COLLATE utf8_unicode_ci NOT NULL,
`pic` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'defaultAvatar.jpg',
`address` varchar(180) COLLATE utf8_unicode_ci DEFAULT NULL,
`postal_code` char(10) COLLATE utf8_unicode_ci NOT NULL,
`country` varchar(2) COLLATE utf8_unicode_ci DEFAULT NULL,
`email` varchar(80) COLLATE utf8_unicode_ci NOT NULL,
`phone` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
......
# Changes
Changes to be performed manually in servers to upgrade
AngularJS
## AngularJS
(already done in dev)
- angular-re-captcha has been replaced by angular-recaptcha, so bower has to be run
- reinstall ui-bootstrap
- replace angular-file-upload by ng-file-upload
`bower install`
## Database
Database
(already done in dev)
- reload trigers-enrolments-integrity-constraints.sql
- alter table supervisor to add postal_code:
`alter table supervisor add column `postal_code` char(10) COLLATE utf8_unicode_ci NOT NULL;`
- alter table office to add postal_code:
`alter table office add column `postal_code` char(10) COLLATE utf8_unicode_ci NOT NULL;`
- remove max_students and current_students columns from offices:
`alter table office drop column max_students;`
`alter table office drop column current_students;`
- copy postal_code value from office to its supervisors
`update supervisor as sup inner join office as off on off.id = sup.id_off set sup.postal_code = off.postal_code;`
- alter table office
`alter table office modify logo_url varchar(240) default null;`
......@@ -102,7 +102,8 @@ module.exports = {
*/
getAll: function (req, res) {
Office.find().populate('admin').then(function (offices) {
res.ok(offices);
// return all offices filtering out personal ones
res.ok(offices.filter(o => o.name !== 'no_office'));
})
.catch(function () {
res.serverError();
......
......@@ -71,10 +71,13 @@ module.exports = {
Supervisor.findOneByEmail(email).then(function (supervisor) {
if (!supervisor)
return res.notFound("Supervisor not found")
throw new Error("Supervisor not found")
if (!bcrypt.compareSync(password, supervisor.password))
throw res.unauthorized("Invalid email/password")
throw new Error("Invalid email/password");
if (supervisor.active == 0)
throw new Error("This account has not been activated");
return (supervisor);
......@@ -100,7 +103,7 @@ module.exports = {
}
if (!supervisor.isSupAdmin && !stuSup)
throw res.unauthorized("Supervisor without students");
throw new Error("Supervisor without students");
return res.ok({
user: supervisor,
......@@ -108,9 +111,8 @@ module.exports = {
token: sailsTokenAuth.issueToken(supervisor, sails.config.jwt.expiresInMinutes)
});
})
.catch(function (err) {
return res.serverError("Error when connecting to database");
return res.badRequest(err.message);
});
},
......@@ -119,7 +121,7 @@ module.exports = {
* Activate the user account specified
* @param {request} req
* {
* "token": "12398123aas78sf798as7d987234" // Encryted code with supervisor ID
* "token": "12398123aas78sf798as7d987234" // Encryted code with supervisor ID, siging role and id_off
* }
* @param {response} login view
* {
......@@ -132,21 +134,67 @@ module.exports = {
* }
*/
activate: function (req, res) {
if (!req.params.token)
return res.badRequest("Invalid activation URL");
sailsTokenAuth.verifyToken(req.params.token, function(err, token) {
if (err)
return res.badRequest("Invalid token");
Supervisor.findOne(token).then(function (supervisor) {
Supervisor.findOne(token.id_sup)
.then(function (supervisor) {
if (!supervisor)
throw new Error("Error when looking for user");
supervisor.active = true;
delete supervisor.password;
supervisor.save();
return res.view('accountActivated', {sup: supervisor, login_url: 'https://' + req.headers.host + '/app'});
// an email has to be sent to office administrators
if (token.role == 'tutor_office' || token.role === 'therapist_office') {
Office.findOne(token.id_off)
.populate('admin')
.then((off) => {
if (!off)
throw new Error("Office not found: ");
var message = sails.__({
phrase: token.role + '_request',
locale: supervisor.lang
}, {name: supervisor.name + " " + supervisor.surname, email: supervisor.email});
mailService.mailer()
.send({
to: off.admin.email,
text: message
})
.then(() => {})
.catch((err) => {throw err});
})
.catch((err) => {throw err;});
}
// welcome message is returned
return res.view('accountActivated', {
welcome_msg1: sails.__({
phrase: 'welcome_msg1',
locale: supervisor.lang
}, {name: supervisor.name}),
welcome_msg2: sails.__({
phrase: 'welcome_msg2',
locale: supervisor.lang
}),
login_url: 'https://' + req.headers.host + '/app',
login: sails.__('login')
});
})
.catch(function (err) {
return res.serverError("Error when activating account " + err);
return res.serverError(err.message ? err.message : 'Supervisor not found');
});
});
},
......@@ -212,61 +260,97 @@ module.exports = {
*/
create: function (req, res) {
var params = req.params.all();
var supervisor;
// Send email confirmation
function sendConfirmationMail(cb) {
console.log("mail------------\n" + JSON.stringify(supervisor));
var token = sailsTokenAuth.issueToken({
id_sup: supervisor.id,
role: params.role,
id_off: params.id_off,
}, 60*24*7); // expires in 1 week
var message = sails.__({
phrase: 'signin_mail',
locale: params.lang || 'es-es'
}) + 'https://' + req.headers.host + '/sup/activate/' + token; // expires in 1 week
sails.log.debug("Sending activation email: \n" + message);
mailService.mailer()
.send({
to: params.email,
text: message
})
.then(() => {cb();})
.catch((err) => {cb(err);});
} // /sendConfirmationEmail()
sails.log.debug("Creating supervisor with params " + JSON.stringify(params));
if (params.name &&
params.surname &&
params.gender &&
params.password &&
params.email) {
Supervisor.create({
name: params.name,
surname: params.surname,
gender: params.gender,
password: params.password,
email: params.email,
pic: sails.config.pictogram.paths.defaultAvatarFileName,
address: params.address || null,
country: params.country || null,
phone: params.phone || null,
lang: params.lang || null,
ttsEngine: params.ttsEngine || null
}).then(function (supervisor) {
if (!params.name || !params.surname || !params.gender || !params.password || !params.email )
res.badRequest("Invalid params");
if (!supervisor)
res.serverError("Supervisor created but returned null");
var supData = {
name: params.name,
surname: params.surname,
gender: params.gender,
password: params.password,
email: params.email,
pic: sails.config.pictogram.paths.defaultAvatarFileName,
address: params.address || '',
postalCode: params.postalCode || '',
country: params.country || '',
phone: params.phone || '',
lang: params.lang || 'es-es',
};
if (params.id_off)
supData.id_off = params.id_off;
console.log("supData:\n" + JSON.stringify(supData));
Supervisor.create(supData)
.then(function (sup) {
sails.log.debug("SUPERVISOR: " + JSON.stringify(supervisor));
if (!sup)
res.serverError("Supervisor created but returned null");
/* Send email confirmation */
var message = sails.__({
phrase: 'signin_mail',
locale: params.lang || 'es-es'
}) + 'https://' + req.headers.host + '/sup/activate/' + sailsTokenAuth.issueToken(supervisor.id, 60*24*7); // expires in 1 week
sails.log.debug("Sending activation email: \n" + message);
supervisor = sup;
mailService.mailer()
.send({
to: params.email,
text: message
})
.then(() => {
res.ok({
user: supervisor,
token: sailsTokenAuth.issueToken(supervisor.id)
});
})
.catch((err) => {
res.serverError("Mail could not be sent " + err);
if (params.role === 'therapist_office' || params.role === 'tutor_office') {
sendConfirmationMail((err) => {
if (err) throw err;
return res.ok();
});
} else if (params.role === 'therapist_nooffice' || params.role === 'tutor_nooffice') {
}).catch(function (err) {
res.serverError("Supervisor could not be created: " + err);
});
} else {
res.badRequest("Invalid params");
}
Office.create(params.office)
.then((off) => {
// link supervisor with office
supervisor.id_off = off.id;
delete supervisor.password;
supervisor.save();
// set supervisor as admin in the office
off.admin = supervisor.id;
off.save();
sendConfirmationMail((err) => {
if (err) throw err;
return res.ok();
});
})
.catch(err => {throw err});
} else
return res.badRequest("Invalid role");
}).catch(function (err) {
return res.serverError("Supervisor could not be created: " + err);
});
},
/*
......
......@@ -26,15 +26,25 @@ module.exports = {
type: "string",
size: 80
},
logoUrl: {
columnName: 'logo_url',
type: "string",
size: 240
},
address: {
required: true,
type: "string",
size: 80
},
logo_url: {
required: true,
type: "string",
size: 240
country: {
type: "string",
size: 5,
required: true
},
lang: {
required: true,
type: "string",
size: 2
},
contactPerson: {
columnName: "contact_person",
......@@ -57,37 +67,33 @@ module.exports = {
type: "string",
size: 20
},
lang: {
required: true,
type: "string",
size: 2
admin: {
columnName: 'admin',
type: 'integer',
model: 'Supervisor'
},
country: {
postalCode: {
columnName: 'postal_code',
required: true,
type: "string",
size: 5
},
maxStudents: {
columnName: "max_students",
type: "integer"
},
currentStudents: {
columnName: "current_students",
type: "integer"
},
admin: {
model: 'supervisor',
required: false
//type: 'integer'
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]
// Relación con Student. [1 Office to N Student]
students: {
collection: "Student",
via: 'office'
}
},
beforeCreate: function (attrs, next) {
if (!attrs.logoUrl)
attrs.logoUrl = '/app/img/logo_pictogram.png';
next();
}
};
......@@ -52,6 +52,11 @@ module.exports = {
type: "string",
size: 180
},
postalCode: {
columnName: 'postal_code',
type: "string",
size: 10
},
country: {
type: "string",
size: 2
......
......@@ -63,6 +63,8 @@
"contact_person": "Contact person",
"continue_session": "Resume_session",
"country": "Country",
"country_requested": "Country requested",
"country_office_requested": "Country for office/center is mandatory",
"create_account": "Create account",
"create_an_account": "Create an account",
"credentials": "Credentials",
......@@ -136,6 +138,7 @@
"highlighted": "Highlighted",
"hours": "hours",
"how_many": "How many?",
"inactive_account": "This account has not been activated. Please, follow the link sent to your email address.",
"input_selection": "How to place a pictogram",
"instruction": "Instruction",
"instruction_begin": "Begin instruction",
......@@ -204,6 +207,7 @@
"no": "No",
"nobegin": "No started",
"no_method": "No method defined",
"no_office": "No office",
"no_subscribed": "No connection to student account",
"no_instruction": "No instruction defined",
"no_students_for_user": "You are not associated to any students. Please ask your office to link your account to a Pictogram student.",
......
......@@ -63,6 +63,8 @@
"contact_person": "Persona de contacto",
"continue_session": "Reanudar sesión",
"country": "País",
"country_requested": "Debe especificar el país",
"country_office_requested": "Debe especificar el país del gabinete/centro",
"create_account": "Crear cuenta",
"create_an_account": "Crear una cuenta",
"credentials": "Credenciales",
......@@ -136,6 +138,7 @@
"highlighted": "Resaltado",
"hours": "horas",
"how_many": "¿Cuántas?",
"inactive_account": "Esta cuenta no ha sido activada. Por favor, siga el enlace de confirmación enviado a su dirección de correo electrónico.",
"input_selection": "Cómo colocar un pictograma",
"instruction": "Instrucción",
"instruction_begin": "Primer intento",
......@@ -203,6 +206,7 @@
"next_sessions": "Sesiones posteriores",
"no": "No",
"no_method": "Método sin definir",
"no_office": "Sin centro",
"no_instruction": "Instrucción sin definir",
"no_students_for_user": "Su cuenta no está asociada a ningún estudiante. Por favor, contacte con su gabinete para enlazar su cuenta a un estudiante.",
"no_space_in_category": "No queda espacio en la categoría",
......
......@@ -24,7 +24,7 @@ function LoginCtrl(
};
$scope.office = {
logo_url : 'img/logo_pictogram.png',
logoUrl : 'img/logo_pictogram.png',
name : 'Pictogram'
};
......@@ -48,18 +48,23 @@ function LoginCtrl(
data.user.isTutor = true;
} else
data.user.isTutor = false;
if (data.user.office.logo_url.length < 5)
data.user.office.logo_url = 'img/logo_pictogram.png';
if (data.user.office.logoUrl.length < 5)
data.user.office.logoUrl = 'img/logo_pictogram.png';
$window.sessionStorage.token = data.token;
// Adapt language en-us to en-gb (the latter is the one supported for 'en')
//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') {
data.user.lang = 'en-gb';
}
//Update $scope
$scope.lang = data.user.lang;
//Update $translate
$translate.use($scope.lang);
} else {
//No user data, use default lang
$translate.use($scope.lang);
}
......@@ -83,8 +88,8 @@ function LoginCtrl(
$translate('no_students_for_user').then(function (translation) {
ngToast.warning({ content: translation });
});
} else {
$translate('login_fail').then(function (translation) {
} else if (err.search("not been activated") > 0) {
$translate('inactive_account').then(function (translation) {
ngToast.danger({ content: translation });
});
}
......
......@@ -21,16 +21,25 @@ function SignInCtrl($scope,
name: '',
surname: '',
address: '',
postal_code: '',
country: '00',
phone: '',
gender: 'M',
gender: 'F',
email: '',
email_confirm: '',
password: '',
password_confirm: '',
lang: 'es',
role: null,
lang: '00',
role: 'therapist_office',
office: {
country: 'NULL'
name: '',
address: '',
postalCode: '',
country: '00',
contactPerson: '',
phone1: '',
email: '',
logoUrl: ''
},
office_idx: -1
};
......@@ -48,6 +57,17 @@ function SignInCtrl($scope,
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
......@@ -67,6 +87,16 @@ function SignInCtrl($scope,
return;
}
if ($scope.formdata.country == '00') {
ngToast.danger({ content: $translate.instant('country_requested') });
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;
......@@ -82,10 +112,26 @@ function SignInCtrl($scope,
$scope.showdialog = true;
if ($scope.formdata.office_idx != -1) {
$scope.formdata.id_off = $scope.offices[$scope.formdata.office_idx].id;
delete $scope.formdata.office_idx;
}
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') });
ngToast.success({ content: $translate.instant('user_created', { name: $scope.formdata.name, surname: $scope.formdata.surname }) });
$scope.reset();
})
.error(function () {
......
......@@ -11,7 +11,7 @@
</p>
-->
<p class="text-center">
<img ng-src="{{office.logo_url}}" alt="{{office.name}}" title="{{office.name}}">
<img ng-src="{{office.logoUrl}}" alt="{{office.name}}" title="{{office.name}}">
</p>
<!-- Formulario -->
<!-- LoginCtrl controls here, see app.js -->
......
......@@ -33,7 +33,25 @@
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" id="signin_address" placeholder="{{ 'address' | translate }}" ng-model="formdata.address"/>
<input type="text" class="form-control" id="signin_address" placeholder="{{ 'address' | translate }}" ng-model="formdata.address"/ required>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<input type="text" class="form-control" id="signin_postal_code" placeholder="{{ 'postal_code' | translate }}" required ng-model="formdata.postalCode" ng-change="formdata.postalCode = formdata.postalCode.toUpperCase()"/>
</div>
</div>
<div class="col-md-8">
<div class="form-group">
<select class="form-control" ng-model="formdata.country" required>
<option value="00" selected disabled hidden>{{ 'country' | translate }}</option>
<option value="ES">España</option>
<option value="US">United States</option>
<option value="UK">United Kingdom</option>
<option value="IE">Ireland</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
......@@ -90,9 +108,9 @@
<fieldset>
<fieldset>
<label translate>language</label>
<div class="form-group">
<select class="form-control" name="signin_language" id="signin_language" ng-model="formdata.lang">
<select class="form-control" name="signin_language" id="signin_language" ng-model="formdata.lang" required>
<option value="00" selected disabled hidden>{{ 'language' | translate }}</option>
<option value="es-es" selected>Español</option>
<option value="en-gb">English</option>
</select>
......@@ -124,7 +142,8 @@
type="radio"
ng-model="formdata.role"
value="therapist_nooffice"
onClick="$('#office_selection').slideUp(); $('#office_form').slideDown();">
onClick="$('#office_selection').slideUp(); $('#office_form').slideDown();"
ng-click="copyFields();">
</div>
<div class="col-md-11">
{{ 'case_therapist_nooffice' | translate }}
......@@ -154,7 +173,8 @@
type="radio"
ng-model="formdata.role"
value="tutor_nooffice"
onClick="$('#office_selection').slideUp(); $('#office_form').slideUp();">
onClick="$('#office_selection').slideUp(); $('#office_form').slideUp(); copyFields();"
ng-click="copyFields();">
</div>
<div class="col-md-11">
{{ 'case_tutor_nooffice' | translate }}
......@@ -166,54 +186,45 @@
<div class="form-group" id="office_form" hidden>
<legend translate>office_center</legend>
<div class="form-group">
<input type="text" class="form-control" placeholder="{{ 'name' | translate }}" ng-model="formdata.office.name"/>
<input type="text" class="form-control" placeholder="{{ 'name' | translate }}" ng-model="formdata.office.name" ng-required="formdata.role == 'therapist_nooffice'"/>
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="{{ 'logo_url' | translate }}" ng-model="formdata.office.logo_url"/>
<input type="text" class="form-control" placeholder="{{ 'logo_url' | translate }}" ng-model="formdata.office.logoUrl"/>
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="{{ 'address' | translate }}" ng-model="formdata.office.address"/>
<input type="text" class="form-control" placeholder="{{ 'address' | translate }}" ng-model="formdata.office.address" ng-required="formdata.role == 'therapist_nooffice'"/>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" placeholder="{{ 'postal_code' | translate }}" ng-model="formdata.office.postal_code"/>
<input type="text" class="form-control" placeholder="{{ 'postal_code' | translate }}" ng-model="formdata.office.postalCode" ng-change="formdata.office.postalCode = formdata.office.postalCode.toUpperCase()" ng-required="formdata.role == 'therapist_nooffice'"/>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<select class="form-control" ng-model="formdata.office.country">
<option value="NULL" selected disabled hidden>{{ 'country' | translate }}</option>
<option value="ES">España</option>
<option value="US">United States</option>
<option value="UK">United Kingdom</option>
<option value="IE">Ireland</option>
</select>
<input type="text" class="form-control" placeholder="{{ 'contact_person' | translate }}" ng-model="formdata.office.contactPerson" ng-required="formdata.role == 'therapist_nooffice'"/>
</div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="{{ 'contact_person' | translate }}" ng-model="formdata.office.contact_person"/>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<input type="email" class="form-control" placeholder="{{ 'email' | translate }}" ng-model="formdata.office.email"/>
<input type="email" class="form-control" placeholder="{{ 'email' | translate }}" ng-model="formdata.office.email" ng-required="formdata.role == 'therapist_nooffice'"/>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" placeholder="{{ 'phone' | translate }}" ng-model="formdata.office.phone1"/>
<input type="text" class="form-control" placeholder="{{ 'phone' | translate }}" ng-model="formdata.office.phone1" ng-required="formdata.role == 'therapist_nooffice'"/>
</div>
</div>
</div>
</div>
<div class="form-group" id="office_selection" hidden>
<div class="form-group" id="office_selection">
<legend translate>office_center</legend>
<select class="form-control" ng-model="formdata.office_idx">
<select class="form-control" ng-model="formdata.office_idx" ng-required="formdata.role == 'therapist_office' || formdata.role == 'tutor_office'">
<option selected disabled hidden value="-1">{{ 'select_office' | translate }}</option>
<option ng-repeat="office in offices | orderBy: 'name' track by $index" value="$index"> {{ office.name }} </option>
<option ng-repeat="office in offices | orderBy: 'name' track by $index" ng-value="$index"> {{ office.name }} </option>
</select>
</div>
......@@ -229,6 +240,7 @@
</div>
<p class="text-center">
<a href="/app/#/login" class="btn btn-default" translate>cancel</a>
<button type="submit" class="btn btn-primary" ng-disabled="signInForm.$invalid" translate>create_account</button>
</p>
......
......@@ -173,7 +173,7 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
function(data, status, headers, config) {
$scope.wsessions[0].end = data.data.end;
$scope.ws_recover=false;
$sessionRunning = false;
$scope.sessionRunning = false;
}
,function(data, status, headers, config) {
}
......
......@@ -51,7 +51,7 @@
<!-- /.picto-legend -->
<img
src="/app/img/redcross.png"
class="disabled"
class="red-cross-visibility disabled"
ng-if="studentPicto.attributes.status == 'disabled'"/>
<img
ng-src="{{studentPicto.picto.uri}}"
......@@ -137,7 +137,7 @@
<!-- /.picto-legend -->
<img
src="/app/img/redcross.png"
class="disabled"
class="red-cross-visibility disabled"
ng-if="studentPicto.attributes.status == 'disabled'"/>
<img
ng-src="{{studentPicto.picto.uri}}"
......@@ -258,7 +258,7 @@
<!-- /.picto-legend -->
<img
src="/app/img/redcross.png"
class="disabled"
class="red-cross-visibility disabled"
ng-if="studentPicto.attributes.status == 'disabled'"/>
<img
ng-src="{{studentPicto.picto.uri}}"
......
......@@ -111,7 +111,7 @@
<div class="form-group">
<label translate>gender</label>
<select class="form-control" name="student_gender" id="student_gender" ng-model="formUser.gender" required>
<option value=" ">&nsp;</option>
<option value=" " disabled></option>
<option value="F" translate>woman</option>
<option value="M" translate>man</option>
</select>
......@@ -146,7 +146,7 @@
</fieldset>
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" translate>save</button>
<button type="submit" class="btn btn-primary" ng-click="updateStudent()" translate>save</button>
</div>
</form>
</div>
......
......@@ -35,9 +35,6 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
if ($scope.user.office.admin === $scope.user.id) {
$scope.user.isAdmin = true;
}
console.log("currentStudents: " + $scope.user.office.currentStudents);
console.log("maxStudents: " + $scope.user.office.maxStudents);
} else {
$scope.user.office = { name: '' };
}
......
......@@ -5,7 +5,7 @@
//-----------------------
// Supervisor Controller
//-----------------------
dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope, $window, $location, IOService, $modal) {
dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope, $window, $location, IOService, $modal, $translate) {
// Restore user data from session
var user = JSON.parse($window.sessionStorage.user);
......@@ -16,6 +16,8 @@ dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope
$scope.user.surname = user.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.isTutor = user.isTutor;
......@@ -33,61 +35,4 @@ dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope
}
});
//
// Own Pictos
//
$scope.own_pictos = function () {
console.log();
var modalInstance = $modal.open({
animation: true,
templateUrl: 'modules/supervisor/views/own_pictos.html',
controller: 'AddPictoCtrl',
size: 'lg',
resolve: {
student: function () {
return $scope.studentData;
},
supervisor: function () {
return $scope.user;
}
}
});
// Returned data from the modal window
modalInstance.result.then(function (pictoId) {
// Send the picto to the server
$http.post(config.backend + '/stu/' + $scope.studentData.id + '/picto/' + pictoId, {
attributes: {
id_cat: $scope.showFreeCategory ? null : $scope.getCategoryId($scope.selectedCategory),
coord_x: $scope.showFreeCategory ? null : col,
coord_y: $scope.showFreeCategory ? null : row,
status: 'enabled',
free_category_coord_x: $scope.showFreeCategory ? col : null,
free_category_coord_y: $scope.showFreeCategory ? row : null
}
})
.success(function (studentPicto) {
console.log(studentPicto);
placePicto(studentPicto);
io.socket.post('/stu/vocabulary', {
action: 'add',
attributes: {
id_stu: $scope.studentData.id,
stu_picto: studentPicto
}
}, function () {});
})
.error(function (err) {
if (err.code && err.code == 1) // codes are in sails/config/pictogram.js
ngToast.danger({ content: $translate.instant('error_duplicated_picto') });
else
ngToast.danger({ content: $translate.instant('error_adding_picto') });
});
// not needed
// $scope.loadPictos();
});
};
});
......@@ -5,31 +5,16 @@
//--------------------------
dashboardControllers.controller('SupervisorsCtrl', function SupervisorsCtrl($scope, $window, $http, config, $translate, ngToast) {
//Office ID
$http
.get(config.backend+'/office/get_all')
.success(function(data, status, headers, config) {
// Add to list
$scope.office = data[0];
$scope.supervisors_list();
.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) {
console.log("Error from API: " + data.error);
$translate('error_downloading_supervisors').then(function (translation) {
ngToast.danger({ content: translation });
});
});
// List of supervisors
$scope.supervisors_list = function(){
$http
.get(config.backend+'/office/get/' + $scope.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 });
});
});
};
});
......@@ -8,7 +8,7 @@
href="/app/#/students">
<img
class="topbar__logo__image"
ng-src="{{user.office.logo_url}}"
ng-src="{{user.office.logoUrl}}"
alt="{{user.office.name}}"
title="{{user.office.name}}" />
</a>
......@@ -44,12 +44,6 @@
</a>
</li>
<li>
<a ng-click="own_pictos()" class="pointer" role="menuitem" tabindex="0" href="">
<i class="glyphicon glyphicon-picture" aria-hidden="true"></i>
{{ 'own_pictos' | translate }}
</a>
</li>
<li>
<a class="pointer" role="menuitem" tabindex="0" href="/app/#/setup">
<i class="glyphicon glyphicon-cog" aria-hidden="true"></i>
{{ 'setup' | translate }}
......
......@@ -19,7 +19,7 @@
</div>
<div class="col-xs-6 input-group">
<input type="text" ng-model="search_students" id="search_students" placeholder="{{ 'filter' | translate }}" class="form-control" aria-describedby="basic-addon2">
<span class="input-group-addon glyphicon glyphicon-search" id="basic-addon2" aria-hidden="true"></span>
<span class="input-group-addon"><span class="glyphicon glyphicon-search" id="basic-addon2" aria-hidden="true"></span></span>
</div>
<div class="col-xs-3">
</div>
......
......@@ -525,6 +525,11 @@ textarea.editable{
justify-content: center;
}
/* Red cross in student collection */
.red-cross-visibility {
z-index: 1;
}
/* In addpicto */
#collections{
height: 300px;
......
......@@ -3,5 +3,10 @@
"A brand new app.": "A brand new app.",
"notification_from_pictogram": "Notification from Pictogram",
"signin_mail": "To activate your Pictogram account, click on this link:\n",
"change_password_mail": "To change your password, please click on the following link:\n"
"change_password_mail": "To change your password, please click on the following link:\n",
"login": "login",
"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"
}
......@@ -3,5 +3,10 @@
"A brand new app.": "Una aplicación de la nueva marca.",
"notification_from_pictogram": "Notificación desde Pictogram",
"signin_mail": "Para activar su cuenta en Pictogram, haga click en el siguiente enlace:\n",
"change_password_mail": "Para cambiar su contraseña, haga click en el siguiente enlace:\n"
"change_password_mail": "Para cambiar su contraseña, haga click en el siguiente enlace:\n",
"login": "acceder",
"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"
}
......@@ -112,6 +112,9 @@ module.exports.pictogram = {
},
error_codes: {
'DUPLICATED_PICTO': 1
'DUPLICATED_PICTO': 1,
'OFFICE_NOT_FOUND': 2,
'SUPERVISOR_NOT_FOUND': 3,
'STUDENT_NOT_FOUND': 4
}
};
......@@ -2,21 +2,31 @@
<html lang="es">
<head>
<meta charset="utf-8">
<title>Pictogram Dashboard</title>
<title>Pictogram Web</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/app/css/main.css">
</head>
<body>
<img title="Pictogram" alt="Pictogram" src="/app/img/logo_pictogram.png">
<div class="container">
<div class="row">
<div class="col-md-2">
<img title="Pictogram" alt="Pictogram" src="/app/img/logo_pictogram.png" style="margin-top:40px">
</div>
<div class="col-md-10">
<div class="page-header">
<h1><%= welcome_msg1 %></h1>
</div>
<p class="lead"><%= welcome_msg2 %> <a href="<%= login_url %>"><%= login %></a>.</p>
</div>
</div>
</div>
<p><strong>Welcome to Pictogram, <%= sup.name %>!</strong></p>
<p>Your account is now active, so you can proceed to <a href="<%= login_url %>">login</a>.</p>
<p></p>
<p><strong>¡Bienvenido a Pictogram, <%= sup.name %>!</strong></p>
<p>Su cuenta está ahora activa, por lo que puede <a href="<%= login_url %>">acceder</a>.</p>
<footer class="footer">
<div class="container">
<p class="text-muted">Pictogram Web - Yottacode S.L.</p>
</div>
</footer>
</body>
</html>
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