feat: user events page view, fix #340 and #156

This commit is contained in:
lesion 2024-01-29 14:46:11 +01:00
parent 9be6fbc19c
commit 2492f6b545
No known key found for this signature in database
GPG key ID: 352918250B012177
7 changed files with 171 additions and 45 deletions

View file

@ -15,7 +15,7 @@ li {
}
.v-dialog .theme--dark.v-card {
background-color: #1e1e1e;
background-color: #333;
}
.v-application {

20
components/TBtn.vue Normal file
View file

@ -0,0 +1,20 @@
<template>
<v-tooltip top>
<template v-slot:activator="{ on, attrs}">
<v-btn v-bind="attrs" :to='to' icon v-on="on" @click='$emit("click")' :color='color'>
<slot />
</v-btn>
</template>
<span>{{ tooltip }}</span>
</v-tooltip>
</template>
<script>
export default {
name: 'TBtn',
props: {
tooltip: String,
color: String,
to: String
}
}
</script>

View file

@ -109,7 +109,8 @@
"pin": "Pin",
"trusted_instances": "Trusted instances",
"actors": "Node",
"collection_in_home": "Show a collection in home"
"collection_in_home": "Show a collection in home",
"my_events": "My Events"
},
"login": {
"description": "By logging in you can publish new events.",

View file

@ -1,54 +1,119 @@
<template lang="pug">
v-container
v-card
v-card-title.text-h5 {{$auth.user.email}}
v-card-text
p {{$t('settings.remove_account')}}
v-btn.black--text(color='warning' @click='remove_account') {{$t('common.remove')}}
<template>
<v-container class="pa-0 pa-md-3">
<v-card>
<v-card-title>{{$auth.user.email}}</v-card-title>
<v-card-text>
<v-tabs v-model='selectedTab'>
<v-tab href="#mine">{{$t('common.my_events')}}</v-tab>
<v-tab href='#settings'>{{$t('common.settings')}}</v-tab>
<v-tab-item value="mine">
<v-container>
<v-card-text class="pa-0 pa-md-3">
<v-text-field v-model="search" :label="$t('common.search')"/>
<v-data-table :items="events" :hide-default-footer='events.length<10' dense :search="search"
:header-props='{ sortIcon: mdiChevronDown }'
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
:headers='headers'>
<template v-slot:item.data-table-select="{ isSelected, select }">
<v-simple-checkbox size='small' small dense :on-icon="mdiCheckboxOutline" :off-icon="mdiCheckboxBlankOutline" :value="isSelected" @input="select($event)" />
</template>
<template v-slot:item.when='{ item }'>
<span v-if='!item.recurrent'>{{$time.when(item)}}</span>
<span v-else><v-icon color='success' v-text='mdiRepeat' /> {{$time.recurrentDetail({ parent: item }, 'EEEE, HH:mm')}}</span>
</template>
<template v-slot:item.actions='{ item }'>
<template v-if="!item.recurrent">
<t-btn @click='toggle(item)' v-if='!item.is_visible' color='success' :tooltip="$t('common.confirm')"><v-icon v-text='mdiEye' /></t-btn>
<t-btn @click='toggle(item)' v-else-if="!item.parentId" color='info' :tooltip="$t('common.hide')"><v-icon v-text='mdiEyeOff' /></t-btn>
<t-btn @click='toggle(item)' v-else color='info' :tooltip="$t('common.skip')"><v-icon v-text='mdiDebugStepOver' /></t-btn>
<t-btn :to='`/event/${item.slug || item.id}`' :tooltip="$t('common.preview')"><v-icon v-text='mdiArrowRight' /></t-btn>
</template>
<template v-else>
<t-btn @click='toggle(item)' v-if='!item.is_visible' color='success' :tooltip="$t('common.start')"><v-icon v-text='mdiPlay' /></t-btn>
<t-btn @click='toggle(item)' v-else color='info' :tooltip="$t('common.pause')"><v-icon v-text='mdiPause' /></t-btn>
</template>
<t-btn :to='`/add/${item.id}`' color='warning' :tooltip="$t('common.edit')"><v-icon v-text='mdiPencil' /></t-btn>
<t-btn @click='remove(item, item.recurrent)' color='error' :tooltip="$t('common.delete')"> <v-icon v-text='item.recurrent ? mdiDeleteForever : mdiDelete' /></t-btn>
</template>
</v-data-table>
</v-card-text>
</v-container>
</v-tab-item>
<v-tab-item value='settings'>
<v-container>
<v-btn @click='forgot'>{{$t('login.forgot_password')}}</v-btn><br/><br/>
<v-divider />
<p>{{$t('settings.remove_account')}}</p>
<v-btn color='warning' @click='remove_account'>{{$t('common.remove')}}</v-btn>
</v-container>
</v-tab-item>
</v-tabs>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
import { mdiChevronLeft, mdiChevronRight, mdiChevronDown, mdiCheckboxOutline, mdiCheckboxBlankOutline,
mdiPencil, mdiDelete, mdiArrowRight, mdiEye, mdiEyeOff, mdiRepeat, mdiPause, mdiPlay, mdiDebugStepOver, mdiDeleteForever } from '@mdi/js'
import { mapState } from 'vuex'
import TBtn from '../components/TBtn.vue'
export default {
name: 'Settings',
middleware: ['auth'],
components: { TBtn },
data () {
return {
password: '',
user: { }
mdiChevronLeft, mdiChevronRight, mdiChevronDown, mdiCheckboxOutline, mdiCheckboxBlankOutline,
mdiPencil, mdiDelete, mdiArrowRight, mdiEye, mdiEyeOff, mdiRepeat, mdiPause, mdiPlay, mdiDebugStepOver, mdiDeleteForever,
selectedTab: 'mine',
events: [],
selectedEvents: [],
search: '',
headers: [
{ value: 'title', text: this.$t('common.title') },
{ value: 'place.name', text: this.$t('common.place') },
{ value: 'when', text: this.$t('common.when') },
{ value: 'actions', text: this.$t('common.actions'), align: 'right' }
]
}
},
computed: mapState(['settings']),
async fetch () {
this.events = await this.$axios.$get('/events/mine')
},
methods: {
// async change_password () {
// if (!this.password) { return }
// const user_data = { id: this.$auth.user.id, password: this.password }
// try {
// await this.$axios.$put('/user', user_data)
// Message({ message: this.$t('settings.password_updated'), showClose: true, type: 'success' })
// this.$router.replace('/')
// } catch (e) {
// console.log(e)
// }
// },
// update_settings () {
// MessageBox.confirm(this.$t('settings.update_confirm'),
// this.$t('common.confirm'), {
// confirmButtonText: this.$t('common.ok'),
// cancelButtonText: this.$t('common.cancel'),
// type: 'error'
// }).then(async () => {
// this.user = await this.$axios.$put('/user', { ...this.user, password: this.password })
// }).catch(e => {
// Message({ message: e, showClose: true, type: 'warning' })
// })
// },
async forgot () {
this.loading = true
await this.$axios.$post('/user/recover', { email: this.$auth.user.email })
this.loading = false
this.$root.$message('login.check_email', { color: 'success' })
},
async remove_account () {
const ret = await this.$root.$confirm('settings.remove_account_confirm', { color: 'error' })
if (!ret) return
this.$axios.$delete('/user')
this.$auth.logout()
this.$router.replace('/')
},
async toggle (event) {
const id = event.id
const is_visible = event.is_visible
const method = is_visible ? 'unconfirm' : 'confirm'
try {
await this.$axios.$put(`/event/${method}/${id}`)
event.is_visible = !is_visible
} catch (e) {
console.error(e)
}
},
async remove (event, parent) {
const ret = await this.$root.$confirm(`event.remove_${parent ? 'recurrent_' : ''}confirmation`)
if (!ret) { return }
await this.$axios.delete(`/event/${event.id}`)
this.$fetch()
this.$root.$message('admin.event_remove_ok')
}
},
head () {

View file

@ -88,7 +88,7 @@ export default ({ app, store }, inject) => {
endOfDay (date) { return DateTime.fromJSDate(date, { zone }).endOf('day').toUnixInteger()},
recurrentDetail (event) {
recurrentDetail (event, format='EEEE') {
const opt = {
zone,
locale: app.i18n.locale || store.state.settings.instance_locale
@ -98,9 +98,9 @@ export default ({ app, store }, inject) => {
const { frequency, type } = parent.recurrent
let recurrent
if (frequency === '1w' || frequency === '2w') {
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: DateTime.fromSeconds(parent.start_datetime, opt).toFormat('EEEE')})
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: DateTime.fromSeconds(parent.start_datetime, opt).toFormat(format)})
} else if (frequency === '1m' || frequency === '2m') {
const d = type === 'ordinal' ? DateTime.fromSeconds(parent.start_datetime, opt).day : DateTime.fromSeconds(parent.start_datetime, opt).toFormat('EEEE')
const d = type === 'ordinal' ? DateTime.fromSeconds(parent.start_datetime, opt).day : DateTime.fromSeconds(parent.start_datetime, opt).toFormat(format)
if (type === 'ordinal') {
recurrent = app.i18n.t(`event.recurrent_${frequency}_days`, { days: d })
} else {

View file

@ -437,8 +437,13 @@ const eventController = {
try {
const body = req.body
const event = await Event.findByPk(body.id)
if (!event) { return res.sendStatus(404) }
if (!event) {
log.debug('[UPDATE] Event not found: %s', body?.id)
return res.sendStatus(404)
}
if (!req.user.is_admin && event.userId !== req.user.id) {
log.debug('[UPDATE] the user is neither an admin nor the owner of the event')
return res.sendStatus(403)
}
@ -448,19 +453,23 @@ const eventController = {
// // validate start_datetime and end_datetime
if (end_datetime) {
if (start_datetime > end_datetime) {
log.debug('[UPDATE] start_datetime is greated than end_datetime')
return res.status(400).send(`start datetime is greater than end datetime`)
}
if (Number(end_datetime) > 1000*24*60*60*365) {
log.debug('[UPDATE] end_datetime is too much in the future')
return res.status(400).send('are you sure?')
}
}
if (!Number(start_datetime)) {
log.debug('[UPDATE] start_datetime has to be a number')
return res.status(400).send(`Wrong format for start datetime`)
}
if (Number(start_datetime) > 1000*24*60*60*365) {
log.debug('[UPDATE] start_datetime is too much in the future')
return res.status(400).send('are you sure?')
}
@ -523,9 +532,11 @@ const eventController = {
try {
place = await eventController._findOrCreatePlace(body)
if (!place) {
log.info('[UPDATE] Place not found')
return res.status(400).send(`Place not found`)
}
} catch (e) {
log.info('[UPDATE] %s', e?.message ?? String(e))
return res.status(400).send(e.message)
}
await event.setPlace(place)
@ -618,14 +629,12 @@ const eventController = {
page,
older,
reverse,
user_id,
include_unconfirmed = false,
include_parent = false,
include_description=false }) {
const where = {
// do not include _parent_ recurrent event
recurrent: null,
// confirmed event only
is_visible: true,
apUserApId: null,
@ -635,6 +644,20 @@ const eventController = {
}
}
if (user_id) {
where.userId = user_id
}
if (include_parent !== true) {
// do not include _parent_ recurrent event
where.recurrent = null
}
if (include_unconfirmed !== true) {
// confirmed event only
where.is_visible = true
}
// include recurrent events?
if (!show_recurrent) {
where.parentId = null
@ -689,7 +712,12 @@ const eventController = {
const events = await Event.findAll({
where,
attributes: {
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'resources', 'recurrent', 'placeId', 'image_path', ...(!include_description ? ['description']: [])]
exclude: [
'likes', 'boost', 'userId', 'createdAt', 'resources', 'placeId', 'image_path',
...(!include_parent ? ['recurrent']: []),
...(!include_unconfirmed ? ['is_visible']: []),
...(!include_description ? ['description']: [])
]
},
order: [['start_datetime', reverse ? 'DESC' : 'ASC']],
include: [
@ -715,6 +743,17 @@ const eventController = {
})
},
async mine (req, res) {
const events = await eventController._select({
user_id: req.user.id,
include_parent: true,
include_unconfirmed: true,
show_recurrent: true,
show_multidate: true
})
return res.json(events)
},
/**
* Select events based on params
*/

View file

@ -117,6 +117,7 @@ module.exports = () => {
* [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
*/
api.get('/events/mine', isAuth, eventController.mine)
api.get('/events', cors, eventController.select)
/**