This commit is contained in:
j3d1 2023-12-06 06:47:30 +01:00
parent 470541d239
commit 8fb058ee50
7 changed files with 396 additions and 201 deletions

View file

@ -125,6 +125,8 @@ class LMTPHandler:
log.info(f"Created email {email.id}") log.info(f"Created email {email.id}")
systemevent = await sync_to_async(SystemEvent.objects.create)(type='email received', reference=email.id) systemevent = await sync_to_async(SystemEvent.objects.create)(type='email received', reference=email.id)
log.info(f"Created system event {systemevent.id}") log.info(f"Created system event {systemevent.id}")
state_change = await sync_to_async(
active_issue_thread.state_changes.create)(state='new', issue_thread=active_issue_thread)
channel_layer = get_channel_layer() channel_layer = get_channel_layer()
await channel_layer.group_send( await channel_layer.group_send(
'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id, 'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id,

View file

@ -13,13 +13,13 @@
<ul class="nav nav-tabs flex-nowrap"> <ul class="nav nav-tabs flex-nowrap">
<li class="nav-item"> <li class="nav-item">
<router-link :to="{name: 'items', params: {event: getEventSlug}}" <router-link :to="{name: 'items', params: {event: getEventSlug}}"
:class="['nav-link', { active: getActiveView === 'items' }]"> :class="['nav-link', { active: getActiveView === 'items' || getActiveView === 'item' }]">
Items Items
</router-link> </router-link>
</li> </li>
<li class="nav-item" v-if="checkRole('postevent')"> <li class="nav-item" v-if="checkRole('postevent')">
<router-link :to="{name: 'tickets', params: {event: getEventSlug}}" <router-link :to="{name: 'tickets', params: {event: getEventSlug}}"
:class="['nav-link', { active: getActiveView === 'tickets' }]"> :class="['nav-link', { active: getActiveView === 'tickets' || getActiveView === 'ticket' }]">
Tickets Tickets
</router-link> </router-link>
</li> </li>
@ -131,7 +131,6 @@ export default {
padding-bottom: 1rem !important; padding-bottom: 1rem !important;
border: var(--gray) solid 1px !important; border: var(--gray) solid 1px !important;
border-bottom: none !important; border-bottom: none !important;
color: var(--blue) !important;
&.active { &.active {
background: black !important; background: black !important;

View file

@ -1,76 +1,23 @@
<template> <template>
<ol class="timeline"> <ol class="timeline">
<li class="timeline-item"> <li v-for="(item, index) in timeline" :key="index"
<span class="timeline-item-icon | faded-icon"> :class="{'timeline-item':true, 'extra-space': item.type === 'mail'}">
<font-awesome-icon icon="pen"/> <span class="timeline-item-icon filled-icon" v-if="item.type === 'mail'">
</span>
<div class="timeline-item-description">
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<span><a href="#">Luna Bonifacio</a> has changed <a href="#">2 attributes</a> on <time
datetime="21-01-2021">Jan 21, 2021</time></span>
</div>
</li>
<li class="timeline-item">
<span class="timeline-item-icon | faded-icon">
<font-awesome-icon icon="check"/>
</span>
<div class="timeline-item-description">
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<span><a href="#">Yoan Almedia</a> moved <a href="#">Eric Lubin</a> to <a href="#">📚 Technical Test</a> on <time
datetime="20-01-2021">Jan 20, 2021</time></span>
</div>
</li>
<li class="timeline-item | extra-space">
<span class="timeline-item-icon | filled-icon">
<font-awesome-icon icon="envelope"/> <font-awesome-icon icon="envelope"/>
</span> </span>
<div class="timeline-item-wrapper"> <span class="timeline-item-icon faded-icon" v-else-if="item.type === 'comment'">
<div class="timeline-item-description"> <font-awesome-icon icon="comment"/>
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<span><a href="#">Yoan Almedia</a> commented on <time
datetime="20-01-2021">Jan 20, 2021</time></span>
</div>
<div class="comment">
<p>I've sent him the assignment we discussed recently, he is coming back to us this week. Regarding
to our last call, I really enjoyed talking to him and so far he has the profile we are looking
for. Can't wait to see his technical test, I'll keep you posted and we'll debrief it all
together!😊</p>
<button class="button">👏 2</button>
<button class="button | square">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="none" d="M0 0h24v24H0z"/>
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zM7 12a5 5 0 0 0 10 0h-2a3 3 0 0 1-6 0H7z"/>
</svg>
</button>
</div>
<button class="show-replies">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-forward"
width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M15 11l4 4l-4 4m4 -4h-11a4 4 0 0 1 0 -8h1"/>
</svg>
Show 3 replies
<span class="avatar-list">
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
</span> </span>
</button> <span class="timeline-item-icon faded-icon" v-else-if="item.type === 'state'">
</div> <font-awesome-icon icon="check"/>
</span>
<span class="timeline-item-icon faded-icon" v-else>
<font-awesome-icon icon="pen"/>
</span>
<TimelineMail v-if="item.type === 'mail'" :item="item"/>
<TimelineComment v-else-if="item.type === 'comment'" :item="item"/>
<TimelineStateChange v-else-if="item.type === 'state'" :item="item"/>
<p v-else>{{ item }}</p>
</li> </li>
<li class="timeline-item"> <li class="timeline-item">
<span class="timeline-item-icon | faded-icon"> <span class="timeline-item-icon | faded-icon">
@ -85,8 +32,13 @@
<script> <script>
import TimelineMail from "@/components/TimelineMail.vue";
import TimelineComment from "@/components/TimelineComment.vue";
import TimelineStateChange from "@/components/TimelineStateChange.vue";
export default { export default {
name: 'Timeline', name: 'Timeline',
components: {TimelineStateChange, TimelineComment, TimelineMail},
props: { props: {
timeline: { timeline: {
type: Array, type: Array,
@ -96,7 +48,7 @@ export default {
}; };
</script> </script>
<style> <style lang="scss" scoped>
*, *,
*:before, *:before,
@ -104,16 +56,6 @@ export default {
box-sizing: border-box; box-sizing: border-box;
} }
:root {
--c-grey-100: #f4f6f8;
--c-grey-200: #e3e3e3;
--c-grey-300: #b2b2b2;
--c-grey-400: #7b7b7b;
--c-grey-500: #3d3d3d;
--c-blue-500: #688afd;
}
button, button,
input, input,
select, select,
@ -137,7 +79,7 @@ img {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 32px 0 32px 32px; padding: 32px 0 32px 32px;
border-left: 2px solid var(--c-grey-200); border-left: 2px solid var(--gray);
font-size: 1.125rem; font-size: 1.125rem;
margin: 0 auto; margin: 0 auto;
} }
@ -159,20 +101,20 @@ img {
width: 100%; width: 100%;
input { input {
border: 1px solid var(--c-grey-200); border: 1px solid var(--gray);
border-radius: 6px; border-radius: 6px;
height: 48px; height: 48px;
padding: 0 16px; padding: 0 16px;
width: 100%; width: 100%;
&::placeholder { &::placeholder {
color: var(--c-grey-300); color: var(--gray-dark);
} }
&:focus { &:focus {
border-color: var(--c-grey-300); border-color: var(--gray-dark);
outline: 0; /* Don't actually do this */ outline: 0; /* Don't actually do this */
box-shadow: 0 0 0 4px var(--c-grey-100); box-shadow: 0 0 0 4px var(--dark);
} }
} }
} }
@ -194,69 +136,16 @@ img {
} }
&.faded-icon { &.faded-icon {
background-color: var(--c-grey-100); background-color: var(--secondary);
color: var(--c-grey-400); color: var(--light);
} }
&.filled-icon { &.filled-icon {
background-color: var(--c-blue-500); background-color: var(--primary);
color: #fff; color: var(--light);
} }
} }
.timeline-item-description {
display: flex;
padding-top: 6px;
gap: 8px;
color: var(--c-grey-400);
img {
flex-shrink: 0;
}
a {
/*color: var(--c-grey-500);*/
font-weight: 500;
text-decoration: none;
&:hover,
&:focus {
outline: 0; /* Don't actually do this */
color: var(--c-blue-500);
}
}
}
.avatar {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
overflow: hidden;
aspect-ratio: 1 / 1;
flex-shrink: 0;
width: 40px;
height: 40px;
&.small {
width: 28px;
height: 28px;
}
img {
object-fit: cover;
}
}
.comment {
margin-top: 12px;
/*color: var(--c-grey-500);*/
border: 1px solid var(--c-grey-200);
background: var(--dark);
border-radius: 6px;
padding: 16px;
font-size: 1rem;
}
.button { .button {
border: 0; border: 0;
@ -269,68 +158,15 @@ img {
font-size: 1rem; font-size: 1rem;
height: 32px; height: 32px;
padding: 0 8px; padding: 0 8px;
background-color: var(--c-grey-100); background-color: var(--dark);
flex-shrink: 0; flex-shrink: 0;
cursor: pointer; cursor: pointer;
border-radius: 99em; border-radius: 99em;
&:hover { &:hover {
background-color: var(--c-grey-200); background-color: var(--gray);
}
&.square {
border-radius: 50%;
color: var(--c-grey-400);
width: 32px;
height: 32px;
padding: 0;
svg {
width: 24px;
height: 24px;
}
&:hover {
background-color: var(--c-grey-200);
color: var(--c-grey-500);
}
}
}
.show-replies {
color: var(--c-grey-300);
background-color: transparent;
border: 0;
padding: 0;
margin-top: 16px;
display: flex;
align-items: center;
gap: 6px;
font-size: 1rem;
cursor: pointer;
svg {
flex-shrink: 0;
width: 24px;
height: 24px;
}
&:hover,
&:focus {
color: var(--c-grey-200);
} }
} }
.avatar-list {
display: flex;
align-items: center;
& > * {
position: relative;
box-shadow: 0 0 0 2px #fff;
background: var(--dark);
margin-right: -8px;
}
}
</style> </style>

View file

@ -0,0 +1,79 @@
<template>
<div class="timeline-item-description">
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<span><a href="#">$USER</a> commented <b>{{ item.comment }}</b> on <time
:datetime="item.timestamp">{{ item.timestamp }}</time></span>
</div>
</template>
<script>
export default {
name: 'TimelineComment',
props: {
'item': {
type: Object,
required: true
}
},
};
</script>
<style scoped>
a {
color: inherit;
}
/* End basic CSS override */
.timeline-item-description {
display: flex;
padding-top: 6px;
gap: 8px;
color: var(--c-grey-400);
img {
flex-shrink: 0;
}
a {
/*color: var(--c-grey-500);*/
font-weight: 500;
text-decoration: none;
&:hover,
&:focus {
outline: 0; /* Don't actually do this */
color: var(--c-blue-500);
}
}
}
.avatar {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
overflow: hidden;
aspect-ratio: 1 / 1;
flex-shrink: 0;
width: 40px;
height: 40px;
&.small {
width: 28px;
height: 28px;
}
img {
object-fit: cover;
}
}
</style>

View file

@ -0,0 +1,199 @@
<template>
<div class="timeline-item-wrapper">
<div class="timeline-item-description">
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<span><a href="#">$USER</a> commented on <time
:datetime="item.timestamp">{{ item.timestamp }}</time></span>
</div>
<div class="comment">
<p>{{ item.body }}</p>
<button class="button">👏 2</button>
<button class="button | square">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="none" d="M0 0h24v24H0z"/>
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zM7 12a5 5 0 0 0 10 0h-2a3 3 0 0 1-6 0H7z"/>
</svg>
</button>
</div>
<button class="show-replies">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-forward"
width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M15 11l4 4l-4 4m4 -4h-11a4 4 0 0 1 0 -8h1"/>
</svg>
Show 3 replies
<span class="avatar-list">
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
</span>
</button>
</div>
</template>
<script>
export default {
name: 'TimelineMail',
props: {
'item': {
type: Object,
required: true
}
},
};
</script>
<style scoped>
a {
color: inherit;
}
img {
display: block;
max-width: 100%;
}
.timeline-item-description {
display: flex;
padding-top: 6px;
gap: 8px;
color: var(--gray);
img {
flex-shrink: 0;
}
a {
/*color: var(--c-grey-500);*/
font-weight: 500;
text-decoration: none;
&:hover,
&:focus {
outline: 0; /* Don't actually do this */
color: var(--gray);
}
}
}
.comment {
margin-top: 12px;
color: var(--light);
border: 1px solid var(--gray);
background: var(--dark);
border-radius: 6px;
padding: 16px;
font-size: 1rem;
}
.avatar {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
overflow: hidden;
aspect-ratio: 1 / 1;
flex-shrink: 0;
width: 40px;
height: 40px;
&.small {
width: 28px;
height: 28px;
}
img {
object-fit: cover;
}
}
.button {
border: 0;
display: inline-flex;
vertical-align: middle;
margin-right: 4px;
margin-top: 12px;
align-items: center;
justify-content: center;
font-size: 1rem;
height: 32px;
padding: 0 8px;
background-color: var(--dark);
flex-shrink: 0;
cursor: pointer;
border-radius: 99em;
&:hover {
background-color: var(--gray);
}
&.square {
border-radius: 50%;
color: var(--gray);
width: 32px;
height: 32px;
padding: 0;
svg {
width: 24px;
height: 24px;
}
&:hover {
background-color: var(--dark);
color: var(--info);
}
}
}
.show-replies {
color: var(--gray);
background-color: transparent;
border: 0;
padding: 0;
margin-top: 16px;
display: flex;
align-items: center;
gap: 6px;
font-size: 1rem;
cursor: pointer;
svg {
flex-shrink: 0;
width: 24px;
height: 24px;
}
&:hover,
&:focus {
color: var(--info);
}
}
.avatar-list {
display: flex;
align-items: center;
& > * {
position: relative;
box-shadow: 0 0 0 2px #fff;
background: var(--dark);
margin-right: -8px;
}
}
</style>

View file

@ -0,0 +1,77 @@
<template>
<div class="timeline-item-description">
<i class="avatar | small">
<font-awesome-icon icon="user"/>
</i>
<span><a href="#">$USER</a> has changed state to <span
class="badge badge-pill badge-primary">{{ item.state }}</span> on <time
:datetime="item.timestamp">{{ item.timestamp }}</time></span>
</div>
</template>
<script>
export default {
name: 'TimelineStateChange',
props: {
'item': {
type: Object,
required: true
}
},
};
</script>
<style scoped>
a {
color: inherit;
}
.timeline-item-description {
display: flex;
padding-top: 6px;
gap: 8px;
color: var(--c-grey-400);
img {
flex-shrink: 0;
}
a {
/*color: var(--c-grey-500);*/
font-weight: 500;
text-decoration: none;
&:hover,
&:focus {
outline: 0; /* Don't actually do this */
color: var(--c-blue-500);
}
}
}
.avatar {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
overflow: hidden;
aspect-ratio: 1 / 1;
flex-shrink: 0;
width: 40px;
height: 40px;
&.small {
width: 28px;
height: 28px;
}
img {
object-fit: cover;
}
}
</style>

View file

@ -39,7 +39,10 @@ export default {
} }
}, },
methods: { methods: {
...mapActions(['deleteItem', 'markItemReturned']), ...mapActions(['deleteItem', 'markItemReturned', 'loadTickets']),
},
created() {
this.loadTickets()
} }
}; };
</script> </script>