Add an initial Events Timeline for Stories
This doesn't cover all the possible events yet, but is a sufficient starting point.
This commit is contained in:
parent
2eb447ccb1
commit
fa0bee29d1
45
src/components/EventComment.vue
Normal file
45
src/components/EventComment.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="event-details">
|
||||
<p class="event-title">
|
||||
<a href="">{{ author.full_name }}</a>
|
||||
commented on {{ createdDate.toDateString() }}
|
||||
at {{ createdDate.toLocaleTimeString() }}
|
||||
</p>
|
||||
<div class="event-body">
|
||||
{{ event.comment.content }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'EventComment',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
42
src/components/EventStoryCreated.vue
Normal file
42
src/components/EventStoryCreated.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="event-details">
|
||||
<p class="event-title">
|
||||
<a href="">{{ author.full_name }}</a>
|
||||
created this story on {{ createdDate.toDateString() }}
|
||||
at {{ createdDate.toLocaleTimeString() }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'EventStoryCreated',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
42
src/components/EventStoryUpdated.vue
Normal file
42
src/components/EventStoryUpdated.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="event-details">
|
||||
<p class="event-title">
|
||||
<a href="">{{ author.full_name }}</a>
|
||||
updated this story on {{ createdDate.toDateString() }}
|
||||
at {{ createdDate.toLocaleTimeString() }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'EventStoryUpdated',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
53
src/components/EventTagsAdded.vue
Normal file
53
src/components/EventTagsAdded.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="event-details">
|
||||
<p class="event-title">
|
||||
<a href="">{{ author.full_name }}</a>
|
||||
tagged this story as
|
||||
<span class="tag" v-for="tag in info.tags" :key="tag">{{ tag }}</span>
|
||||
on {{ createdDate.toDateString() }}
|
||||
at {{ createdDate.toLocaleTimeString() }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'EventTagsAdded',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
},
|
||||
info () {
|
||||
return JSON.parse(this.event.event_info)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag {
|
||||
padding: 6px 12px;
|
||||
margin: 0 5px;
|
||||
background-color: #f0ad4e;
|
||||
color: #f7f6f4;
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
48
src/components/EventTaskAssigneeUpdated.vue
Normal file
48
src/components/EventTaskAssigneeUpdated.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="event-details">
|
||||
<p class="event-title">
|
||||
<a href="">{{ author.full_name }}</a>
|
||||
updated the asssignee of the task "{{ info.task_title }}"
|
||||
from <a href="" v-if="info.old_assignee_id">{{ info.old_assignee_fullname }}</a> <span v-if="!info.old_assignee_id">{{ info.old_assignee_fullname }}</span>
|
||||
to <a href="">{{ info.new_assignee_fullname }}</a>
|
||||
on {{ createdDate.toDateString() }}
|
||||
at {{ createdDate.toLocaleTimeString() }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'EventTaskAssigneeUpdated',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
},
|
||||
info () {
|
||||
return JSON.parse(this.event.event_info)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
46
src/components/EventTaskCreated.vue
Normal file
46
src/components/EventTaskCreated.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="event-details">
|
||||
<p class="event-title">
|
||||
<a href="">{{ author.full_name }}</a>
|
||||
created the task "{{ info.task_title }}"
|
||||
on {{ createdDate.toDateString() }}
|
||||
at {{ createdDate.toLocaleTimeString() }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'EventTaskCreated',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
},
|
||||
info () {
|
||||
return JSON.parse(this.event.event_info)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
46
src/components/EventTaskUpdated.vue
Normal file
46
src/components/EventTaskUpdated.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="event-details">
|
||||
<p class="event-title">
|
||||
<a href="">{{ author.full_name }}</a>
|
||||
updated the task "{{ info.task_title }}"
|
||||
on {{ createdDate.toDateString() }}
|
||||
at {{ createdDate.toLocaleTimeString() }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'EventTaskUpdated',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
},
|
||||
info () {
|
||||
return JSON.parse(this.event.event_info)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
129
src/components/TimelineEvent.vue
Normal file
129
src/components/TimelineEvent.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="timeline-event">
|
||||
<p class="timeline-event-icon" :class="iconClass">
|
||||
x
|
||||
</p>
|
||||
<EventStoryCreated :event="event" v-if="event.event_type === 'story_created'" />
|
||||
<EventStoryUpdated :event="event" v-if="event.event_type === 'story_details_changed'" />
|
||||
<EventTagsAdded :event="event" v-if="event.event_type === 'tags_added'" />
|
||||
<EventTaskCreated :event="event" v-if="event.event_type === 'task_created'" />
|
||||
<EventTaskUpdated :event="event" v-if="event.event_type === 'task_details_changed'" />
|
||||
<EventTaskAssigneeUpdated :event="event" v-if="event.event_type === 'task_assignee_changed'" />
|
||||
<EventComment :event="event" v-if="isComment" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import EventStoryCreated from '@/components/EventStoryCreated'
|
||||
import EventStoryUpdated from '@/components/EventStoryUpdated'
|
||||
import EventTagsAdded from '@/components/EventTagsAdded'
|
||||
import EventTaskCreated from '@/components/EventTaskCreated'
|
||||
import EventTaskUpdated from '@/components/EventTaskUpdated'
|
||||
import EventTaskAssigneeUpdated from '@/components/EventTaskAssigneeUpdated'
|
||||
import EventComment from '@/components/EventComment'
|
||||
|
||||
const PRIMARY_EVENTS = [
|
||||
'story_created',
|
||||
'user_comment'
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'TimelineEvent',
|
||||
data () {
|
||||
return {
|
||||
author: {}
|
||||
}
|
||||
},
|
||||
props: ['event'],
|
||||
components: {
|
||||
EventComment,
|
||||
EventStoryCreated,
|
||||
EventStoryUpdated,
|
||||
EventTagsAdded,
|
||||
EventTaskCreated,
|
||||
EventTaskUpdated,
|
||||
EventTaskAssigneeUpdated
|
||||
},
|
||||
computed: {
|
||||
createdDate () {
|
||||
return new Date(this.event.created_at)
|
||||
},
|
||||
isComment () {
|
||||
return this.event.event_type === 'user_comment'
|
||||
},
|
||||
iconClass () {
|
||||
return {
|
||||
primary: PRIMARY_EVENTS.includes(this.event.event_type)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getAuthor(this.event.author_id)
|
||||
},
|
||||
methods: {
|
||||
async getAuthor (authorId) {
|
||||
const baseUrl = 'http://localhost:8080/v1'
|
||||
const { data: author } = await axios.get(`${baseUrl}/users/${authorId}`)
|
||||
this.author = author
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.timeline-event {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 20px;
|
||||
|
||||
.timeline-event-icon {
|
||||
flex: 0 0 30px;
|
||||
margin: 3px 5px;
|
||||
background-color: #f7f6f4;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
border-radius: 50px;
|
||||
border: 3px solid #ddd;
|
||||
|
||||
&.primary {
|
||||
flex-basis: 40px;
|
||||
margin: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background-color: #c43422;
|
||||
color: #f7f6f4;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
.event-details {
|
||||
margin-left: 50px;
|
||||
flex: 1 0 0;
|
||||
|
||||
::v-deep p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
::v-deep .event-title {
|
||||
padding: 5px 10px;
|
||||
line-height: 30px;
|
||||
background-color: #eee;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.12);
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
::v-deep .event-body {
|
||||
background-color: #fff;
|
||||
margin: 0 2px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -43,21 +43,21 @@
|
||||
</div>
|
||||
<h2>Events Timeline and Comments</h2>
|
||||
<div class="events">
|
||||
<div class="timeline-event" v-for="event in events" :key="event.id">
|
||||
{{ event.event_type }}
|
||||
</div>
|
||||
<TimelineEvent v-for="event in events" :key="event.id" :event="event"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TaskListEntry from '@/components/TaskListEntry.vue'
|
||||
import TimelineEvent from '@/components/TimelineEvent.vue'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'StoryDetailView',
|
||||
components: {
|
||||
TaskListEntry
|
||||
TaskListEntry,
|
||||
TimelineEvent
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@ -183,4 +183,20 @@ h2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.events {
|
||||
position: relative;
|
||||
margin-bottom: 100px;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: " ";
|
||||
width: 4px;
|
||||
height: calc(100% - 20px);
|
||||
left: 21px;
|
||||
top: 20px;
|
||||
background-color: #ddd;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user