Update reminder UI/UX
This commit is contained in:
@@ -1,94 +1,121 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-list-group-item class="flex-column align-items-start px-2 py-2 border-0">
|
||||
<b-form
|
||||
class="import-form"
|
||||
@submit.prevent
|
||||
>
|
||||
<b-form-group :label="$t('reminder.edit.titleLabel')">
|
||||
<b-form-input
|
||||
v-model="title"
|
||||
data-test-id="input-title"
|
||||
required
|
||||
type="text"
|
||||
:placeholder="$t('reminder.edit.titlePlaceholder')"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('reminder.edit.notesLabel')">
|
||||
<b-form-textarea
|
||||
v-model="notes"
|
||||
data-test-id="textarea-notes"
|
||||
:placeholder="$t('reminder.edit.notesPlaceholder')"
|
||||
rows="6"
|
||||
max-rows="10"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('reminder.edit.remindAtLabel')">
|
||||
<b-form-select
|
||||
v-model="remindAt"
|
||||
data-test-id="select-remind-at"
|
||||
:options="remindAtPresets"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('reminder.edit.assigneeLabel')">
|
||||
<vue-select
|
||||
v-model="assignedTo"
|
||||
data-test-id="select-assignee"
|
||||
:options="assignees"
|
||||
option-value="userID"
|
||||
option-text="label"
|
||||
:placeholder="$t('field.kind.user.suggestionPlaceholder')"
|
||||
class="bg-white"
|
||||
@search="searchAssignees"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
v-if="reminder.payload.link"
|
||||
:label="$t('reminder.routesTo')"
|
||||
<div
|
||||
class="d-flex flex-column w-100 pb-2"
|
||||
>
|
||||
<b-form
|
||||
v-if="reminder"
|
||||
class="p-2 text-primary"
|
||||
@submit.prevent
|
||||
>
|
||||
<b-form-group v-if="reminder.reminderID !== '0'">
|
||||
<b-form-checkbox
|
||||
:checked="!!reminder.dismissedAt"
|
||||
switch
|
||||
class="mt-2"
|
||||
@change="$emit('dismiss', reminder, $event)"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="reminder.payload.link.label"
|
||||
data-test-id="input-link"
|
||||
/>
|
||||
{{ $t('reminder.dismissed') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button
|
||||
:disabled="!recordViewer"
|
||||
:to="recordViewer(reminder.payload.link.params)"
|
||||
:title="$t('label.recordPageLink')"
|
||||
variant="light"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'external-link-alt']" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</b-form>
|
||||
</b-list-group-item>
|
||||
<div class="position-sticky text-center bg-white py-1 fixed-bottom">
|
||||
<b-button
|
||||
data-test-id="button-save"
|
||||
variant="outline-primary"
|
||||
class="px-2"
|
||||
@click="save"
|
||||
<b-form-group :label="$t('reminder.edit.titleLabel')">
|
||||
<b-form-input
|
||||
v-model="title"
|
||||
data-test-id="input-title"
|
||||
required
|
||||
:placeholder="$t('reminder.edit.titlePlaceholder')"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('reminder.edit.notesLabel')">
|
||||
<b-form-textarea
|
||||
v-model="notes"
|
||||
data-test-id="textarea-notes"
|
||||
:placeholder="$t('reminder.edit.notesPlaceholder')"
|
||||
rows="6"
|
||||
max-rows="10"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('reminder.edit.remindAtLabel')">
|
||||
<c-input-date-time
|
||||
v-model="reminder.remindAt"
|
||||
data-test-id="select-remind-at"
|
||||
:labels="{
|
||||
clear: $t('label.clear'),
|
||||
none: $t('label.none'),
|
||||
now: $t('label.now'),
|
||||
today: $t('label.today'),
|
||||
}"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('reminder.edit.assigneeLabel')">
|
||||
<vue-select
|
||||
v-model="reminder.assignedTo"
|
||||
data-test-id="select-assignee"
|
||||
:options="assignees"
|
||||
:get-option-label="getUserLabel"
|
||||
:loading="processingUsers"
|
||||
:placeholder="$t('field.kind.user.suggestionPlaceholder')"
|
||||
:reduce="user => user.userID"
|
||||
option-value="userID"
|
||||
class="bg-white"
|
||||
@search="searchUsers"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
v-if="reminder.payload.link"
|
||||
:label="$t('reminder.routesTo')"
|
||||
>
|
||||
{{ $t('label.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="reminder.payload.link.label"
|
||||
data-test-id="input-link"
|
||||
/>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button
|
||||
:disabled="!recordViewer"
|
||||
:to="recordViewer"
|
||||
:title="$t('reminder.recordPageLink')"
|
||||
variant="light"
|
||||
class="d-flex align-items-center text-primary"
|
||||
>
|
||||
<font-awesome-icon :icon="['far', 'file-alt']" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
|
||||
<div class="text-center py-1">
|
||||
<b-button
|
||||
data-test-id="button-save"
|
||||
variant="outline-primary"
|
||||
@click="$emit('save', reminder)"
|
||||
>
|
||||
{{ $t('label.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-form>
|
||||
|
||||
<b-button
|
||||
variant="outline-light"
|
||||
class="text-primary mt-auto border-0"
|
||||
@click="$emit('back')"
|
||||
>
|
||||
{{ $t('label.back') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import { VueSelect } from 'vue-select'
|
||||
import { system } from '@cortezaproject/corteza-js'
|
||||
import { components } from '@cortezaproject/corteza-vue'
|
||||
const { CInputDateTime } = components
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
@@ -97,6 +124,7 @@ export default {
|
||||
|
||||
components: {
|
||||
VueSelect,
|
||||
CInputDateTime,
|
||||
},
|
||||
props: {
|
||||
edit: {
|
||||
@@ -119,97 +147,60 @@ export default {
|
||||
|
||||
data () {
|
||||
return {
|
||||
processingUsers: false,
|
||||
|
||||
// Do this, so we don't edit the original object
|
||||
reminder: {},
|
||||
assignees: [{ userID: this.myID, label: this.$t('reminder.edit.assigneePlaceholder') }],
|
||||
reminder: undefined,
|
||||
assignees: [{ userID: this.myID }],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
title: {
|
||||
get: function () {
|
||||
get () {
|
||||
return (this.reminder.payload || {}).title
|
||||
},
|
||||
set: function (v) {
|
||||
set (v) {
|
||||
this.updPayload('title', v)
|
||||
},
|
||||
},
|
||||
|
||||
notes: {
|
||||
get: function () {
|
||||
get () {
|
||||
return (this.reminder.payload || {}).notes
|
||||
},
|
||||
set: function (v) {
|
||||
set (v) {
|
||||
this.updPayload('notes', v)
|
||||
},
|
||||
},
|
||||
|
||||
remindAt: {
|
||||
get: function () {
|
||||
get () {
|
||||
return (this.reminder.payload || {}).remindAt || null
|
||||
},
|
||||
set: function (v) {
|
||||
set (v) {
|
||||
this.updPayload('remindAt', v)
|
||||
},
|
||||
},
|
||||
|
||||
assignedTo: {
|
||||
get: function () {
|
||||
return this.assignees.find(({ userID }) => userID === this.reminder.assignedTo)
|
||||
},
|
||||
set: function (user) {
|
||||
let userID
|
||||
if (user) {
|
||||
userID = user.userID
|
||||
}
|
||||
this.$set(this.reminder, 'assignedTo', userID)
|
||||
},
|
||||
},
|
||||
|
||||
remindAtPresets () {
|
||||
return [
|
||||
{ value: null, text: this.$t('reminder.edit.remindAtNone') },
|
||||
{ value: 1000 * 60 * 1, text: this.$t('label.timeMinute', { t: 1 }) },
|
||||
{ value: 1000 * 60 * 5, text: this.$t('label.timeMinute', { t: 5 }) },
|
||||
{ value: 1000 * 60 * 15, text: this.$t('label.timeMinute', { t: 15 }) },
|
||||
{ value: 1000 * 60 * 30, text: this.$t('label.timeMinute', { t: 30 }) },
|
||||
{ value: 1000 * 60 * 60 * 1, text: this.$t('label.timeHour', { t: 1 }) },
|
||||
{ value: 1000 * 60 * 60 * 2, text: this.$t('label.timeHour', { t: 2 }) },
|
||||
{ value: 1000 * 60 * 60 * 24, text: this.$t('label.timeHour', { t: 24 }) },
|
||||
]
|
||||
recordViewer () {
|
||||
const { params } = this.reminder.payload.link || {}
|
||||
return params ? { name: 'page.record', params } : undefined
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
edit: {
|
||||
handler: function () {
|
||||
this.reminder = new system.Reminder(this.edit)
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (edit) {
|
||||
this.reminder = new system.Reminder(edit)
|
||||
this.searchUsers()
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
recordViewer (params) {
|
||||
return params ? { name: 'page.record', params } : undefined
|
||||
},
|
||||
|
||||
save () {
|
||||
// @todo support for updating times
|
||||
let r = {}
|
||||
if (this.remindAt) {
|
||||
r.remindAt = moment().add(this.remindAt, 'ms').toISOString()
|
||||
}
|
||||
|
||||
r = {
|
||||
...this.reminder,
|
||||
...r,
|
||||
}
|
||||
|
||||
this.$emit('save', r)
|
||||
},
|
||||
|
||||
// Helper to handle undefined fields
|
||||
updPayload (f, v) {
|
||||
if (!this.reminder.payload) {
|
||||
@@ -218,22 +209,23 @@ export default {
|
||||
this.$set(this.reminder.payload, f, v)
|
||||
},
|
||||
|
||||
searchAssignees (query) {
|
||||
if (query) {
|
||||
this.debouncedSearch(this, query)
|
||||
}
|
||||
},
|
||||
searchUsers: _.debounce(function (query) {
|
||||
this.processingUsers = true
|
||||
|
||||
debouncedSearch: _.debounce((vm, query) => {
|
||||
vm.$SystemAPI.userList({ query }).then(({ set }) => {
|
||||
vm.assignees = set.map(({ userID, name: label }) => {
|
||||
if (userID === vm.myID) {
|
||||
return { userID, label: vm.$t('reminder.edit.assigneePlaceholder') }
|
||||
}
|
||||
return { userID, label }
|
||||
})
|
||||
this.$SystemAPI.userList({ query, limit: 15 }).then(({ set = [] }) => {
|
||||
this.assignees = set
|
||||
}).finally(() => {
|
||||
this.processingUsers = false
|
||||
})
|
||||
}, 300),
|
||||
|
||||
getUserLabel ({ userID, email, name, username }) {
|
||||
if (userID === this.myID) {
|
||||
return this.$t('reminder.edit.assigneePlaceholder')
|
||||
}
|
||||
|
||||
return name || username || email || `<@${userID}>`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="h-100 pl-1"
|
||||
class="h-100"
|
||||
>
|
||||
<div
|
||||
class="text-center bg-white py-2 sticky-top"
|
||||
@@ -16,76 +16,88 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="r in sortedReminders"
|
||||
v-for="(r, i) in sortedReminders"
|
||||
:key="r.reminderID"
|
||||
class="d-flex align-items-center mb-1 overflow-auto"
|
||||
>
|
||||
<b-form-checkbox
|
||||
data-test-id="checkbox-dismiss-reminder"
|
||||
:checked="!!r.dismissedAt"
|
||||
@change="$emit('dismiss', r, $event)"
|
||||
<hr v-if="r.dismissedAt && sortedReminders[i - 1] ? !sortedReminders[i - 1].dismissedAt : false ">
|
||||
|
||||
<div
|
||||
class="d-flex flex-row flex-nowrap align-items-center mb-2 overflow-auto border card"
|
||||
>
|
||||
<span
|
||||
<b-form-checkbox
|
||||
v-b-tooltip.hover.left.350
|
||||
data-test-id="checkbox-dismiss-reminder"
|
||||
:checked="!!r.dismissedAt"
|
||||
switch
|
||||
:title="$t(`reminder.${!!r.dismissedAt ? 'undismiss' : 'dismiss'}`)"
|
||||
class="my-2 ml-2"
|
||||
@change="$emit('dismiss', r, $event)"
|
||||
/>
|
||||
|
||||
<div
|
||||
data-test-id="span-reminder-title"
|
||||
class="text-break"
|
||||
:style="`${!!r.dismissedAt ? 'text-decoration: line-through;' : ''}`"
|
||||
>
|
||||
{{ r.payload.title || r.link || rlLabel(r) || r.linkLabel }}
|
||||
</span>
|
||||
</b-form-checkbox>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="ml-auto"
|
||||
>
|
||||
<font-awesome-icon
|
||||
v-if="r.snoozeCount"
|
||||
data-test-id="icon-snoozed-reminder"
|
||||
:icon="['far', 'clock']"
|
||||
class="ml-1"
|
||||
/>
|
||||
|
||||
<font-awesome-icon
|
||||
v-else-if="r.remindAt"
|
||||
v-b-tooltip.hover
|
||||
data-test-id="icon-remind-at"
|
||||
:title="makeTooltip(r)"
|
||||
:icon="['far', 'bell']"
|
||||
class="ml-1"
|
||||
/>
|
||||
|
||||
<b-button
|
||||
v-if="r.payload.link"
|
||||
:disabled="!recordViewer()"
|
||||
:to="recordViewer(r.payload.link.params)"
|
||||
:title="$t('label.recordPageLink')"
|
||||
variant="link"
|
||||
class="p-0 ml-2"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'external-link-alt']" />
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
data-test-id="button-edit-reminder"
|
||||
variant="link"
|
||||
class="p-1 ml-2"
|
||||
@click="$emit('edit', r)"
|
||||
<div
|
||||
class="d-flex align-items-center text-primary ml-auto px-2"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['far', 'edit']"
|
||||
class="text-primary"
|
||||
v-if="r.snoozeCount"
|
||||
data-test-id="icon-snoozed-reminder"
|
||||
:title="makeTooltip(r)"
|
||||
:icon="['far', 'clock']"
|
||||
class="m-2"
|
||||
/>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
data-test-id="button-delete-reminder"
|
||||
variant="link"
|
||||
class="text-dark p-1"
|
||||
@click.prevent="$emit('delete', r)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['far', 'trash-alt']"
|
||||
v-if="r.remindAt"
|
||||
data-test-id="icon-remind-at"
|
||||
:title="makeTooltip(r)"
|
||||
:icon="['far', 'bell']"
|
||||
class="m-2"
|
||||
/>
|
||||
</b-button>
|
||||
|
||||
<b-button-group
|
||||
size="sm"
|
||||
>
|
||||
<b-button
|
||||
v-if="r.payload.link"
|
||||
:disabled="!recordViewer()"
|
||||
:to="recordViewer(r.payload.link.params)"
|
||||
:title="$t('reminder.recordPageLink')"
|
||||
variant="outline-light"
|
||||
class="d-flex align-items-center py-2 text-primary border-0"
|
||||
>
|
||||
<font-awesome-icon :icon="['far', 'file-alt']" />
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
data-test-id="button-edit-reminder"
|
||||
variant="outline-light"
|
||||
:title="$t('reminder.edit.label')"
|
||||
class="d-flex align-items-center py-2 text-primary border-0"
|
||||
@click="$emit('edit', r)"
|
||||
>
|
||||
<font-awesome-icon :icon="['far', 'edit']" />
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
data-test-id="button-delete-reminder"
|
||||
variant="outline-light"
|
||||
:title="$t('reminder.delete')"
|
||||
class="d-flex align-items-center py-2 text-danger border-0"
|
||||
@click.prevent="$emit('delete', r)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['far', 'trash-alt']"
|
||||
/>
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,19 +136,20 @@ export default {
|
||||
},
|
||||
|
||||
stdSort (a, b) {
|
||||
if (!a.remindAt) {
|
||||
if (!a.dismissedAt) {
|
||||
return -1
|
||||
}
|
||||
if (!b.remindAt) {
|
||||
if (!b.dismissedAt) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return a.remindAt - b.remindAt
|
||||
return a.dismissedAt - b.dismissedAt
|
||||
},
|
||||
|
||||
makeTooltip ({ remindAt }) {
|
||||
return fmt.fullDateTime(remindAt)
|
||||
},
|
||||
|
||||
recordViewer (params) {
|
||||
return { name: 'page.record', params }
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<list
|
||||
v-if="!edit"
|
||||
:reminders="reminders"
|
||||
class="flex-fill"
|
||||
@edit="onEdit"
|
||||
@dismiss="onDismiss"
|
||||
@delete="onDelete"
|
||||
@@ -13,6 +14,9 @@
|
||||
:edit="edit"
|
||||
:my-i-d="$auth.user.userID"
|
||||
:users="users"
|
||||
class="flex-fill"
|
||||
@dismiss="onDismiss"
|
||||
@back="edit = undefined"
|
||||
@save="onSave"
|
||||
/>
|
||||
</div>
|
||||
@@ -73,11 +77,9 @@ export default {
|
||||
},
|
||||
|
||||
onSave (r) {
|
||||
let h = 'reminderCreate'
|
||||
if (r.reminderID && r.reminderID !== NoID) {
|
||||
h = 'reminderUpdate'
|
||||
}
|
||||
this.$SystemAPI[h](r).then(r => {
|
||||
const endpoint = r.reminderID && r.reminderID !== NoID ? 'reminderUpdate' : 'reminderCreate'
|
||||
this.$SystemAPI[endpoint](r).then(r => {
|
||||
this.edit = undefined
|
||||
this.fetchReminders()
|
||||
this.$Reminder.prefetch()
|
||||
})
|
||||
@@ -86,23 +88,18 @@ export default {
|
||||
},
|
||||
|
||||
onCancel () {
|
||||
this.edit = null
|
||||
this.edit = undefined
|
||||
},
|
||||
|
||||
onDismiss (r, value) {
|
||||
if (value) {
|
||||
this.$SystemAPI.reminderDismiss(r).then(() => {
|
||||
this.fetchReminders()
|
||||
})
|
||||
} else {
|
||||
this.$SystemAPI.reminderUndismiss(r).then(() => {
|
||||
this.fetchReminders()
|
||||
})
|
||||
}
|
||||
onDismiss ({ reminderID }, value) {
|
||||
const endpoint = value ? 'reminderDismiss' : 'reminderUndismiss'
|
||||
this.$SystemAPI[endpoint]({ reminderID }).then(() => {
|
||||
this.fetchReminders()
|
||||
})
|
||||
},
|
||||
|
||||
onDelete (r) {
|
||||
this.$SystemAPI.reminderDelete(r).then(() => {
|
||||
onDelete ({ reminderID }) {
|
||||
this.$SystemAPI.reminderDelete({ reminderID }).then(() => {
|
||||
this.fetchReminders()
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1164,8 +1164,8 @@ export default {
|
||||
createReminder (record) {
|
||||
// Determine initial reminder title
|
||||
const { recordID, values = {} } = record
|
||||
const tField = ((this.options.fields || []).find(({ name }) => !!values[name]) || {}).name
|
||||
const title = values[tField]
|
||||
const { name, isMulti } = (this.options.fields || []).find(({ name }) => !!values[name]) || {}
|
||||
const title = isMulti ? values[name].join(', ') : values[name]
|
||||
|
||||
const resource = `compose:record:${recordID}`
|
||||
const payload = {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<b-sidebar
|
||||
v-model="isVisible"
|
||||
header-class="d-flex align-items-center justify-content-between reminder-sidebar-header px-2 py-3 border-bottom"
|
||||
body-class="bg-white px-2"
|
||||
body-class="bg-white px-2 pb-2"
|
||||
:title="title"
|
||||
:backdrop="isMobile"
|
||||
no-footer
|
||||
|
||||
@@ -56,7 +56,6 @@ label:
|
||||
processing: Processing...
|
||||
record: Record
|
||||
recordPage: Record page
|
||||
recordPageLink: View record
|
||||
field: Field
|
||||
remove: Remove
|
||||
removeDefault: Remove default
|
||||
@@ -90,17 +89,22 @@ placeholder:
|
||||
reminder:
|
||||
add: Add a new reminder
|
||||
dismiss: Dismiss
|
||||
dismissed: Dismissed
|
||||
undismiss: Undismiss
|
||||
delete: Delete reminder
|
||||
recordPageLink: View record
|
||||
edit:
|
||||
label: Edit reminder
|
||||
assigneeLabel: Assignee
|
||||
assigneePlaceholder: Myself (default)
|
||||
notesLabel: Notes
|
||||
notesPlaceholder: Notes
|
||||
remindAtLabel: Notify me
|
||||
remindAtLabel: Remind me at
|
||||
remindAtNone: Never
|
||||
titleLabel: Title
|
||||
titlePlaceholder: Title
|
||||
listLabel: Reminders
|
||||
routesTo: 'Includes link to:'
|
||||
routesTo: Label for page link
|
||||
snooze: Snooze
|
||||
tooltip:
|
||||
dragAndDrop: Drag and drop to change order
|
||||
|
||||
Reference in New Issue
Block a user