Update reminder UI/UX

This commit is contained in:
Jože Fortun
2023-02-28 15:24:29 +01:00
parent dc4d146e3d
commit bded96c718
6 changed files with 238 additions and 232 deletions
@@ -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