mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 22:27:26 +01:00
Group approval ui (#8184)
* Added all ui components back * Added group ui items back and initial group approval directive * Added ability to mark tasks as requires approval. Added approvals ctrl. Added get approvals method to tasks service * Added approval list view with approving functionality * Added error to produce message when task requests approval * Added notification display for group approvals * Fixed notification read and adding task * Fixed syncing with group approval required * Added group id to notifications for redirect on client side * Fixed approval request tests * Fixed linting issues * Removed expectation from beforeEach * Moved string to locale * Added eslint ignore * Updated notification for group approved, added new icons, and updated styles * Hid group plan ui
This commit is contained in:
committed by
Matteo Pagliazzi
parent
3ff7692528
commit
13df60e0dd
@@ -34,7 +34,12 @@ describe('GET /approvals/group/:groupId', () => {
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
|
||||
try {
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-empty
|
||||
}
|
||||
});
|
||||
|
||||
it('errors when user is not the group leader', async () => {
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.length).to.equal(1);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved'));
|
||||
|
||||
expect(syncedTask.group.approval.approved).to.be.true;
|
||||
|
||||
@@ -37,7 +37,12 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
let response = await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
let updatedTask = await member.get(`/tasks/${syncedTask._id}`);
|
||||
|
||||
await user.sync();
|
||||
@@ -48,8 +53,8 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
user: member.auth.local.username,
|
||||
taskName: updatedTask.text,
|
||||
}));
|
||||
expect(user.notifications[0].data.groupId).to.equal(guild._id);
|
||||
|
||||
expect(response.message).to.equal(t('taskApprovalHasBeenRequested'));
|
||||
expect(updatedTask.group.approval.requested).to.equal(true);
|
||||
expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
||||
});
|
||||
@@ -58,7 +63,12 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
|
||||
@@ -107,6 +107,7 @@ describe('Group Task Methods', () => {
|
||||
|
||||
let updatedTaskName = 'Update Task name';
|
||||
task.text = updatedTaskName;
|
||||
task.group.approval.required = true;
|
||||
|
||||
await guild.updateTask(task);
|
||||
|
||||
@@ -121,10 +122,12 @@ describe('Group Task Methods', () => {
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
expect(syncedTask.group.approval.required).to.equal(true);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(newMember._id);
|
||||
expect(syncedMemberTask).to.exist;
|
||||
expect(syncedMemberTask.text).to.equal(task.text);
|
||||
expect(syncedMemberTask.group.approval.required).to.equal(true);
|
||||
});
|
||||
|
||||
it('removes an assigned task and unlinks assignees', async () => {
|
||||
|
||||
@@ -178,15 +178,15 @@ window.habitrpg = angular.module('habitrpg',
|
||||
$scope.group.challenges = response.data.data;
|
||||
});
|
||||
//@TODO: Add this back when group tasks go live
|
||||
// return Tasks.getGroupTasks($scope.group._id);
|
||||
return Tasks.getGroupTasks($scope.group._id);
|
||||
})
|
||||
// .then(function (response) {
|
||||
// var tasks = response.data.data;
|
||||
// tasks.forEach(function (element, index, array) {
|
||||
// if (!$scope.group[element.type + 's']) $scope.group[element.type + 's'] = [];
|
||||
// $scope.group[element.type + 's'].push(element);
|
||||
// })
|
||||
// });
|
||||
.then(function (response) {
|
||||
var tasks = response.data.data;
|
||||
tasks.forEach(function (element, index, array) {
|
||||
if (!$scope.group[element.type + 's']) $scope.group[element.type + 's'] = [];
|
||||
$scope.group[element.type + 's'].push(element);
|
||||
})
|
||||
});
|
||||
}]
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
habitrpg.controller('GroupApprovalsCtrl', ['$scope', 'Tasks',
|
||||
function ($scope, Tasks) {
|
||||
$scope.approvals = [];
|
||||
|
||||
Tasks.getGroupApprovals($scope.group._id)
|
||||
.then(function (response) {
|
||||
$scope.approvals = response.data.data;
|
||||
});
|
||||
|
||||
$scope.approve = function (taskId, userId, $index) {
|
||||
if (!confirm(env.t('confirmTaskApproval'))) return;
|
||||
Tasks.approve(taskId, userId)
|
||||
.then(function (response) {
|
||||
$scope.approvals.splice($index, 1);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.approvalTitle = function (approval) {
|
||||
return env.t('approvalTitle', {text: approval.text, userName: approval.userId.profile.name});
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('groupApprovals', groupApprovals);
|
||||
|
||||
groupApprovals.$inject = [
|
||||
];
|
||||
|
||||
function groupApprovals() {
|
||||
|
||||
return {
|
||||
scope: {
|
||||
group: '=',
|
||||
},
|
||||
templateUrl: 'partials/groups.tasks.approvals.html',
|
||||
controller: 'GroupApprovalsCtrl',
|
||||
};
|
||||
}
|
||||
}());
|
||||
@@ -2,6 +2,11 @@ habitrpg.controller('GroupTaskActionsCtrl', ['$scope', 'Shared', 'Tasks', 'User'
|
||||
function ($scope, Shared, Tasks, User) {
|
||||
$scope.assignedMembers = [];
|
||||
$scope.user = User.user;
|
||||
|
||||
$scope.task._edit.requiresApproval = false;
|
||||
if ($scope.task.group.approval.required) {
|
||||
$scope.task._edit.requiresApproval = $scope.task.group.approval.required;
|
||||
}
|
||||
|
||||
$scope.$on('addedGroupMember', function(evt, userId) {
|
||||
Tasks.assignTask($scope.task.id, userId);
|
||||
|
||||
@@ -3,14 +3,16 @@ habitrpg.controller('GroupTasksCtrl', ['$scope', 'Shared', 'Tasks', 'User', func
|
||||
$scope.toggleBulk = Tasks.toggleBulk;
|
||||
$scope.cancelTaskEdit = Tasks.cancelTaskEdit;
|
||||
|
||||
function addTask (listDef, task) {
|
||||
var task = Shared.taskDefaults({text: task, type: listDef.type});
|
||||
//If the group has not been created, we bulk add tasks on save
|
||||
var group = $scope.obj;
|
||||
if (group._id) Tasks.createGroupTasks(group._id, task);
|
||||
if (!group[task.type + 's']) group[task.type + 's'] = [];
|
||||
group[task.type + 's'].unshift(task);
|
||||
delete listDef.newTask;
|
||||
function addTask (listDef, taskTexts) {
|
||||
taskTexts.forEach(function (taskText) {
|
||||
var task = Shared.taskDefaults({text: taskText, type: listDef.type});
|
||||
|
||||
//If the group has not been created, we bulk add tasks on save
|
||||
var group = $scope.obj;
|
||||
if (group._id) Tasks.createGroupTasks(group._id, task);
|
||||
if (!group[task.type + 's']) group[task.type + 's'] = [];
|
||||
group[task.type + 's'].unshift(task);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addTask = function(listDef) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('habitrpg')
|
||||
.controller('MenuCtrl', ['$scope', '$rootScope', '$http', 'Chat', 'Content',
|
||||
function($scope, $rootScope, $http, Chat, Content) {
|
||||
.controller('MenuCtrl', ['$scope', '$rootScope', '$http', 'Chat', 'Content', 'User', '$state',
|
||||
function($scope, $rootScope, $http, Chat, Content, User, $state) {
|
||||
|
||||
$scope.logout = function() {
|
||||
localStorage.clear();
|
||||
window.location.href = '/logout';
|
||||
};
|
||||
|
||||
function selectNotificationValue(mysteryValue, invitationValue, cardValue, unallocatedValue, messageValue, noneValue) {
|
||||
function selectNotificationValue(mysteryValue, invitationValue, cardValue, unallocatedValue, messageValue, noneValue, groupApprovalRequested, groupApproved) {
|
||||
var user = $scope.user;
|
||||
if (user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems && user.purchased.plan.mysteryItems.length) {
|
||||
return mysteryValue;
|
||||
@@ -21,6 +21,14 @@ angular.module('habitrpg')
|
||||
return unallocatedValue;
|
||||
} else if (!(_.isEmpty(user.newMessages))) {
|
||||
return messageValue;
|
||||
} else if (!_.isEmpty(user.groupNotifications)) {
|
||||
var groupNotificationTypes = _.pluck(user.groupNotifications, 'type');
|
||||
if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVAL') !== -1) {
|
||||
return groupApprovalRequested;
|
||||
} else if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVED') !== -1) {
|
||||
return groupApproved;
|
||||
}
|
||||
return noneValue;
|
||||
} else {
|
||||
return noneValue;
|
||||
}
|
||||
@@ -97,12 +105,28 @@ angular.module('habitrpg')
|
||||
'glyphicon-envelope',
|
||||
'glyphicon-plus-sign',
|
||||
'glyphicon-comment',
|
||||
'glyphicon-comment inactive'
|
||||
'glyphicon-comment inactive',
|
||||
'glyphicon-question-sign',
|
||||
'glyphicon glyphicon-ok-sign'
|
||||
);
|
||||
};
|
||||
|
||||
$scope.hasNoNotifications = function() {
|
||||
return selectNotificationValue(false, false, false, false, false, true);
|
||||
}
|
||||
return selectNotificationValue(false, false, false, false, false, true, false);
|
||||
};
|
||||
|
||||
$scope.viewGroupApprovalNotification = function (notification, $index) {
|
||||
User.readNotification(notification.id);
|
||||
User.user.groupNotifications.splice($index, 1);
|
||||
$state.go("options.social.guilds.detail", {gid: notification.data.groupId});
|
||||
};
|
||||
|
||||
$scope.groupApprovalNotificationIcon = function (notification) {
|
||||
if (notification.type === 'GROUP_TASK_APPROVAL') {
|
||||
return 'glyphicon glyphicon-question-sign';
|
||||
} else if (notification.type === 'GROUP_TASK_APPROVED') {
|
||||
return 'glyphicon glyphicon-ok-sign';
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -74,6 +74,11 @@ habitrpg.controller('NotificationCtrl',
|
||||
// Avoid showing the same notiication more than once
|
||||
var lastShownNotifications = [];
|
||||
|
||||
function trasnferGroupNotification(notification) {
|
||||
if (!User.user.groupNotifications) User.user.groupNotifications = [];
|
||||
User.user.groupNotifications.push(notification);
|
||||
}
|
||||
|
||||
function handleUserNotifications (after) {
|
||||
if (!after || after.length === 0) return;
|
||||
|
||||
@@ -123,6 +128,14 @@ habitrpg.controller('NotificationCtrl',
|
||||
if (notification.data.mp) Notification.mp(notification.data.mp);
|
||||
}
|
||||
break;
|
||||
case 'GROUP_TASK_APPROVAL':
|
||||
trasnferGroupNotification(notification);
|
||||
markAsRead = false;
|
||||
break;
|
||||
case 'GROUP_TASK_APPROVED':
|
||||
trasnferGroupNotification(notification);
|
||||
markAsRead = false;
|
||||
break;
|
||||
default:
|
||||
markAsRead = false; // If the notification is not implemented, skip it
|
||||
break;
|
||||
|
||||
@@ -106,6 +106,20 @@ angular.module('habitrpg')
|
||||
});
|
||||
};
|
||||
|
||||
function getGroupApprovals (groupId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: '/api/v3/approvals/group/' + groupId,
|
||||
});
|
||||
};
|
||||
|
||||
function approve (taskId, userId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/approve/' + userId,
|
||||
});
|
||||
};
|
||||
|
||||
function getTask (taskId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
@@ -367,5 +381,8 @@ angular.module('habitrpg')
|
||||
navigateChecklist: navigateChecklist,
|
||||
checklistCompletion: checklistCompletion,
|
||||
collapseChecklist: collapseChecklist,
|
||||
|
||||
getGroupApprovals: getGroupApprovals,
|
||||
approve: approve,
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -107,7 +107,17 @@
|
||||
"js/controllers/sortableInventoryCtrl.js",
|
||||
"js/controllers/tavernCtrl.js",
|
||||
"js/controllers/tasksCtrl.js",
|
||||
"js/controllers/userCtrl.js"
|
||||
"js/controllers/userCtrl.js",
|
||||
|
||||
"js/components/groupTasks/groupTasksController.js",
|
||||
"js/components/groupTasks/groupTasksDirective.js",
|
||||
"js/components/groupTaskActions/groupTaskActionsController.js",
|
||||
"js/components/groupTaskActions/groupTaskActionsDirective.js",
|
||||
"js/components/groupMembersAutocomplete/groupMembersAutocompleteDirective.js",
|
||||
"js/components/groupTaskMetaActions/groupTaskMetaActionsDirective.js",
|
||||
"js/components/groupTaskMetaActions/groupTaskMetaActionsController.js",
|
||||
"js/components/groupApprovals/groupApprovalsDirective.js",
|
||||
"js/components/groupApprovals/groupApprovalsController.js"
|
||||
],
|
||||
"css": [
|
||||
"bower_components/bootstrap/dist/css/bootstrap.css",
|
||||
@@ -118,7 +128,11 @@
|
||||
"bower_components/pnotify/jquery.pnotify.default.icons.css",
|
||||
"css/habitrpg-shared.css",
|
||||
"bower_components/bootstrap-tour/build/css/bootstrap-tour.css",
|
||||
"fontello/css/fontelico.css"
|
||||
"fontello/css/fontelico.css",
|
||||
|
||||
"bower_components/jquery-ui/themes/base/minified/jquery.ui.autocomplete.min.css",
|
||||
"bower_components/jquery-ui/themes/base/minified/jquery.ui.menu.min.css",
|
||||
"bower_components/jquery-ui/themes/ui-lightness/jquery-ui.min.css"
|
||||
]
|
||||
},
|
||||
"static": {
|
||||
|
||||
@@ -218,5 +218,8 @@
|
||||
"claim": "Claim",
|
||||
"onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription",
|
||||
"yourTaskHasBeenApproved": "Your task has been approved",
|
||||
"userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>"
|
||||
"userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>",
|
||||
"confirmTaskApproval": "Are you sure you want to approve this task?",
|
||||
"approve": "Approve",
|
||||
"approvalTitle": "<%= text %> for user: <%= userName %>"
|
||||
}
|
||||
|
||||
@@ -136,5 +136,7 @@
|
||||
"perceptionExample": "Relating to work or financial tasks",
|
||||
"constitutionExample": "Relating to health, wellness, and social interaction",
|
||||
"taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested",
|
||||
"taskApprovalHasBeenRequested": "Approval has been requested"
|
||||
"taskApprovalHasBeenRequested": "Approval has been requested",
|
||||
"approvals": "Approvals",
|
||||
"approvalRequired": "Approval Required"
|
||||
}
|
||||
|
||||
@@ -263,6 +263,10 @@ api.updateTask = {
|
||||
// repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject()
|
||||
// see https://github.com/Automattic/mongoose/issues/2749
|
||||
|
||||
if (sanitizedObj.requiresApproval) {
|
||||
task.group.approval.required = true;
|
||||
}
|
||||
|
||||
let savedTask = await task.save();
|
||||
|
||||
if (group && task.group.id && task.group.assignedUsers.length > 0) {
|
||||
@@ -331,11 +335,12 @@ api.scoreTask = {
|
||||
user: user.profile.name,
|
||||
taskName: task.text,
|
||||
}),
|
||||
groupId: group._id,
|
||||
});
|
||||
|
||||
await Bluebird.all([groupLeader.save(), task.save()]);
|
||||
|
||||
return res.respond(200, {message: res.t('taskApprovalHasBeenRequested'), task});
|
||||
throw new NotAuthorized(res.t('taskApprovalHasBeenRequested'));
|
||||
}
|
||||
|
||||
let wasCompleted = task.completed;
|
||||
|
||||
@@ -225,7 +225,10 @@ api.approveTask = {
|
||||
task.group.approval.approvingUser = user._id;
|
||||
task.group.approval.approved = true;
|
||||
|
||||
assignedUser.addNotification('GROUP_TASK_APPROVAL', {message: res.t('yourTaskHasBeenApproved')});
|
||||
assignedUser.addNotification('GROUP_TASK_APPROVED', {
|
||||
message: res.t('yourTaskHasBeenApproved'),
|
||||
groupId: group._id,
|
||||
});
|
||||
|
||||
await Bluebird.all([assignedUser.save(), task.save()]);
|
||||
|
||||
@@ -266,7 +269,9 @@ api.getGroupApprovals = {
|
||||
'group.id': groupId,
|
||||
'group.approval.approved': false,
|
||||
'group.approval.requested': true,
|
||||
}, 'userId group').exec();
|
||||
}, 'userId group text')
|
||||
.populate('userId', 'profile')
|
||||
.exec();
|
||||
|
||||
res.respond(200, approvals);
|
||||
},
|
||||
|
||||
@@ -893,6 +893,8 @@ schema.methods.updateTask = async function updateTask (taskToSync) {
|
||||
updateCmd.$set[key] = syncableAttributes[key];
|
||||
}
|
||||
|
||||
updateCmd.$set['group.approval.required'] = taskToSync.group.approval.required;
|
||||
|
||||
let taskSchema = Tasks[taskToSync.type];
|
||||
// Updating instead of loading and saving for performances, risks becoming a problem if we introduce more complexity in tasks
|
||||
await taskSchema.update({
|
||||
|
||||
@@ -13,6 +13,7 @@ const NOTIFICATION_TYPES = [
|
||||
'NEW_CONTRIBUTOR_LEVEL',
|
||||
'CRON',
|
||||
'GROUP_TASK_APPROVAL',
|
||||
'GROUP_TASK_APPROVED',
|
||||
];
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
@@ -147,14 +147,16 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
h3.popover-title {{group.leader.profile.name}}
|
||||
.popover-content
|
||||
markdown(text='group._editing ? groupCopy.leaderMessage : group.leaderMessage')
|
||||
|
||||
ul.options-menu(ng-init="groupPane = 'chat'", style="display:none;")
|
||||
//- li
|
||||
//- a(ng-click="groupPane = 'chat'")=env.t('chat')
|
||||
//- li
|
||||
//- a(ng-click="groupPane = 'tasks'", ng-if='group.purchased.active')=env.t('tasks')
|
||||
//- li
|
||||
//- a(ng-click="groupPane = 'subscription'", ng-show='group.leader._id === user._id')=env.t('subscription')
|
||||
|
||||
ul.options-menu(ng-init="groupPane = 'chat'", ng-hide="true")
|
||||
//- li
|
||||
//- a(ng-click="groupPane = 'chat'")=env.t('chat')
|
||||
//- li
|
||||
//- a(ng-click="groupPane = 'tasks'", ng-show='group.purchased.active')=env.t('tasks')
|
||||
//- li
|
||||
//- a(ng-click="groupPane = 'approvals'", ng-show='group.purchased.active && group.leader._id === user._id')=env.t('approvals')
|
||||
//- li
|
||||
//- a(ng-click="groupPane = 'subscription'", ng-show='group.leader._id === user._id')=env.t('subscription')
|
||||
|
||||
.tab-content
|
||||
.tab-pane.active
|
||||
@@ -170,6 +172,8 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
|
||||
group-tasks(ng-show="groupPane == 'tasks'")
|
||||
|
||||
group-approvals(ng-show="groupPane == 'approvals'", ng-if="group.leader._id === user._id", group="group")
|
||||
|
||||
//TODO: This can be a directive and the group/user can be an object passed via attribute
|
||||
div(ng-show="groupPane == 'subscription'")
|
||||
.col-md-12
|
||||
|
||||
@@ -2,3 +2,9 @@ script(type='text/ng-template', id='partials/groups.tasks.actions.html')
|
||||
div(ng-if="group.leader._id === user._id", class="col-md-12")
|
||||
strong=env.t('assignTask')
|
||||
group-members-autocomplete(ng-model="assignedMembers")
|
||||
|
||||
ul.priority-multiplier
|
||||
li {{requiresApproval}}
|
||||
button(type='button', ng-class='{active: task._edit.requiresApproval==true}',
|
||||
ng-click='task._edit.requiresApproval = !task._edit.requiresApproval')
|
||||
=env.t('approvalRequired')
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
script(type='text/ng-template', id='partials/groups.tasks.approvals.html')
|
||||
.panel-group(ng-repeat="approval in approvals")
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
span {{approvalTitle(approval)}}
|
||||
a.btn.btn-sm.btn-success.pull-right(ng-click="approve(approval.group.taskId, approval.userId._id)")=env.t('approve')
|
||||
@@ -1,6 +1,7 @@
|
||||
include ./group-tasks-actions
|
||||
include ./group-tasks-meta-actions
|
||||
include ./group-members-autocomplete
|
||||
include ./group-tasks-approvals
|
||||
|
||||
script(type='text/ng-template', id='partials/groups.tasks.html')
|
||||
habitrpg-tasks(main=false)
|
||||
|
||||
@@ -218,6 +218,11 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
span {{v.name}}
|
||||
a(ng-click='clearMessages(k)', popover=env.t('clear'),popover-placement='right',popover-trigger='mouseenter',popover-append-to-body='true')
|
||||
span.glyphicon.glyphicon-remove-circle
|
||||
li(ng-repeat='notification in user.groupNotifications')
|
||||
a(ng-click='viewGroupApprovalNotification(notification, $index)', data-close-menu)
|
||||
span(class="{{::groupApprovalNotificationIcon(notification)}}")
|
||||
span
|
||||
| {{notification.data.message}}
|
||||
|
||||
ul.toolbar-controls
|
||||
li.toolbar-controls-button
|
||||
|
||||
@@ -6,7 +6,7 @@ include ./task_view/mixins
|
||||
script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
||||
.tasks-lists.container-fluid
|
||||
.row
|
||||
.col-md-3.col-sm-6(ng-repeat='list in lists', ng-class='::{ "rewards-module": list.type==="reward", "new-row-md": list.type==="todo", "col-md-2": obj.type }')
|
||||
.col-md-3.col-sm-6(ng-repeat='list in lists', ng-class='::{ "rewards-module": list.type==="reward", "new-row-md": list.type==="todo", "col-md-3": obj.type }')
|
||||
.task-column(class='{{::list.type}}s')
|
||||
|
||||
include ./task_view/graph
|
||||
|
||||
Reference in New Issue
Block a user