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"> </span>
<font-awesome-icon icon="user"/> <span class="timeline-item-icon faded-icon" v-else-if="item.type === 'state'">
</i> <font-awesome-icon icon="check"/>
<span><a href="#">Yoan Almedia</a> commented on <time </span>
datetime="20-01-2021">Jan 20, 2021</time></span> <span class="timeline-item-icon faded-icon" v-else>
</div> <font-awesome-icon icon="pen"/>
<div class="comment"> </span>
<p>I've sent him the assignment we discussed recently, he is coming back to us this week. Regarding <TimelineMail v-if="item.type === 'mail'" :item="item"/>
to our last call, I really enjoyed talking to him and so far he has the profile we are looking <TimelineComment v-else-if="item.type === 'comment'" :item="item"/>
for. Can't wait to see his technical test, I'll keep you posted and we'll debrief it all <TimelineStateChange v-else-if="item.type === 'state'" :item="item"/>
together!😊</p> <p v-else>{{ item }}</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>
</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>