diff --git a/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js b/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js index 77af1aba29..45359c2fce 100644 --- a/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js +++ b/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js @@ -93,15 +93,6 @@ describe('POST /tasks/:taskId/assign/:memberId', () => { expect(syncedTask).to.exist; }); - it('sends a message to the group when a user claims a task', async () => { - await member.post(`/tasks/${task._id}/assign/${member._id}`); - - let updateGroup = await user.get(`/groups/${guild._id}`); - - expect(updateGroup.chat[0].text).to.equal(t('userIsClamingTask', {username: member.profile.name, task: task.text})); - expect(updateGroup.chat[0].uuid).to.equal('system'); - }); - it('assigns a task to a user', async () => { await user.post(`/tasks/${task._id}/assign/${member._id}`); diff --git a/test/client/unit/specs/store/getters/tasks/getTaskClasses.js b/test/client/unit/specs/store/getters/tasks/getTaskClasses.js index 1399283f55..c1ccca379d 100644 --- a/test/client/unit/specs/store/getters/tasks/getTaskClasses.js +++ b/test/client/unit/specs/store/getters/tasks/getTaskClasses.js @@ -131,10 +131,12 @@ describe('getTaskClasses getter', () => { up: { bg: 'task-good-control-bg', inner: 'task-good-control-inner-habit', + icon: 'task-good-control-icon', }, down: { bg: 'task-disabled-habit-control-bg', inner: 'task-disabled-habit-control-inner', + icon: 'task-good-control-icon', }, }); }); diff --git a/website/client/assets/scss/task.scss b/website/client/assets/scss/task.scss index 7ed5d819a3..46d17aba95 100644 --- a/website/client/assets/scss/task.scss +++ b/website/client/assets/scss/task.scss @@ -1,7 +1,7 @@ .task { &-worst { // dark red &-control { - &-bg { + &-bg { background: $maroon-100 !important; &:hover { .habit-control { background: rgba(26, 24, 29, 0.48) !important; } @@ -11,6 +11,7 @@ &-inner-habit { background: rgba(26, 24, 29, 0.24) !important; } &-inner-daily-todo { background: $maroon-500 !important; } &-checkbox { color: $maroon-100 !important; } + &-icon { color: #6c0406 !important; } } &-modal { @@ -30,7 +31,7 @@ &-worse { // light red &-control { - &-bg { + &-bg { background: $red-100 !important; &:hover { .habit-control { background: rgba(26, 24, 29, 0.48) !important; } @@ -40,6 +41,7 @@ &-inner-habit { background: rgba(26, 24, 29, 0.24) !important; } &-inner-daily-todo { background: $red-500 !important; } &-checkbox { color: $red-100 !important; } + &-icon { color: #6c0406 !important; } } &-modal { @@ -59,7 +61,7 @@ &-bad { // orange &-control { - &-bg { + &-bg { background: $orange-100 !important; &:hover { @@ -70,10 +72,11 @@ &-inner-habit { background: rgba(183, 90, 28, 0.4) !important; } &-inner-daily-todo { background: $orange-500 !important; } &-checkbox { color: $orange-100 !important; } + &-icon { color: #7f3300 !important; } } &-modal { - &-bg { + &-bg { background: $orange-100 !important; .form-control { @@ -99,7 +102,7 @@ &-neutral { // yellow &-control { - &-bg { + &-bg { background: $yellow-100 !important; &:hover { .habit-control { background: #bf7d1a !important; } @@ -109,10 +112,11 @@ &-inner-habit { background: rgba(183, 90, 28, 0.32) !important; } &-inner-daily-todo { background: $yellow-500 !important; } &-checkbox { color: $yellow-100 !important; } + &-icon { color: #794b00 !important; } } &-modal { - &-bg { + &-bg { background: $yellow-100 !important; .form-control { @@ -138,7 +142,7 @@ &-good { // green &-control { - &-bg { + &-bg { background: $green-100 !important; &:hover { .habit-control { background: rgba(26, 24, 29, 0.48) !important; } @@ -148,6 +152,7 @@ &-inner-habit { background: rgba(26, 24, 29, 0.24) !important; } &-inner-daily-todo { background: #77f4c7 !important; } &-checkbox { color: $green-10 !important; } + &-icon { color: #005737 !important; } } &-modal { @@ -167,7 +172,7 @@ &-better { // teal &-control { - &-bg { + &-bg { background: $teal-100 !important; &:hover { .habit-control { background: rgba(26, 24, 29, 0.48) !important; } @@ -177,6 +182,7 @@ &-inner-habit { background: rgba(26, 24, 29, 0.24) !important; } &-inner-daily-todo { background: #8dedf6 !important; } &-checkbox { color: $teal-100 !important; } + &-icon { color: #005158 !important; } } &-modal { @@ -196,7 +202,7 @@ &-best { // blue &-control { - &-bg { + &-bg { background: $blue-100 !important; &:hover { .habit-control { background: rgba(26, 24, 29, 0.48) !important; } @@ -206,6 +212,7 @@ &-inner-habit { background: rgba(26, 24, 29, 0.24) !important; } &-inner-daily-todo { background: $blue-500 !important; } &-checkbox { color: $blue-100 !important; } + &-icon { color: #033f5e !important; } } &-modal { @@ -241,7 +248,7 @@ &-reward { &-control { - &-bg { + &-bg { background: rgba(255, 217, 160, 0.32) !important; .small-text { color: $orange-10 !important; } @@ -251,7 +258,7 @@ } &-disabled { - &-habit { + &-habit { &-control { &-bg { background: $gray-600; } &-inner { @@ -261,13 +268,13 @@ } } - &-daily-todo { + &-daily-todo { &-control { - &-bg { + &-bg { background: $gray-400 !important; &:hover { .daily-todo-control { background: rgba(255, 255, 255, 0.72) !important; } - } + } } &-inner { background: $gray-500 !important; } &-checkbox { color: $gray-400 !important; } @@ -298,6 +305,12 @@ margin: 0 auto; } + .lock { + margin-top: 7px; + height: 12px; + width: 10px; + } + .positive { margin-top: 9px; } @@ -321,4 +334,10 @@ display: block; } } + + .svg-icon.lock { + margin: 8px auto; + height: 12px; + width: 10px; + } } diff --git a/website/client/assets/svg/lock.svg b/website/client/assets/svg/lock.svg index 0ac53170bd..065191ef1b 100644 --- a/website/client/assets/svg/lock.svg +++ b/website/client/assets/svg/lock.svg @@ -1,3 +1,3 @@ - + diff --git a/website/client/components/tasks/task.vue b/website/client/components/tasks/task.vue index fbdc7a2d2e..006a20ebc6 100644 --- a/website/client/components/tasks/task.vue +++ b/website/client/components/tasks/task.vue @@ -6,11 +6,13 @@ // Habits left side control .left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up.bg") .task-control.habit-control(:class="controlClass.up.inner", @click="(isUser && task.up) ? score('up') : null") - .svg-icon.positive(v-html="icons.positive") + .svg-icon.lock(v-if="this.task.group.id && !isUser", v-html="icons.lock", :class="controlClass.up.icon") + .svg-icon.positive(v-else, v-html="icons.positive") // Dailies and todos left side control .left-control.d-flex.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass.bg") .task-control.daily-todo-control(:class="controlClass.inner", @click="isUser ? score(task.completed ? 'down' : 'up') : null") - .svg-icon.check(v-html="icons.check", :class="{'display-check-icon': task.completed, [controlClass.checkbox]: true}") + .svg-icon.lock(v-html="icons.lock", v-if="this.task.group.id && !isUser && !task.completed", :class="controlClass.icon") + .svg-icon.check(v-else, v-html="icons.check", :class="{'display-check-icon': task.completed, [controlClass.checkbox]: true}") // Task title, description and icons .task-content(:class="contentClass") .task-clickable-area(@click="edit($event, task)", :class="{'task-clickable-area-user': isUser}") @@ -99,7 +101,8 @@ // Habits right side control .right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down.bg") .task-control.habit-control(:class="controlClass.down.inner", @click="(isUser && task.down) ? score('down') : null") - .svg-icon.negative(v-html="icons.negative") + .svg-icon.lock(v-if="this.task.group.id && !isUser", v-html="icons.lock", :class="controlClass.down.icon") + .svg-icon.negative(v-else, v-html="icons.negative") // Rewards right side control .right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass.bg", @click="isUser ? score('down') : null") .svg-icon(v-html="icons.gold") @@ -533,6 +536,7 @@ import topIcon from 'assets/svg/top.svg'; import bottomIcon from 'assets/svg/bottom.svg'; import deleteIcon from 'assets/svg/delete.svg'; import checklistIcon from 'assets/svg/checklist.svg'; +import lockIcon from 'assets/svg/lock.svg'; import menuIcon from 'assets/svg/menu.svg'; import markdownDirective from 'client/directives/markdown'; import notifications from 'client/mixins/notifications'; @@ -569,6 +573,7 @@ export default { top: topIcon, bottom: bottomIcon, menu: menuIcon, + lock: lockIcon, }), }; }, diff --git a/website/client/store/getters/tasks.js b/website/client/store/getters/tasks.js index a2e38f628d..5fb2e49604 100644 --- a/website/client/store/getters/tasks.js +++ b/website/client/store/getters/tasks.js @@ -94,6 +94,7 @@ export function getTaskClasses (store) { bg: `task-${color}-control-bg`, checkbox: `task-${color}-control-checkbox`, inner: `task-${color}-control-inner-daily-todo`, + icon: `task-${color}-control-icon`, }; } else if (type === 'reward') { return { @@ -102,11 +103,11 @@ export function getTaskClasses (store) { } else if (type === 'habit') { return { up: task.up ? - { bg: `task-${color}-control-bg`, inner: `task-${color}-control-inner-habit`} : - { bg: 'task-disabled-habit-control-bg', inner: 'task-disabled-habit-control-inner' }, + { bg: `task-${color}-control-bg`, inner: `task-${color}-control-inner-habit`, icon: `task-${color}-control-icon`} : + { bg: 'task-disabled-habit-control-bg', inner: 'task-disabled-habit-control-inner', icon: `task-${color}-control-icon` }, down: task.down ? - { bg: `task-${color}-control-bg`, inner: `task-${color}-control-inner-habit`} : - { bg: 'task-disabled-habit-control-bg', inner: 'task-disabled-habit-control-inner' }, + { bg: `task-${color}-control-bg`, inner: `task-${color}-control-inner-habit`, icon: `task-${color}-control-icon`} : + { bg: 'task-disabled-habit-control-bg', inner: 'task-disabled-habit-control-inner', icon: `task-${color}-control-icon` }, }; } break; diff --git a/website/common/script/ops/scoreTask.js b/website/common/script/ops/scoreTask.js index 58e594754d..20a8ce20fa 100644 --- a/website/common/script/ops/scoreTask.js +++ b/website/common/script/ops/scoreTask.js @@ -193,7 +193,7 @@ module.exports = function scoreTask (options = {}, req = {}) { exp: user.stats.exp, }; - if (task.group && task.group.approval && task.group.approval.required && !task.group.approval.approved) return; + if (task.group && task.group.approval && task.group.approval.required && !task.group.approval.approved) return 0; // This is for setting one-time temporary flags, such as streakBonus or itemDropped. Useful for notifying // the API consumer, then cleared afterwards diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 1d2b7ab747..dda6a0d6a8 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -638,6 +638,18 @@ api.scoreTask = { if (task.group && task.group.taskId) { await handleSharedCompletion(task); + try { + const groupTask = await Tasks.Task.findOne({ + _id: task.group.taskId, + }).exec(); + + if (groupTask) { + const groupDelta = groupTask.group.assignedUsers ? delta / groupTask.group.assignedUsers.length : delta; + await groupTask.scoreChallengeTask(groupDelta, direction); + } + } catch (e) { + logger.error(e); + } } // Save results and handle request diff --git a/website/server/controllers/api-v3/tasks/groups.js b/website/server/controllers/api-v3/tasks/groups.js index aee836dafd..d4b0e5178c 100644 --- a/website/server/controllers/api-v3/tasks/groups.js +++ b/website/server/controllers/api-v3/tasks/groups.js @@ -201,19 +201,7 @@ api.assignTask = { let promises = []; - // User is claiming the task - if (user._id === assignedUserId) { - let message = res.t('userIsClamingTask', {username: user.profile.name, task: task.text}); - const newMessage = group.sendChat({ - message, - info: { - type: 'claim_task', - user: user.profile.name, - task: task.text, - }, - }); - promises.push(newMessage.save()); - } else { + if (user._id !== assignedUserId) { const taskText = task.text; const managerName = user.profile.name; diff --git a/website/server/libs/cron.js b/website/server/libs/cron.js index 5106b013e2..09178a35f9 100644 --- a/website/server/libs/cron.js +++ b/website/server/libs/cron.js @@ -389,6 +389,13 @@ export function cron (options = {}) { task.checklist.forEach(i => i.completed = false); } } + + if (task.group && task.group.approval && task.group.approval.approved) { + task.group.approval.approved = false; + task.group.approval.dateApproved = null; + task.group.approval.requested = false; + task.group.approval.requestedDate = null; + } }); resetHabitCounters(user, tasksByType, now, daysMissed); @@ -399,6 +406,12 @@ export function cron (options = {}) { if (task.up === false || task.down === false) { task.value = Math.abs(task.value) < 0.1 ? 0 : task.value = task.value / 2; } + if (task.group && task.group.approval && task.group.approval.approved) { + task.group.approval.approved = false; + task.group.approval.dateApproved = null; + task.group.approval.requested = false; + task.group.approval.requestedDate = null; + } }); // Finished tallying diff --git a/website/server/middlewares/cron.js b/website/server/middlewares/cron.js index 15a66b02bf..47368dc8fd 100644 --- a/website/server/middlewares/cron.js +++ b/website/server/middlewares/cron.js @@ -105,8 +105,19 @@ async function cronAsync (req, res) { // Save user and tasks let toSave = [user.save()]; - tasks.forEach(task => { + tasks.forEach(async task => { if (task.isModified()) toSave.push(task.save()); + if (task.isModified() && task.group && task.group.taskId) { + const groupTask = await Tasks.Task.findOne({ + _id: task.group.taskId, + }).exec(); + + if (groupTask) { + let delta = Math.pow(0.9747, task.value) * -1; + if (groupTask.group.assignedUsers) delta /= groupTask.group.assignedUsers.length; + await groupTask.scoreChallengeTask(delta, 'down'); + } + } }); await Promise.all(toSave); diff --git a/website/server/models/task.js b/website/server/models/task.js index db7a05e867..fe5ec25b4e 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -200,8 +200,8 @@ TaskSchema.methods.scoreChallengeTask = async function scoreChallengeTask (delta if (chalTask.type === 'habit' || chalTask.type === 'daily') { // Add only one history entry per day const history = chalTask.history; - const lastChallengHistoryIndex = history.length - 1; - const lastHistoryEntry = history[lastChallengHistoryIndex]; + const lastChallengeHistoryIndex = history.length - 1; + const lastHistoryEntry = history[lastChallengeHistoryIndex]; if ( lastHistoryEntry && lastHistoryEntry.date && @@ -222,7 +222,7 @@ TaskSchema.methods.scoreChallengeTask = async function scoreChallengeTask (delta } } - chalTask.markModified(`history.${lastChallengHistoryIndex}`); + chalTask.markModified(`history.${lastChallengeHistoryIndex}`); } else { const historyEntry = { date: Number(new Date()),