mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-07-14 02:02:19 +00:00
WIP(teams): reimplement open tasking
This commit is contained in:
parent
6e39c79cff
commit
248e1c6fe9
7 changed files with 76 additions and 77 deletions
|
|
@ -3,9 +3,9 @@
|
|||
<div
|
||||
class="task transition"
|
||||
:class="[{
|
||||
'groupTask': task.group.id,
|
||||
'task-not-editable': !teamManagerAccess},
|
||||
`type_${task.type}`
|
||||
'groupTask': task.group.id,
|
||||
'task-not-editable': !teamManagerAccess
|
||||
}, `type_${task.type}`
|
||||
]"
|
||||
@click="castEnd($event, task)"
|
||||
>
|
||||
|
|
@ -16,8 +16,7 @@
|
|||
/>
|
||||
<div
|
||||
class="d-flex"
|
||||
:class="{'task-not-scoreable': isUser !== true || task.group.approval.requested
|
||||
&& !(task.group.approval.approved && task.type === 'habit')}"
|
||||
:class="{'task-not-scoreable': showTaskLockIcon }"
|
||||
>
|
||||
<!-- Habits left side control-->
|
||||
<div
|
||||
|
|
@ -31,19 +30,16 @@
|
|||
<div
|
||||
class="task-control habit-control"
|
||||
:class="[{
|
||||
'habit-control-positive-enabled': task.up && isUser,
|
||||
'habit-control-positive-disabled': !task.up && isUser,
|
||||
'task-not-scoreable': isUser !== true
|
||||
|| (task.group.approval.requested && !task.group.approval.approved),
|
||||
'habit-control-positive-enabled': task.up && !showTaskLockIcon,
|
||||
'habit-control-positive-disabled': !task.up && !showTaskLockIcon,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.up.inner]"
|
||||
tabindex="0"
|
||||
@click="(isUser && task.up && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('up') : null"
|
||||
@keypress.enter="(isUser && task.up && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('up') : null"
|
||||
@click="score('up')"
|
||||
@keypress.enter="score('up')"
|
||||
>
|
||||
<div
|
||||
v-if="!isUser"
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="task.up ? controlClass.up.icon : 'positive'"
|
||||
v-html="icons.lock"
|
||||
|
|
@ -67,13 +63,11 @@
|
|||
class="task-control daily-todo-control"
|
||||
:class="controlClass.inner"
|
||||
tabindex="0"
|
||||
@click="isUser && !task.group.approval.requested
|
||||
? score(task.completed ? 'down' : 'up' ) : null"
|
||||
@keypress.enter="isUser && !task.group.approval.requested
|
||||
? score(task.completed ? 'down' : 'up' ) : null"
|
||||
@click="score(task.completed ? 'down' : 'up' )"
|
||||
@keypress.enter="score(task.completed ? 'down' : 'up' )"
|
||||
>
|
||||
<div
|
||||
v-if="!isUser"
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="controlClass.icon"
|
||||
v-html="icons.lock"
|
||||
|
|
@ -346,19 +340,16 @@
|
|||
<div
|
||||
class="task-control habit-control"
|
||||
:class="[{
|
||||
'habit-control-negative-enabled': task.down && isUser,
|
||||
'habit-control-negative-disabled': !task.down && isUser,
|
||||
'task-not-scoreable': isUser !== true
|
||||
|| (task.group.approval.requested && !task.group.approval.approved),
|
||||
'habit-control-negative-enabled': task.down && !showTaskLockIcon,
|
||||
'habit-control-negative-disabled': !task.down && !showTaskLockIcon,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.down.inner]"
|
||||
tabindex="0"
|
||||
@click="(isUser && task.down && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('down') : null"
|
||||
@keypress.enter="(isUser && task.down && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('down') : null"
|
||||
@click="score('down')"
|
||||
@keypress.enter="score('down')"
|
||||
>
|
||||
<div
|
||||
v-if="!isUser"
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="task.down ? controlClass.down.icon : 'negative'"
|
||||
v-html="icons.lock"
|
||||
|
|
@ -376,8 +367,8 @@
|
|||
class="right-control d-flex align-items-center justify-content-center reward-control"
|
||||
:class="controlClass.bg"
|
||||
tabindex="0"
|
||||
@click="isUser ? score('down') : null"
|
||||
@keypress.enter="isUser ? score('down') : null"
|
||||
@click="score('down')"
|
||||
@keypress.enter="score('down')"
|
||||
>
|
||||
<div
|
||||
class="svg-icon"
|
||||
|
|
@ -389,10 +380,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<approval-footer
|
||||
v-if="task.group.id"
|
||||
v-if="task.group.id && !isOpenTask"
|
||||
:task="task"
|
||||
:group="group"
|
||||
@claimRewards="score('up')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1048,12 +1038,26 @@ export default {
|
|||
},
|
||||
teamManagerAccess () {
|
||||
if (!this.isGroupTask || !this.group) return true;
|
||||
if (!this.group.leader && !this.group.managers) return false;
|
||||
return (this.group.leader._id === this.user._id || this.group.managers[this.user._id]);
|
||||
},
|
||||
displayNotes () {
|
||||
if (this.isGroupTask && !this.isUser) return this.task.group.managerNotes;
|
||||
return this.task.notes;
|
||||
},
|
||||
isOpenTask () {
|
||||
if (!this.isGroupTask) return false;
|
||||
if (this.task.group.assignedUsers.length > 0) return false;
|
||||
return true;
|
||||
},
|
||||
showTaskLockIcon () {
|
||||
if (this.isUser) return false;
|
||||
if (this.isGroupTask) {
|
||||
if (this.isOpenTask) return false;
|
||||
if (this.task.group.assignedUsers.indexOf(this.user._id) !== -1) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
|
|
@ -1121,6 +1125,8 @@ export default {
|
|||
setTimeout(() => this.$root.$emit('castEnd', task, 'task', e), 0);
|
||||
},
|
||||
async score (direction) {
|
||||
if (this.showTaskLockIcon) return;
|
||||
if (this.task.type === 'habit' && !this.task[direction]) return;
|
||||
if (this.isYesterdaily === true) {
|
||||
await this.beforeTaskScore(this.task);
|
||||
this.task.completed = !this.task.completed;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
import moment from 'moment';
|
||||
import { mapState } from '@/libs/store';
|
||||
import scoreTask from '@/mixins/scoreTask';
|
||||
import sync from '@/mixins/sync';
|
||||
import Task from './task';
|
||||
import LoadingSpinner from '../ui/loadingSpinner';
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ export default {
|
|||
Task,
|
||||
LoadingSpinner,
|
||||
},
|
||||
mixins: [scoreTask],
|
||||
mixins: [scoreTask, sync],
|
||||
props: {
|
||||
yesterDailies: {
|
||||
type: Array,
|
||||
|
|
@ -180,6 +181,7 @@ export default {
|
|||
|
||||
this.isLoading = false;
|
||||
this.$root.$emit('bv::hide::modal', 'yesterdaily');
|
||||
if (this.$route.fullPath.indexOf('task-information') !== -1) this.sync();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -114,15 +114,20 @@ export function canEdit (store) {
|
|||
};
|
||||
}
|
||||
|
||||
function _nonInteractive (task) {
|
||||
return (task.group && task.group.id && !task.userId)
|
||||
|| (task.challenge && task.challenge.id && !task.userId)
|
||||
|| (task.group && task.group.approval && task.group.approval.requested
|
||||
&& task.type !== 'habit');
|
||||
function _nonInteractive (task, userId) {
|
||||
if (task.userId) return false;
|
||||
if (task.challenge && task.challenge.id) return true;
|
||||
if (
|
||||
task.group
|
||||
&& task.group.assignedUsers.length > 0
|
||||
&& task.group.assignedUsers.indexOf(userId) === -1
|
||||
) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getTaskClasses (store) {
|
||||
const userPreferences = store.state.user.data.preferences;
|
||||
const userId = store.state.user.data._id;
|
||||
|
||||
// Purpose can be one of the following strings:
|
||||
// Edit Modal: edit-modal-bg, edit-modal-text, edit-modal-icon
|
||||
|
|
@ -171,7 +176,7 @@ export function getTaskClasses (store) {
|
|||
if (type === 'todo' || type === 'daily') {
|
||||
if (task.completed || (!shouldDo(dueDate, task, userPreferences) && type === 'daily')) {
|
||||
return {
|
||||
bg: _nonInteractive(task) ? 'task-disabled-daily-todo-control-bg-noninteractive' : 'task-disabled-daily-todo-control-bg',
|
||||
bg: _nonInteractive(task, userId) ? 'task-disabled-daily-todo-control-bg-noninteractive' : 'task-disabled-daily-todo-control-bg',
|
||||
checkbox: 'task-disabled-daily-todo-control-checkbox',
|
||||
inner: 'task-disabled-daily-todo-control-inner',
|
||||
content: 'task-disabled-daily-todo-control-content',
|
||||
|
|
@ -179,28 +184,28 @@ export function getTaskClasses (store) {
|
|||
}
|
||||
|
||||
return {
|
||||
bg: _nonInteractive(task) ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`,
|
||||
bg: _nonInteractive(task, userId) ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`,
|
||||
checkbox: `task-${color}-control-checkbox`,
|
||||
inner: `task-${color}-control-inner-daily-todo`,
|
||||
icon: `task-${color}-control-icon`,
|
||||
};
|
||||
} if (type === 'reward') {
|
||||
return {
|
||||
bg: _nonInteractive(task) ? 'task-reward-control-bg-noninteractive' : 'task-reward-control-bg',
|
||||
bg: _nonInteractive(task, userId) ? 'task-reward-control-bg-noninteractive' : 'task-reward-control-bg',
|
||||
};
|
||||
} if (type === 'habit') {
|
||||
return {
|
||||
up: task.up
|
||||
? {
|
||||
bg: _nonInteractive(task) ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`,
|
||||
inner: _nonInteractive(task) ? `task-${color}-control-inner-habit-noninteractive` : `task-${color}-control-inner-habit`,
|
||||
bg: _nonInteractive(task, userId) ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`,
|
||||
inner: _nonInteractive(task, userId) ? `task-${color}-control-inner-habit-noninteractive` : `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: _nonInteractive(task) ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`,
|
||||
inner: _nonInteractive(task) ? `task-${color}-control-inner-habit-noninteractive` : `task-${color}-control-inner-habit`,
|
||||
bg: _nonInteractive(task, userId) ? `task-${color}-control-bg-noninteractive` : `task-${color}-control-bg`,
|
||||
inner: _nonInteractive(task, userId) ? `task-${color}-control-inner-habit-noninteractive` : `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` },
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ function _gainMP (user, val) {
|
|||
// ===== CONSTITUTION =====
|
||||
// TODO Decreases HP loss from bad habits / missed dailies by 0.5% per point.
|
||||
function _subtractPoints (user, task, stats, delta) {
|
||||
if (task.group.id && !task.userId) return stats.hp;
|
||||
if (task.group.id && !task.userId && task.type === 'daily') return stats.hp;
|
||||
let conBonus = 1 - statsComputed(user).con / 250;
|
||||
if (conBonus < 0.1) conBonus = 0.1;
|
||||
|
||||
|
|
|
|||
|
|
@ -654,13 +654,6 @@ api.updateTask = {
|
|||
// the other of the keys when using .toObject()
|
||||
// see https://github.com/Automattic/mongoose/issues/2749
|
||||
|
||||
task.group.approval.required = false;
|
||||
if (sanitizedObj.requiresApproval) {
|
||||
task.group.approval.required = true;
|
||||
}
|
||||
if (sanitizedObj.sharedCompletion) {
|
||||
task.group.sharedCompletion = sanitizedObj.sharedCompletion;
|
||||
}
|
||||
if (sanitizedObj.managerNotes) {
|
||||
task.group.managerNotes = sanitizedObj.managerNotes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,26 +208,13 @@ api.assignTask = {
|
|||
const group = await Group.getGroup({ user, groupId: task.group.id, fields: groupFields });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
|
||||
if (canNotEditTasks(group, user, assignedUserId)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
|
||||
const promises = [];
|
||||
const taskText = task.text;
|
||||
const userName = `@${user.auth.local.username}`;
|
||||
|
||||
if (user._id === assignedUserId) {
|
||||
const managerIds = Object.keys(group.managers);
|
||||
managerIds.push(group.leader);
|
||||
const managers = await User.find({ _id: managerIds }, 'notifications preferences').exec();
|
||||
managers.forEach(manager => {
|
||||
if (manager._id === user._id) return;
|
||||
manager.addNotification('GROUP_TASK_CLAIMED', {
|
||||
message: res.t('taskClaimed', { userName, taskText }, manager.preferences.language),
|
||||
groupId: group._id,
|
||||
taskId: task._id,
|
||||
});
|
||||
promises.push(manager.save());
|
||||
});
|
||||
} else {
|
||||
if (user._id !== assignedUserId) {
|
||||
assignedUser.addNotification('GROUP_TASK_ASSIGNED', {
|
||||
message: res.t('youHaveBeenAssignedTask', { managerName: userName, taskText }),
|
||||
taskId: task._id,
|
||||
|
|
@ -283,7 +270,7 @@ api.unassignTask = {
|
|||
const group = await Group.getGroup({ user, groupId: task.group.id, fields });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
|
||||
if (canNotEditTasks(group, user, assignedUserId)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
|
||||
await group.unlinkTask(task, assignedUser);
|
||||
|
||||
|
|
@ -531,7 +518,7 @@ api.getGroupApprovals = {
|
|||
if (canNotEditTasks(group, user)) {
|
||||
approvals = await Tasks.Task.find({
|
||||
'group.id': groupId,
|
||||
'group.approval.approved': false,
|
||||
'group.approval.approved': { $ne: true },
|
||||
'group.approval.requested': true,
|
||||
'group.assignedUsers': user._id,
|
||||
userId: user._id,
|
||||
|
|
@ -541,7 +528,7 @@ api.getGroupApprovals = {
|
|||
} else {
|
||||
approvals = await Tasks.Task.find({
|
||||
'group.id': groupId,
|
||||
'group.approval.approved': false,
|
||||
'group.approval.approved': { $ne: true },
|
||||
'group.approval.requested': true,
|
||||
}, 'userId group text')
|
||||
.populate('userId', 'profile')
|
||||
|
|
|
|||
|
|
@ -216,8 +216,6 @@ TaskSchema.statics.findMultipleByIdOrAlias = async function findByIdOrAlias (
|
|||
if (!userId) throw new Error('User identifier is a required argument');
|
||||
|
||||
const query = _.cloneDeep(additionalQueries);
|
||||
query.userId = userId;
|
||||
|
||||
const ids = [];
|
||||
const aliases = [];
|
||||
|
||||
|
|
@ -229,10 +227,18 @@ TaskSchema.statics.findMultipleByIdOrAlias = async function findByIdOrAlias (
|
|||
}
|
||||
});
|
||||
|
||||
query.$or = [
|
||||
{ _id: { $in: ids } },
|
||||
{ alias: { $in: aliases } },
|
||||
];
|
||||
if (ids.length > 0 && aliases.length > 0) {
|
||||
query.$or = [
|
||||
{ _id: { $in: ids } },
|
||||
{ alias: { $in: aliases } },
|
||||
];
|
||||
} else if (ids.length > 0) {
|
||||
query._id = { $in: ids };
|
||||
} else if (aliases.length > 0) {
|
||||
query.alias = { $in: aliases };
|
||||
} else {
|
||||
throw new Error('No identifiers found.'); // Should be covered by the !identifiers check, but..
|
||||
}
|
||||
|
||||
const tasks = await this.find(query).exec();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue