WIP(teams): display assigned tasks on user's personal board

This commit is contained in:
SabreCat 2022-06-13 16:53:29 -05:00
parent 9fec111c4d
commit a0177fa44d
12 changed files with 106 additions and 136 deletions

View file

@ -1,4 +1,3 @@
import clone from 'lodash/clone';
import filter from 'lodash/filter';
import find from 'lodash/find';
import isArray from 'lodash/isArray';
@ -20,19 +19,26 @@ async function updateTeamTasks (team) {
boardTask.group.assignedDate = undefined;
boardTask.group.assigningUsername = undefined;
boardTask.group.sharedCompletion = undefined;
const assignedUsers = clone(boardTask.group.assignedUsers);
boardTask.group.assignedUsers = null;
for (const assignedUser of assignedUsers) {
for (const assignedUser of boardTask.group.assignedUsers) {
const userTask = find(teamUserTasks, task => task.userId === assignedUser
&& task.group.taskId === boardTask._id);
if (!boardTask.group.assignedUsersDetail) boardTask.group.assignedUsersDetail = {};
if (userTask) {
if (!boardTask.group.assignedUsers) boardTask.group.assignedUsers = {};
boardTask.group.assignedUsers[assignedUser] = {
boardTask.group.assignedUsersDetail[assignedUser] = {
assignedDate: userTask.group.assignedDate,
assigningUsername: userTask.group.assigningUsername,
completed: userTask.completed || false,
completedDate: userTask.dateCompleted,
};
toSave.push(Tasks.Task.findByIdAndDelete(userTask._id));
} else {
boardTask.group.assignedUsersDetail[assignedUser] = {
assignedDate: new Date(),
assigningUsername: null,
completed: false,
completedDate: null,
};
}
}
boardTask.markModified('group');

View file

@ -54,16 +54,16 @@ async function updateTeamTasks (team) {
let processChecklist = false;
let assignments = 0;
let completions = 0;
for (const assignedUser in daily.group.assignedUsers) {
if (Object.prototype.hasOwnProperty.call(daily.group.assignedUsers, assignedUser)) {
for (const assignedUser in daily.group.assignedUsersDetail) {
if (Object.prototype.hasOwnProperty.call(daily.group.assignedUsersDetail, assignedUser)) {
assignments += 1;
if (daily.group.assignedUsers[assignedUser].completed) {
if (daily.group.assignedUsersDetail[assignedUser].completed) {
completions += 1;
daily.group.assignedUsers[assignedUser].completed = false;
daily.group.assignedUsersDetail[assignedUser].completed = false;
}
}
}
if (completions > 0) daily.markModified('group.assignedUsers');
if (completions > 0) daily.markModified('group.assignedUsersDetail');
if (daily.completed) {
processChecklist = true;
daily.completed = false;

View file

@ -41,7 +41,6 @@
</div>
<div
class="claim-bottom-message d-flex align-items-center"
v-if="group"
>
<div
class="mr-auto ml-2"
@ -215,8 +214,6 @@
</style>
<script>
import findIndex from 'lodash/findIndex';
import keys from 'lodash/keys';
import moment from 'moment';
import reduce from 'lodash/reduce';
import { mapState } from '@/libs/store';
@ -239,21 +236,18 @@ export default {
computed: {
...mapState({ user: 'user.data' }),
userIsAssigned () {
return this.task.group.assignedUsers
&& Boolean(this.task.group.assignedUsers[this.user._id]);
return this.task.group.assignedUsersDetail
&& Boolean(this.task.group.assignedUsersDetail[this.user._id]);
},
userIsManager () {
return this.group
&& (this.group.leader.id === this.user._id || this.group.managers[this.user._id]);
},
assignedUsersKeys () {
return keys(this.task.group.assignedUsers);
},
assignedUsersCount () {
return this.assignedUsersKeys.length;
return this.task.group.assignedUsers.length;
},
completionsCount () {
return reduce(this.task.group.assignedUsers, (count, assignment) => {
return reduce(this.task.group.assignedUsersDetail, (count, assignment) => {
if (assignment.completed) return count + 1;
return count;
}, 0);
@ -261,10 +255,9 @@ export default {
completionsList () {
if (this.assignedUsersCount === 1) return [];
const completionsArray = [];
for (const userId of this.assignedUsersKeys) {
for (const userId of this.task.group.assignedUsers) {
if (userId !== this.user._id) {
const index = findIndex(this.group.members, member => member._id === userId);
const { completedDate } = this.task.group.assignedUsers[userId];
const { completedDate } = this.task.group.assignedUsersDetail[userId];
let completedDateString;
if (moment().diff(completedDate, 'days') > 0) {
completedDateString = `Completed ${moment(completedDate).format('M/D/YY')}`;
@ -273,8 +266,8 @@ export default {
}
completionsArray.push({
userId,
userName: this.group.members[index].auth.local.username,
completed: this.task.group.assignedUsers[userId].completed,
userName: this.task.group.assignedUsersDetail[userId].assignedUsername,
completed: this.task.group.assignedUsersDetail[userId].completed,
completedDate,
completedDateString,
});
@ -290,14 +283,11 @@ export default {
return this.$t('assignedToMembers', { userCount: this.assignedUsersCount });
}
if (this.assignedUsersCount === 1) { // Singly assigned
const userId = this.assignedUsersKeys[0];
const index = findIndex(
this.group.members, member => member._id === userId,
);
const userName = this.group.members[index].auth.local.username;
const userId = this.task.group.assignedUsers[0];
const userName = this.task.group.assignedUsersDetail[userId].assignedUsername;
if (this.task.group.assignedUsers[userId].completed) { // completed
const { completedDate } = this.task.group.assignedUsers[userId];
if (this.task.group.assignedUsersDetail[userId].completed) { // completed
const { completedDate } = this.task.group.assignedUsersDetail[userId];
if (this.userIsAssigned) {
if (moment().diff(completedDate, 'days') > 0) {
return `<strong>You</strong> completed ${moment(completedDate).format('M/D/YY')}`;
@ -317,8 +307,8 @@ export default {
return this.$t('error'); // task is open, or the other conditions aren't hitting right
},
singleAssignLastDone () {
const userId = this.assignedUsersKeys[0];
const completion = this.task.group.assignedUsers[userId];
const userId = this.task.group.assignedUsers[0];
const completion = this.task.group.assignedUsersDetail[userId];
return completion.completedDate;
},
showGreen () {
@ -343,7 +333,7 @@ export default {
taskId: this.task._id,
userId: completion.userId,
});
this.task.group.assignedUsers[completion.userId].completed = false;
this.task.group.assignedUsersDetail[completion.userId].completed = false;
},
},
};

View file

@ -98,7 +98,7 @@
<h3
v-markdown="task.text"
class="task-title markdown"
:class="{ 'has-notes': task.notes || (!isUser && task.group.managerNotes)}"
:class="{ 'has-notes': task.notes }"
></h3>
<menu-dropdown
v-if="!isRunningYesterdailies && showOptions"
@ -177,7 +177,7 @@
</menu-dropdown>
</div>
<div
v-markdown="displayNotes"
v-markdown="task.notes"
class="task-notes small-text"
:class="{'has-checklist': task.notes && hasChecklist}"
></div>
@ -889,7 +889,6 @@
import moment from 'moment';
import { v4 as uuid } from 'uuid';
import isEmpty from 'lodash/isEmpty';
import keys from 'lodash/keys';
import { mapState, mapGetters, mapActions } from '@/libs/store';
import positiveIcon from '@/assets/svg/positive.svg';
@ -1056,19 +1055,15 @@ export default {
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) return false;
if (this.task.group.assignedUsers.length > 0) return false;
return true;
},
showCheckIcon () {
if (this.isGroupTask && this.task.group.assignedUsers
&& this.task.group.assignedUsers[this.user._id]) {
return this.task.group.assignedUsers[this.user._id].completed;
if (this.isGroupTask && this.task.group.assignedUsersDetail
&& this.task.group.assignedUsersDetail[this.user._id]) {
return this.task.group.assignedUsersDetail[this.user._id].completed;
}
return this.task.completed;
},
@ -1076,18 +1071,20 @@ export default {
if (this.isUser) return false;
if (this.isGroupTask) {
if (this.task.completed) {
if (this.task.group.assignedUsers && this.task.group.assignedUsers[this.user._id]) {
if (this.task.group.assignedUsersDetail
&& this.task.group.assignedUsersDetail[this.user._id]
) {
return false;
}
if (this.task.group.completedBy.userId === this.user._id) return false;
if (this.teamManagerAccess) {
if (!this.task.group.assignedUsers) return false;
if (keys(this.task.group.assignedUsers).length === 1) return false;
if (this.task.group.assignedUsers.length === 1) return false;
}
return true;
}
if (this.isOpenTask) return false;
if (this.task.group.assignedUsers && this.task.group.assignedUsers[this.user._id]) {
if (this.task.group.assignedUsersDetail[this.user._id]) {
return false;
}
}
@ -1165,11 +1162,11 @@ export default {
if (
this.isGroupTask && direction === 'down'
&& ['todo', 'daily'].indexOf(this.task.type) !== -1
&& this.task.group.assignedUsers && !this.task.group.assignedUsers[this.user._id]
&& !this.task.group.assignedUsersDetail[this.user._id]
) {
this.$store.dispatch('tasks:needsWork', {
taskId: this.task._id,
userId: keys(this.task.group.assignedUsers)[0],
userId: this.task.group.assignedUsers[0],
});
this.task.completed = false;
return;

View file

@ -71,47 +71,33 @@
>
</div>
<div
v-if="isUserTask || isChallengeTask || isOriginalChallengeTask"
class="form-group mb-0"
>
<label
class="d-flex align-items-center justify-content-between mb-1"
>
<span
:class="cssClass('headings')"
>{{ $t('notes') }}</span>
<small>
<div class="d-flex">
<lockable-label
class="mr-auto"
:class-override="cssClass('headings')"
:locked="groupAccessRequiredAndOnPersonalPage || challengeAccessRequired"
:text="`${$t('notes')}`"
/>
<small
class="my-1"
>
<a
target="_blank"
href="https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet"
:class="cssClass('headings')"
>{{ $t('markdownHelpLink') }}</a>
</small>
</label>
</div>
<textarea
v-model="task.notes"
class="form-control input-notes"
:class="cssClass('input')"
:disabled="groupAccessRequiredAndOnPersonalPage || challengeAccessRequired"
:placeholder="$t('addNotes')"
></textarea>
</div>
<div
v-if="showManagerNotes"
class="form-group mb-0 mt-3"
>
<lockable-label
:class-override="cssClass('headings')"
:locked="groupAccessRequiredAndOnPersonalPage"
:text="$t('managerNotes')"
/>
<textarea
v-model="managerNotes"
class="form-control input-notes"
:class="cssClass('input')"
:placeholder="$t('addNotes')"
:disabled="groupAccessRequiredAndOnPersonalPage"
></textarea>
</div>
<div
v-if="task.group && task.group.assignedDate && !task.group.assigningUsername"
class="mt-3 mb-n2"
@ -405,7 +391,7 @@
</div>
</div>
<div
v-if="isUserTask"
v-if="!challengeAccessRequired && !groupAccessRequiredAndOnPersonalPage"
class="tags-select option mt-3"
>
<div class="tags-inline form-group row">
@ -1085,7 +1071,6 @@ export default {
calendar: calendarIcon,
grip: gripIcon,
}),
managerNotes: '',
members: [],
membersNameAndId: [],
memberNamesById: {},
@ -1149,12 +1134,6 @@ export default {
&& !this.isOriginalChallengeTask
&& (!this.groupAccessRequiredAndOnPersonalPage || this.checklist.length > 0);
},
showManagerNotes () {
return Boolean(this.task.group && this.task.group.managerNotes)
|| (
!this.groupAccessRequiredAndOnPersonalPage && this.managers.indexOf(this.user._id) !== -1
);
},
isChallengeTask () {
return Boolean(this.task.challenge && this.task.challenge.id);
},
@ -1261,10 +1240,6 @@ export default {
createTag: 'tags:createTag',
}),
async syncTask () {
if (this.task && this.task.group && this.task.group.managerNotes) {
this.managerNotes = this.task.group.managerNotes;
}
if (this.groupId) {
const members = await this.$store.dispatch('members:getGroupMembers', {
groupId: this.groupId,
@ -1282,7 +1257,7 @@ export default {
});
this.assignedMembers = [];
if (this.task.group?.assignedUsers) {
this.assignedMembers = keys(this.task.group.assignedUsers);
this.assignedMembers = this.task.group.assignedUsers;
}
}
@ -1443,12 +1418,6 @@ export default {
if (!this.canSave) return;
if (this.newChecklistItem) this.addChecklistItem();
// TODO Fix up permissions on task.group so we don't have to keep doing these hacks
if (this.groupId) {
this.task.managerNotes = this.managerNotes;
this.task.group.managerNotes = this.managerNotes;
}
if (this.task.type === 'reward' && this.task.value === '') {
this.task.value = 0;
}
@ -1472,8 +1441,8 @@ export default {
assignedUserIds: this.assignedMembers,
});
this.assignedMembers.forEach(memberId => {
if (!this.task.assignedUsers) this.task.assignedUsers = {};
this.task.assignedUsers[memberId] = {
if (!this.task.assignedUsersDetail) this.task.assignedUsersDetail = {};
this.task.assignedUsersDetail[memberId] = {
assignedDate: new Date(),
assigningUsername: this.user.auth.local.username,
completed: false,
@ -1500,7 +1469,6 @@ export default {
this.$root.$emit('bv::hide::modal', 'task-modal');
},
onClose () {
if (this.task.group && this.task.group.managerNotes) this.managerNotes = null;
this.newChecklistItem = '';
this.$emit('cancel');
},

View file

@ -105,11 +105,8 @@ export function getActiveFilter (type, filterType, isChallenge) {
return activeFilter(taskFilters)(type, filterType);
}
export function sortAndFilterTasks (tasks, selectedFilter, group = false) {
export function sortAndFilterTasks (tasks, selectedFilter) {
let sortedTasks = tasks.filter(selectedFilter.filterFn);
if (!group) {
sortedTasks = sortedTasks.filter(task => !task.group.id);
}
if (selectedFilter.sort) {
sortedTasks = sortBy(sortedTasks, selectedFilter.sort);
}

View file

@ -176,8 +176,8 @@ export function getTaskClasses (store) {
if (type === 'todo' || type === 'daily') {
if (task.completed
|| (!shouldDo(dueDate, task, userPreferences) && type === 'daily')
|| (task.group.assignedUsers && task.group.assignedUsers[userId]
&& task.group.assignedUsers[userId].completed)
|| (task.group.assignedUsersDetail && task.group.assignedUsersDetail[userId]
&& task.group.assignedUsersDetail[userId].completed)
) {
return {
bg: _nonInteractive(task, userId) ? 'task-disabled-daily-todo-control-bg-noninteractive' : 'task-disabled-daily-todo-control-bg',

View file

@ -302,9 +302,9 @@ export default function scoreTask (options = {}, req = {}, analytics) {
task.completed = true;
task.streak += 1;
} else {
task.group.assignedUsers[user._id].completed = true;
task.group.assignedUsers[user._id].completedDate = new Date();
if (!find(task.group.assignedUsers, assignedUser => !assignedUser.completed)) {
task.group.assignedUsersDetail[user._id].completed = true;
task.group.assignedUsersDetail[user._id].completedDate = new Date();
if (!find(task.group.assignedUsersDetail, assignedUser => !assignedUser.completed)) {
task.dateCompleted = new Date();
task.completed = true;
task.streak += 1;
@ -332,16 +332,16 @@ export default function scoreTask (options = {}, req = {}, analytics) {
}
} else if (direction === 'down') {
if (task.group.id) {
if (!task.group.assignedUsers
|| !find(task.group.assignedUsers, assignedUser => !assignedUser.completed)
if (!task.group.assignedUsersDetail
|| !find(task.group.assignedUsersDetail, assignedUser => !assignedUser.completed)
) {
task.streak -= 1;
task.completed = false;
}
if (task.group.completedBy) task.group.completedBy = {};
if (task.group.assignedUsers && task.group.assignedUsers[user._id]) {
task.group.assignedUsers[user._id].completed = false;
task.group.assignedUsers[user._id].completedDate = undefined;
if (task.group.assignedUsersDetail && task.group.assignedUsersDetail[user._id]) {
task.group.assignedUsersDetail[user._id].completed = false;
task.group.assignedUsersDetail[user._id].completedDate = undefined;
}
if (task.markModified) task.markModified('group');
} else {
@ -372,9 +372,9 @@ export default function scoreTask (options = {}, req = {}, analytics) {
};
task.completed = true;
} else {
task.group.assignedUsers[user._id].completed = true;
task.group.assignedUsers[user._id].completedDate = new Date();
if (!find(task.group.assignedUsers, assignedUser => !assignedUser.completed)) {
task.group.assignedUsersDetail[user._id].completed = true;
task.group.assignedUsersDetail[user._id].completedDate = new Date();
if (!find(task.group.assignedUsersDetail, assignedUser => !assignedUser.completed)) {
task.dateCompleted = new Date();
task.completed = true;
}
@ -389,9 +389,9 @@ export default function scoreTask (options = {}, req = {}, analytics) {
task.dateCompleted = undefined;
if (task.group.id) {
if (task.group.completedBy) task.group.completedBy = {};
if (task.group.assignedUsers && task.group.assignedUsers[user._id]) {
task.group.assignedUsers[user._id].completed = false;
task.group.assignedUsers[user._id].completedDate = undefined;
if (task.group.assignedUsersDetail && task.group.assignedUsersDetail[user._id]) {
task.group.assignedUsersDetail[user._id].completed = false;
task.group.assignedUsersDetail[user._id].completedDate = undefined;
}
if (task.markModified) task.markModified('group');
}

View file

@ -355,10 +355,10 @@ api.taskNeedsWork = {
if (['daily', 'todo'].indexOf(task.type) === -1) {
throw new BadRequest('Cannot roll back use of Habits or Rewards.');
}
if (!task.group.assignedUsers || !task.group.assignedUsers[assignedUserId]) {
if (!task.group.assignedUsersDetail || !task.group.assignedUsersDetail[assignedUserId]) {
throw new BadRequest('User not assigned to this task.');
}
if (!task.group.assignedUsers[assignedUserId].completed) {
if (!task.group.assignedUsersDetail[assignedUserId].completed) {
throw new BadRequest('Task not completed by this user.');
}

View file

@ -150,7 +150,7 @@ async function getTasks (req, res, options = {}) {
dueDate,
} = options;
let query = { userId: user._id };
let query = { $or: [{ userId: user._id }, { 'group.assignedUsers': user._id }] };
let limit;
let sort;
const owner = group || challenge || user;
@ -188,10 +188,12 @@ async function getTasks (req, res, options = {}) {
query.type = type.slice(0, -1); // removing the final "s"
}
} else {
query.$or = [ // Exclude completed todos
{ type: 'todo', completed: false },
{ type: { $in: ['habit', 'daily', 'reward'] } },
];
query.$and = [{
$or: [ // Exclude completed todos
{ type: 'todo', completed: false },
{ type: { $in: ['habit', 'daily', 'reward'] } },
],
}];
}
const mQuery = Tasks.Task.find(query);

View file

@ -1447,18 +1447,24 @@ schema.methods.syncTask = async function groupSyncTask (taskToSync, users, assig
for (const user of users) {
const assignmentData = {
assignedDate: new Date(),
assignedUsername: user.auth.local.username,
assigningUsername: assigningUser && assigningUser._id !== user._id
? assigningUser.auth.local.username : null,
completed: false,
};
if (!taskToSync.group.assignedUsers) {
taskToSync.group.assignedUsers = {};
taskToSync.group.assignedUsers = [];
}
if (!taskToSync.group.assignedUsers[user._id]) {
taskToSync.group.assignedUsers[user._id] = assignmentData;
taskToSync.group.assignedUsers.push(user._id);
if (!taskToSync.group.assignedUsersDetail) {
taskToSync.group.assignedUsersDetail = {};
}
taskToSync.markModified('group.assignedUsers');
if (!taskToSync.group.assignedUsersDetail[user._id]) {
taskToSync.group.assignedUsersDetail[user._id] = assignmentData;
}
taskToSync.markModified('group.assignedUsersDetail');
// Sync tags
const userTags = user.tags;
@ -1488,14 +1494,16 @@ schema.methods.unlinkTask = async function groupUnlinkTask (
) {
const findQuery = {
'group.taskId': unlinkingTask._id,
userId: user._id,
'group.assignedUsers': user._id,
};
delete unlinkingTask.group.assignedUsers[user._id];
if (Object.keys(unlinkingTask.group.assignedUsers).length === 0) {
delete unlinkingTask.group.assignedUsersDetail[user._id];
const assignedUserIndex = unlinkingTask.group.assignedUsers.indexOf(user._id);
unlinkingTask.group.assignedUsers.splice(assignedUserIndex, 1);
if (unlinkingTask.group.assignedUsers.length === 0) {
unlinkingTask.group.assignedUsers = undefined;
}
unlinkingTask.markModified('group.assignedUsers');
unlinkingTask.markModified('group');
const promises = [unlinkingTask.save()];

View file

@ -129,10 +129,12 @@ export const TaskSchema = new Schema({
id: { $type: String, ref: 'Group', validate: [v => validator.isUUID(v), 'Invalid uuid for group task.'] },
assignedDate: { $type: Date }, // To be removed
assigningUsername: { $type: String }, // To be removed
assignedUsers: {
assignedUsers: [{ $type: String, ref: 'User', validate: [v => validator.isUUID(v), 'Invalid uuid for group assigned user.'] }],
assignedUsersDetail: {
$type: Schema.Types.Mixed,
// key is assigned UUID, with
// { assignedDate: Date,
// assignedUsername: '@username',
// assigningUsername: '@username',
// completed: Boolean,
// completedDate: Date }