Client Fixed (#9017)

* fix spacing between rewards and items

* fix rewards description

* rewards cost in bold

* fix gp notifications

* fix dailies gray text

* fix cancel in task edit modal

* tags: use AND not OR for filtering

* fix tasksDefaults so that monthlies can be created correctly

* tags: usable if no task selected, saving checklist and tags saved the one being added without requiting to press enter

* remove tags from tasks when they are deleted

* fix tags removal when multiple tags are deleted and fix tags editing
This commit is contained in:
Matteo Pagliazzi
2017-09-02 18:08:32 +02:00
committed by GitHub
parent c2aaa9b592
commit 0424d214c5
11 changed files with 92 additions and 60 deletions

View File

@@ -54,7 +54,7 @@ export default {
challengeId: this.brokenChallengeTask.challenge.id, challengeId: this.brokenChallengeTask.challenge.id,
keep: keepOption, keep: keepOption,
}); });
await this.$store.dispatch('tasks:fetchUserTasks', true); await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true});
this.close(); this.close();
return; return;
} }

View File

@@ -18,7 +18,7 @@
@editTask="editTask", @editTask="editTask",
:group='group', :group='group',
) )
template(v-if="isUser === true && type === 'reward' && activeFilter.label !== 'custom'") template(v-if="hasRewardsList")
.reward-items .reward-items
shopItem( shopItem(
v-for="reward in inAppRewards", v-for="reward in inAppRewards",
@@ -31,7 +31,7 @@
.column-background( .column-background(
v-if="isUser === true", v-if="isUser === true",
:class="{'initial-description': tasks[`${type}s`].length === 0}", :class="{'initial-description': initialColumnDescription}",
ref="columnBackground", ref="columnBackground",
) )
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once) .svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
@@ -46,7 +46,7 @@
height: 556px; height: 556px;
} }
.task + .reward-items { .task-wrapper + .reward-items {
margin-top: 16px; margin-top: 16px;
} }
@@ -243,6 +243,17 @@ export default {
inAppRewards () { inAppRewards () {
return inAppRewards(this.user); return inAppRewards(this.user);
}, },
hasRewardsList () {
return this.isUser === true && this.type === 'reward' && this.activeFilter.label !== 'custom';
},
initialColumnDescription () {
// Show the column description in the middle only if there are no elements (tasks or in app items)
if (this.hasRewardsList) {
if (this.inAppRewards && this.inAppRewards.length >= 0) return false;
}
return this.tasks[`${this.type}s`].length === 0;
},
}, },
watch: { watch: {
taskList: { taskList: {
@@ -299,11 +310,11 @@ export default {
const selectedTags = this.selectedTags; const selectedTags = this.selectedTags;
if (selectedTags && selectedTags.length > 0) { if (selectedTags && selectedTags.length > 0) {
const hasSelectedTag = task.tags.find(tagId => { const hasAllSelectedTag = selectedTags.every(tagId => {
return selectedTags.indexOf(tagId) !== -1; return task.tags.indexOf(tagId) !== -1;
}); });
if (!hasSelectedTag) return false; if (!hasAllSelectedTag) return false;
} }
// Text // Text

View File

@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
div .task-wrapper
broken-task-modal(:brokenChallengeTask='brokenChallengeTask') broken-task-modal(:brokenChallengeTask='brokenChallengeTask')
.task(@click='castEnd($event, task)') .task(@click='castEnd($event, task)')
approval-header(:task='task', v-if='this.task.group.id', :group='group') approval-header(:task='task', v-if='this.task.group.id', :group='group')
@@ -260,6 +260,7 @@ div
margin-top: 4px; margin-top: 4px;
color: $yellow-10; color: $yellow-10;
font-style: initial; font-style: initial;
font-weight: bold;
} }
} }
</style> </style>
@@ -358,13 +359,11 @@ export default {
return false; return false;
}, },
controlClass () { controlClass () {
const dueDate = this.dueDate || new Date(); return this.getTaskClasses(this.task, 'control', this.dueDate);
return this.getTaskClasses(this.task, 'control', dueDate);
}, },
contentClass () { contentClass () {
const classes = []; const classes = [];
const dueDate = this.dueDate || new Date(); classes.push(this.getTaskClasses(this.task, 'content', this.dueDate));
classes.push(this.getTaskClasses(this.task, 'content'), dueDate);
if (this.task.type === 'reward' || this.task.type === 'habit') { if (this.task.type === 'reward' || this.task.type === 'habit') {
classes.push('no-right-border'); classes.push('no-right-border');
} }

View File

@@ -11,10 +11,10 @@
slot="modal-header", slot="modal-header",
:class="[cssClass]", :class="[cssClass]",
) )
.row .clearfix
h1.col-8 {{ title }} h1.float-left {{ title }}
.col-4 .float-right.d-flex.align-items-center
span.cancel-task-btn(v-once, @click="cancel()") {{ $t('cancel') }} span.cancel-task-btn.mr-2(v-if="purpose !== 'create'", v-once, @click="cancel()") {{ $t('cancel') }}
button.btn.btn-secondary(type="submit", v-once) {{ $t('save') }} button.btn.btn-secondary(type="submit", v-once) {{ $t('save') }}
.form-group .form-group
label(v-once) {{ `${$t('title')}*` }} label(v-once) {{ `${$t('title')}*` }}
@@ -335,8 +335,12 @@
} }
} }
.cancel-task-btn { .delete-task-btn, .cancel-task-btn {
margin-right: .5em; cursor: pointer;
&:hover, &:focus, &:active {
text-decoration: underline;
}
} }
.task-modal-footer { .task-modal-footer {
@@ -346,13 +350,7 @@
border-top-right-radius: 8px; border-top-right-radius: 8px;
margin-top: 50px; margin-top: 50px;
.delete-task-btn, .cancel-task-btn {
cursor: pointer;
&:hover, &:focus, &:active {
text-decoration: underline;
}
}
.delete-task-btn { .delete-task-btn {
color: $red-50; color: $red-50;
@@ -528,7 +526,7 @@ export default {
completed: false, completed: false,
}); });
this.newChecklistItem = null; this.newChecklistItem = null;
e.preventDefault(); if (e) e.preventDefault();
}, },
removeChecklistItem (i) { removeChecklistItem (i) {
this.task.checklist.splice(i, 1); this.task.checklist.splice(i, 1);
@@ -537,6 +535,8 @@ export default {
return moment.weekdaysMin(dayNumber); return moment.weekdaysMin(dayNumber);
}, },
submit () { submit () {
if (this.newChecklistItem) this.addChecklistItem();
if (this.purpose === 'create') { if (this.purpose === 'create') {
if (this.challengeId) { if (this.challengeId) {
this.$store.dispatch('tasks:createChallengeTasks', { this.$store.dispatch('tasks:createChallengeTasks', {

View File

@@ -12,7 +12,11 @@
.input-group .input-group
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText") input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
.filter-panel(v-if="isFilterPanelOpen") .filter-panel(v-if="isFilterPanelOpen")
.tags-category.d-flex(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key") .tags-category.d-flex(
v-for="tagsType in tagsByType",
v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'",
:key="tagsType.key"
)
.tags-header .tags-header
strong(v-once) {{ $t(tagsType.key) }} strong(v-once) {{ $t(tagsType.key) }}
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }} a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
@@ -21,7 +25,7 @@
template(v-if="editingTags && tagsType.key === 'tags'") template(v-if="editingTags && tagsType.key === 'tags'")
.col-6(v-for="(tag, tagIndex) in tagsSnap") .col-6(v-for="(tag, tagIndex) in tagsSnap")
.inline-edit-input-group.tag-edit-item.input-group .inline-edit-input-group.tag-edit-item.input-group
input.tag-edit-input.inline-edit-input.form-control(type="text", :value="tag.name") input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name")
span.input-group-btn(@click="removeTag(tagIndex)") span.input-group-btn(@click="removeTag(tagIndex)")
.svg-icon.destroy-icon(v-html="icons.destroy") .svg-icon.destroy-icon(v-html="icons.destroy")
.col-6 .col-6
@@ -377,6 +381,7 @@ export default {
this.tagsSnap.splice(index, 1); this.tagsSnap.splice(index, 1);
}, },
saveTags () { saveTags () {
if (this.newTag) this.addTag();
this.setUser({tags: this.tagsSnap}); this.setUser({tags: this.tagsSnap});
this.cancelTagsEditing(); this.cancelTagsEditing();
}, },

View File

@@ -6,23 +6,8 @@ export default {
...mapState({notifications: 'notificationStore'}), ...mapState({notifications: 'notificationStore'}),
}, },
methods: { methods: {
/**
Show '+ 5 {gold_coin} 3 {silver_coin}'
*/
coins (money) { coins (money) {
let absolute; return this.round(money, 2);
let gold;
let silver;
absolute = Math.abs(money);
gold = Math.floor(absolute);
silver = Math.floor((absolute - gold) * 100);
if (gold && silver > 0) {
return `${gold} <span class='notification-icon shop_gold'></span> ${silver} <span class='notification-icon shop_silver'></span>`;
} else if (gold > 0) {
return `${gold} <span class='notification-icon shop_gold'></span>`;
} else if (silver > 0) {
return `${silver} <span class='notification-icon shop_silver'></span>`;
}
}, },
crit (val) { crit (val) {
let message = `${this.$t('critBonus')} ${Math.round(val)} %`; let message = `${this.$t('critBonus')} ${Math.round(val)} %`;
@@ -105,8 +90,8 @@ export default {
} }
return sign; return sign;
}, },
round (number) { round (number, nDigits) {
return Math.abs(number.toFixed(1)); return Math.abs(number.toFixed(nDigits || 1));
}, },
notify (html, type, icon, sign) { notify (html, type, icon, sign) {
this.notifications.push({ this.notifications.push({

View File

@@ -3,7 +3,7 @@ import axios from 'axios';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
export function fetchUserTasks (store, forceLoad = false) { export function fetchUserTasks (store, options = {}) {
return loadAsyncResource({ return loadAsyncResource({
store, store,
path: 'tasks', path: 'tasks',
@@ -15,7 +15,7 @@ export function fetchUserTasks (store, forceLoad = false) {
return store.dispatch('tasks:order', [response.data.data, userResource.data.tasksOrder]); return store.dispatch('tasks:order', [response.data.data, userResource.data.tasksOrder]);
}); });
}, },
forceLoad, forceLoad: options.forceLoad,
}); });
} }

View File

@@ -19,7 +19,7 @@ export function fetch (store, forceLoad = false) { // eslint-disable-line no-sha
}); });
} }
export function set (store, changes) { export async function set (store, changes) {
const user = store.state.user.data; const user = store.state.user.data;
for (let key in changes) { for (let key in changes) {
@@ -30,6 +30,22 @@ export function set (store, changes) {
}); });
user.tags = changes[key].concat(oldTags); user.tags = changes[key].concat(oldTags);
// Remove deleted tags from tasks
const userTasksByType = (await store.dispatch('tasks:fetchUserTasks')).data; // eslint-disable-line no-await-in-loop
Object.keys(userTasksByType).forEach(taskType => {
userTasksByType[taskType].forEach(task => {
const tagsIndexesToRemove = [];
task.tags.forEach((tagId, tagIndex) => {
if (user.tags.find(tag => tag.id === tagId)) return; // eslint-disable-line max-nested-callbacks
tagsIndexesToRemove.push(tagIndex);
});
tagsIndexesToRemove.forEach(i => task.tags.splice(i, 1));
});
});
} else { } else {
setProps(user, key, changes[key]); setProps(user, key, changes[key]);
} }

View File

@@ -61,7 +61,7 @@ export function getTaskClasses (store) {
} }
break; break;
case 'content': case 'content':
if (type === 'daily' && (task.completed || !task.isDue) || type === 'todo' && task.completed) { if (type === 'daily' && (task.completed || !shouldDo(dueDate, task, userPreferences)) || type === 'todo' && task.completed) {
return 'task-daily-todo-content-disabled'; return 'task-daily-todo-content-disabled';
} }
break; break;

View File

@@ -25,8 +25,13 @@ module.exports = function taskDefaults (task = {}) {
challenge: { challenge: {
shortName: 'None', shortName: 'None',
}, },
group: {}, group: {
yesterDaily: true, approval: {
required: false,
approved: false,
requested: false,
},
},
reminders: [], reminders: [],
attribute: 'str', attribute: 'str',
createdAt: new Date(), // TODO these are going to be overwritten by the server... createdAt: new Date(), // TODO these are going to be overwritten by the server...
@@ -74,6 +79,9 @@ module.exports = function taskDefaults (task = {}) {
startDate: moment().startOf('day').toDate(), startDate: moment().startOf('day').toDate(),
everyX: 1, everyX: 1,
frequency: 'weekly', frequency: 'weekly',
daysOfMonth: [],
weeksOfMonth: [],
yesterDaily: true,
}); });
} }

View File

@@ -285,6 +285,8 @@ api.updateUser = {
async handler (req, res) { async handler (req, res) {
let user = res.locals.user; let user = res.locals.user;
let promisesForTagsRemoval = [];
_.each(req.body, (val, key) => { _.each(req.body, (val, key) => {
let purchasable = requiresPurchase[key]; let purchasable = requiresPurchase[key];
@@ -321,20 +323,26 @@ api.updateUser = {
user.tags.push(Tag.sanitize(t)); user.tags.push(Tag.sanitize(t));
}); });
// Remove from all the tasks TODO test // Remove from all the tasks
Tasks.Task.update({ // NOTE each tag to remove requires a query
userId: user._id,
}, { promisesForTagsRemoval = removedTagsIds.map(tagId => {
$pull: { return Tasks.Task.update({
tags: {$in: [removedTagsIds]}, userId: user._id,
}, }, {
}, {multi: true}).exec(); $pull: {
tags: tagId,
},
}, {multi: true}).exec();
});
} else { } else {
throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key })); throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key }));
} }
}); });
await user.save();
await Promise.all([user.save()].concat(promisesForTagsRemoval));
return res.respond(200, user); return res.respond(200, user);
}, },
}; };