214 lines
11 KiB
Vue
214 lines
11 KiB
Vue
<template>
|
|
<AsyncLoader :loaded="!!ticket.id">
|
|
<div class="container-fluid px-xl-5 mt-3">
|
|
<div class="row">
|
|
<div class="col-xl-8 offset-xl-2">
|
|
<div class="card bg-dark text-light mb-2" id="filters">
|
|
<div class="card-header">
|
|
<h3>Ticket #{{ ticket.id }} - {{ ticket.name }}</h3>
|
|
</div>
|
|
<Timeline :timeline="ticket.timeline">
|
|
<template v-slot:timeline_action1>
|
|
<span class="timeline-item-icon | faded-icon">
|
|
<font-awesome-icon icon="comment"/>
|
|
</span>
|
|
<div class="new-comment card bg-dark">
|
|
<div>
|
|
<textarea placeholder="add comment..." v-model="newComment"
|
|
class="form-control">
|
|
</textarea>
|
|
<AsyncButton class="btn btn-primary float-right" :task="addCommentAndClear" :disabled="!newComment">
|
|
<font-awesome-icon icon="comment"/>
|
|
Save Comment
|
|
</AsyncButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-slot:timeline_action2>
|
|
<span class="timeline-item-icon | faded-icon">
|
|
<font-awesome-icon icon="envelope"/>
|
|
</span>
|
|
<div class="new-mail card bg-dark">
|
|
<div class="card-header">
|
|
{{ newestMailSubject }}
|
|
</div>
|
|
<div>
|
|
<textarea placeholder="reply mail..." v-model="newMail" class="form-control">
|
|
</textarea>
|
|
<AsyncButton class="btn btn-primary float-right" :task="sendMailAndClear" :disabled="!newMail">
|
|
<font-awesome-icon icon="envelope"/>
|
|
Send Mail
|
|
</AsyncButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Timeline>
|
|
<div class="card-footer d-flex justify-content-between">
|
|
<button class="btn btn-secondary mr-2" @click="$router.go(-1)">Back</button>
|
|
<!--button class="btn btn-danger" @click="deleteItem({type: 'tickets', id: ticket.id})">
|
|
<font-awesome-icon icon="trash"/>
|
|
Delete
|
|
</button-->
|
|
<div class="btn-group">
|
|
<select class="form-control" v-model="selected_assignee">
|
|
<option v-for="user in users" :value="user.username">{{
|
|
user.username
|
|
}}
|
|
</option>
|
|
</select>
|
|
<button class="form-control btn btn-success"
|
|
@click="assignTicket(ticket)"
|
|
:disabled="!selected_assignee || (selected_assignee === ticket.assigned_to)">
|
|
Assign Ticket
|
|
</button>
|
|
</div>
|
|
<div class="btn-group">
|
|
<select class="form-control" v-model="selected_state">
|
|
<option v-for="status in state_options" :value="status.value">{{
|
|
status.text
|
|
}}
|
|
</option>
|
|
</select>
|
|
<button class="form-control btn btn-success"
|
|
@click="changeTicketStatus(ticket)"
|
|
:disabled="(selected_state === ticket.state)">
|
|
Change Status
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer d-flex justify-content-between">
|
|
<ClipboardButton :payload="shippingEmail" class="btn btn-primary">
|
|
<font-awesome-icon icon="clipboard"/>
|
|
Copy DHL contact to clipboard
|
|
</ClipboardButton>
|
|
<div class="btn-group">
|
|
<select class="form-control" v-model="shipping_voucher_type">
|
|
<option v-for="type in availableShippingVoucherTypes.filter(t=>t.count>0)"
|
|
:value="type.id">{{ type.name }}
|
|
</option>
|
|
</select>
|
|
<button class="form-control btn btn-success"
|
|
@click="claimShippingVoucher({ticket: ticket.id, shipping_voucher_type}).then(()=>shipping_voucher_type=null)"
|
|
:disabled="!shipping_voucher_type">
|
|
Claim Shipping Voucher
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-xl-2 d-lg-none d-xl-block"
|
|
v-if="ticket.related_items && ticket.related_items.length">
|
|
<div class="card bg-dark text-light mb-2" id="filters">
|
|
<div class="card-body">
|
|
<h5 class="card-title text-info">Related</h5>
|
|
<div class="card bg-dark" v-for="item in ticket.related_items" v-bind:key="item.id">
|
|
<AuthenticatedImage v-if="item.file" cached
|
|
:src="`/media/2/256/${item.file}/`"
|
|
class="d-block card-img"
|
|
@click="openLightboxModalWith(item.file)"
|
|
/>
|
|
<div class="card-body">
|
|
<!--h6 class="card-title text-info"><span class="badge badge-primary">{{ item.relation_status }}</span></--h6-->
|
|
<h6 class="card-subtitle text-secondary">id: {{ item.id }} box: {{
|
|
item.box
|
|
}}</h6>
|
|
<router-link :to="{name: 'item', params: {id: item.id}}">
|
|
<h6 class="card-title">{{ item.description }}</h6>
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AsyncLoader>
|
|
</template>
|
|
|
|
<script>
|
|
import {mapActions, mapGetters, mapMutations, mapState} from 'vuex';
|
|
import Timeline from "@/components/Timeline.vue";
|
|
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
|
import AsyncButton from "@/components/inputs/AsyncButton.vue";
|
|
|
|
export default {
|
|
name: 'Ticket',
|
|
components: {AsyncButton, AuthenticatedImage, AsyncLoader, ClipboardButton, Timeline},
|
|
data() {
|
|
return {
|
|
selected_state: null,
|
|
selected_assignee: null,
|
|
shipping_voucher_type: null,
|
|
newMail: "",
|
|
newComment: ""
|
|
}
|
|
},
|
|
computed: {
|
|
...mapState(['state_options', 'users']),
|
|
...mapGetters(['availableShippingVoucherTypes', 'getAllTickets', 'route']),
|
|
ticket() {
|
|
const id = parseInt(this.route.params.id)
|
|
const ret = this.getAllTickets.find(ticket => ticket.id === id);
|
|
return ret ? ret : {};
|
|
},
|
|
shippingEmail() {
|
|
const domain = document.location.hostname;
|
|
return `ticket+${this.ticket.uuid}@${domain}`;
|
|
},
|
|
newestMailSubject() {
|
|
const mail = this.ticket.timeline ? this.ticket.timeline.filter(item => item.type === 'mail').pop() : null;
|
|
return mail ? mail.subject : "";
|
|
},
|
|
},
|
|
methods: {
|
|
...mapActions(['deleteItem', 'markItemReturned', 'sendMail', 'updateTicketPartial', 'postComment']),
|
|
...mapActions(['loadTickets', 'fetchTicketStates', 'loadUsers', 'scheduleAfterInit']),
|
|
...mapActions(['claimShippingVoucher', 'fetchShippingVouchers']),
|
|
...mapMutations(['openLightboxModalWith']),
|
|
changeTicketStatus() {
|
|
this.ticket.state = this.selected_state;
|
|
this.updateTicketPartial({
|
|
id: this.ticket.id,
|
|
state: this.selected_state,
|
|
})
|
|
},
|
|
assignTicket() {
|
|
this.ticket.assigned_to = this.selected_assignee;
|
|
this.updateTicketPartial({
|
|
id: this.ticket.id,
|
|
assigned_to: this.selected_assignee
|
|
})
|
|
},
|
|
sendMailAndClear: async function () {
|
|
await this.sendMail({
|
|
id: this.ticket.id,
|
|
message: this.newMail,
|
|
})
|
|
this.newMail = "";
|
|
},
|
|
addCommentAndClear: async function () {
|
|
await this.postComment({
|
|
id: this.ticket.id,
|
|
message: this.newComment
|
|
})
|
|
this.newComment = "";
|
|
}
|
|
},
|
|
mounted() {
|
|
this.scheduleAfterInit(() => [Promise.all([this.fetchTicketStates(), this.loadTickets(), this.loadUsers(), this.fetchShippingVouchers()]).then(() => {
|
|
if (this.ticket.state === "pending_new") {
|
|
this.selected_state = "pending_open";
|
|
this.changeTicketStatus()
|
|
}
|
|
this.selected_state = this.ticket.state;
|
|
this.selected_assignee = this.ticket.assigned_to
|
|
})]);
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
|
|
</style>
|