Compare commits
1 commit
0ac6b80816
...
343040fa6d
Author | SHA1 | Date | |
---|---|---|---|
343040fa6d |
14 changed files with 199 additions and 158 deletions
|
@ -1,13 +1,12 @@
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from rest_framework import routers, viewsets, status
|
from rest_framework import routers, viewsets
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from inventory.models import Event, Container, Item, Comment
|
from inventory.models import Event, Container, Item
|
||||||
from inventory.serializers import EventSerializer, ContainerSerializer, CommentSerializer, ItemSerializer, \
|
from inventory.serializers import EventSerializer, ContainerSerializer, ItemSerializer, SearchResultSerializer
|
||||||
SearchResultSerializer
|
|
||||||
|
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
|
||||||
|
@ -23,17 +22,6 @@ class ContainerViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Container.objects.all()
|
queryset = Container.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class ItemViewSet(viewsets.ModelViewSet):
|
|
||||||
serializer_class = ItemSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = Item.objects.all()
|
|
||||||
serializer = self.get_serializer_class()
|
|
||||||
if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'prefetch_related_fields'):
|
|
||||||
queryset = queryset.prefetch_related(*serializer.Meta.prefetch_related_fields)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
def filter_items(items, query):
|
def filter_items(items, query):
|
||||||
query_tokens = query.split(' ')
|
query_tokens = query.split(' ')
|
||||||
for item in items:
|
for item in items:
|
||||||
|
@ -61,7 +49,6 @@ def search_items(request, event_slug, query):
|
||||||
@api_view(['GET', 'POST'])
|
@api_view(['GET', 'POST'])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def item(request, event_slug):
|
def item(request, event_slug):
|
||||||
vs = ItemViewSet()
|
|
||||||
try:
|
try:
|
||||||
event = None
|
event = None
|
||||||
if event_slug != 'none':
|
if event_slug != 'none':
|
||||||
|
@ -69,7 +56,7 @@ def item(request, event_slug):
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
if not request.user.has_event_perm(event, 'view_item'):
|
if not request.user.has_event_perm(event, 'view_item'):
|
||||||
return Response(status=403)
|
return Response(status=403)
|
||||||
return Response(ItemSerializer(vs.get_queryset().filter(event=event), many=True).data)
|
return Response(ItemSerializer(Item.objects.filter(event=event), many=True).data)
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
if not request.user.has_event_perm(event, 'add_item'):
|
if not request.user.has_event_perm(event, 'add_item'):
|
||||||
return Response(status=403)
|
return Response(status=403)
|
||||||
|
@ -84,31 +71,12 @@ def item(request, event_slug):
|
||||||
return Response(status=400)
|
return Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@api_view(['POST'])
|
|
||||||
@permission_classes([IsAuthenticated])
|
|
||||||
@permission_required('tickets.add_comment', raise_exception=True)
|
|
||||||
def add_comment(request, event_slug, id):
|
|
||||||
event = None
|
|
||||||
if event_slug != 'none':
|
|
||||||
event = Event.objects.get(slug=event_slug)
|
|
||||||
item = Item.objects.get(event=event, id=id)
|
|
||||||
if not request.user.has_event_perm(event, 'view_item'):
|
|
||||||
return Response(status=403)
|
|
||||||
if 'comment' not in request.data or request.data['comment'] == '':
|
|
||||||
return Response({'status': 'error', 'message': 'missing comment'}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
comment = Comment.objects.create(
|
|
||||||
item=item,
|
|
||||||
comment=request.data['comment'],
|
|
||||||
)
|
|
||||||
return Response(CommentSerializer(comment).data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET', 'PUT', 'DELETE', 'PATCH'])
|
@api_view(['GET', 'PUT', 'DELETE', 'PATCH'])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def item_by_id(request, event_slug, id):
|
def item_by_id(request, event_slug, id):
|
||||||
try:
|
try:
|
||||||
event = Event.objects.get(slug=event_slug)
|
event = Event.objects.get(slug=event_slug)
|
||||||
item = ItemViewSet().get_queryset().get(event=event, id=id)
|
item = Item.objects.get(event=event, id=id)
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
if not request.user.has_event_perm(event, 'view_item'):
|
if not request.user.has_event_perm(event, 'view_item'):
|
||||||
return Response(status=403)
|
return Response(status=403)
|
||||||
|
@ -149,6 +117,5 @@ urlpatterns = router.urls + [
|
||||||
re_path(r'^(?P<event_slug>[\w-]+)/items/$', item, name='item'),
|
re_path(r'^(?P<event_slug>[\w-]+)/items/$', item, name='item'),
|
||||||
re_path(r'^(?P<event_slug>[\w-]+)/items/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_items, name='search_items'),
|
re_path(r'^(?P<event_slug>[\w-]+)/items/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_items, name='search_items'),
|
||||||
re_path(r'^(?P<event_slug>[\w-]+)/item/$', item, name='item'),
|
re_path(r'^(?P<event_slug>[\w-]+)/item/$', item, name='item'),
|
||||||
re_path(r'^(?P<event_slug>[\w-]+)/item/(?P<id>\d+)/comment/$', add_comment, name='add_comment'),
|
|
||||||
re_path(r'^(?P<event_slug>[\w-]+)/item/(?P<id>\d+)/$', item_by_id, name='item_by_id'),
|
re_path(r'^(?P<event_slug>[\w-]+)/item/(?P<id>\d+)/$', item_by_id, name='item_by_id'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,7 +3,7 @@ from rest_framework import serializers
|
||||||
from rest_framework.relations import SlugRelatedField
|
from rest_framework.relations import SlugRelatedField
|
||||||
|
|
||||||
from files.models import File
|
from files.models import File
|
||||||
from inventory.models import Event, Container, Item, Comment
|
from inventory.models import Event, Container, Item
|
||||||
from inventory.shared_serializers import BasicItemSerializer
|
from inventory.shared_serializers import BasicItemSerializer
|
||||||
from mail.models import EventAddress
|
from mail.models import EventAddress
|
||||||
from tickets.shared_serializers import BasicIssueSerializer
|
from tickets.shared_serializers import BasicIssueSerializer
|
||||||
|
@ -38,18 +38,6 @@ class ContainerSerializer(serializers.ModelSerializer):
|
||||||
return len(instance.items)
|
return len(instance.items)
|
||||||
|
|
||||||
|
|
||||||
class CommentSerializer(serializers.ModelSerializer):
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
if 'comment' not in attrs or attrs['comment'] == '':
|
|
||||||
raise serializers.ValidationError('comment cannot be empty')
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Comment
|
|
||||||
fields = ('id', 'comment', 'timestamp', 'item')
|
|
||||||
|
|
||||||
|
|
||||||
class ItemSerializer(BasicItemSerializer):
|
class ItemSerializer(BasicItemSerializer):
|
||||||
timeline = serializers.SerializerMethodField()
|
timeline = serializers.SerializerMethodField()
|
||||||
dataImage = serializers.CharField(write_only=True, required=False)
|
dataImage = serializers.CharField(write_only=True, required=False)
|
||||||
|
@ -60,7 +48,6 @@ class ItemSerializer(BasicItemSerializer):
|
||||||
fields = ['cid', 'box', 'id', 'description', 'file', 'dataImage', 'returned', 'event', 'related_issues',
|
fields = ['cid', 'box', 'id', 'description', 'file', 'dataImage', 'returned', 'event', 'related_issues',
|
||||||
'timeline']
|
'timeline']
|
||||||
read_only_fields = ['id']
|
read_only_fields = ['id']
|
||||||
prefetch_related_fields = ['comments', 'issue_relation_changes', 'container_history']
|
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
container = None
|
container = None
|
||||||
|
|
|
@ -21,13 +21,7 @@ from tickets.shared_serializers import RelationSerializer
|
||||||
|
|
||||||
class IssueViewSet(viewsets.ModelViewSet):
|
class IssueViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = IssueSerializer
|
serializer_class = IssueSerializer
|
||||||
|
queryset = IssueThread.objects.all().prefetch_related('state_changes', 'comments', 'emails', 'emails__attachments', 'assignments', 'item_relation_changes', 'shipping_vouchers')
|
||||||
def get_queryset(self):
|
|
||||||
queryset = IssueThread.objects.all()
|
|
||||||
serializer = self.get_serializer_class()
|
|
||||||
if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'prefetch_related_fields'):
|
|
||||||
queryset = queryset.prefetch_related(*serializer.Meta.prefetch_related_fields)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class RelationViewSet(viewsets.ModelViewSet):
|
class RelationViewSet(viewsets.ModelViewSet):
|
||||||
|
|
|
@ -47,8 +47,6 @@ class IssueSerializer(BasicIssueSerializer):
|
||||||
model = IssueThread
|
model = IssueThread
|
||||||
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid', 'related_items', 'event')
|
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid', 'related_items', 'event')
|
||||||
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid', 'related_items')
|
read_only_fields = ('id', 'timeline', 'last_activity', 'uuid', 'related_items')
|
||||||
prefetch_related_fields = ['state_changes', 'comments', 'emails', 'emails__attachments', 'assignments',
|
|
||||||
'item_relation_changes', 'shipping_vouchers']
|
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
ret = super().to_internal_value(data)
|
ret = super().to_internal_value(data)
|
||||||
|
@ -65,14 +63,12 @@ class IssueSerializer(BasicIssueSerializer):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_last_activity(self):
|
def get_last_activity(self):
|
||||||
try:
|
try:
|
||||||
last_state_change = max(
|
last_state_change = max([t.timestamp for t in self.state_changes.all()]) if self.state_changes.exists() else None
|
||||||
[t.timestamp for t in self.state_changes.all()]) if self.state_changes.exists() else None
|
|
||||||
last_comment = max([t.timestamp for t in self.comments.all()]) if self.comments.exists() else None
|
last_comment = max([t.timestamp for t in self.comments.all()]) if self.comments.exists() else None
|
||||||
last_mail = max([t.timestamp for t in self.emails.all()]) if self.emails.exists() else None
|
last_mail = max([t.timestamp for t in self.emails.all()]) if self.emails.exists() else None
|
||||||
last_assignment = max([t.timestamp for t in self.assignments.all()]) if self.assignments.exists() else None
|
last_assignment = max([t.timestamp for t in self.assignments.all()]) if self.assignments.exists() else None
|
||||||
|
|
||||||
last_relation = max([t.timestamp for t in
|
last_relation = max([t.timestamp for t in self.item_relation_changes.all()]) if self.item_relation_changes.exists() else None
|
||||||
self.item_relation_changes.all()]) if self.item_relation_changes.exists() else None
|
|
||||||
args = [x for x in [last_state_change, last_comment, last_mail, last_assignment, last_relation] if
|
args = [x for x in [last_state_change, last_comment, last_mail, last_assignment, last_relation] if
|
||||||
x is not None]
|
x is not None]
|
||||||
return max(args)
|
return max(args)
|
||||||
|
@ -133,6 +129,7 @@ class IssueSerializer(BasicIssueSerializer):
|
||||||
return sorted(timeline, key=lambda x: x['timestamp'])
|
return sorted(timeline, key=lambda x: x['timestamp'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SearchResultSerializer(serializers.Serializer):
|
class SearchResultSerializer(serializers.Serializer):
|
||||||
search_score = serializers.IntegerField()
|
search_score = serializers.IntegerField()
|
||||||
item = IssueSerializer()
|
item = IssueSerializer()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div style="min-height: 100vh; display: flex; flex-direction: column;">
|
<div style="min-height: 100vh; display: flex; flex-direction: column;">
|
||||||
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="openLightboxModalWith(null)"/>
|
|
||||||
<AddItemModal v-if="addItemModalOpen && isLoggedIn" @close="closeAddItemModal()" isModal="true"/>
|
<AddItemModal v-if="addItemModalOpen && isLoggedIn" @close="closeAddItemModal()" isModal="true"/>
|
||||||
<AddTicketModal v-if="addTicketModalOpen && isLoggedIn" @close="closeAddTicketModal()" isModal="true"/>
|
<AddTicketModal v-if="addTicketModalOpen && isLoggedIn" @close="closeAddTicketModal()" isModal="true"/>
|
||||||
<AddBoxModal v-if="showAddBoxModal && isLoggedIn" @close="closeAddBoxModal()" isModal="true"/>
|
<AddBoxModal v-if="showAddBoxModal && isLoggedIn" @close="closeAddBoxModal()" isModal="true"/>
|
||||||
|
@ -17,22 +16,20 @@ import {mapState, mapMutations, mapActions, mapGetters} from 'vuex';
|
||||||
import AddTicketModal from "@/components/AddTicketModal.vue";
|
import AddTicketModal from "@/components/AddTicketModal.vue";
|
||||||
import AddBoxModal from "@/components/AddBoxModal.vue";
|
import AddBoxModal from "@/components/AddBoxModal.vue";
|
||||||
import AddEventModal from "@/components/AddEventModal.vue";
|
import AddEventModal from "@/components/AddEventModal.vue";
|
||||||
import Lightbox from "@/components/Lightbox.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {Lightbox, AddBoxModal, AddEventModal, Navbar, AddItemModal, AddTicketModal},
|
components: {AddBoxModal, AddEventModal, Navbar, AddItemModal, AddTicketModal},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['loadedItems', 'layout', 'toasts', 'showAddBoxModal', 'showAddEventModal', 'lightboxHash']),
|
...mapState(['loadedItems', 'layout', 'toasts', 'showAddBoxModal', 'showAddEventModal']),
|
||||||
...mapGetters(['isLoggedIn']),
|
...mapGetters(['isLoggedIn']),
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
addItemModalOpen: false,
|
addItemModalOpen: false,
|
||||||
addTicketModalOpen: false,
|
addTicketModalOpen: false
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(['removeToast', 'createToast', 'closeAddBoxModal', 'openAddBoxModal', 'closeAddEventModal',
|
...mapMutations(['removeToast', 'createToast', 'closeAddBoxModal', 'openAddBoxModal', 'closeAddEventModal']),
|
||||||
'openLightboxModalWith']),
|
|
||||||
...mapActions(['loadEvents', 'scheduleAfterInit']),
|
...mapActions(['loadEvents', 'scheduleAfterInit']),
|
||||||
openAddItemModal() {
|
openAddItemModal() {
|
||||||
this.addItemModalOpen = true;
|
this.addItemModalOpen = true;
|
||||||
|
@ -45,7 +42,7 @@ export default {
|
||||||
},
|
},
|
||||||
closeAddTicketModal() {
|
closeAddTicketModal() {
|
||||||
this.addTicketModalOpen = false;
|
this.addTicketModalOpen = false;
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
document.title = document.location.hostname;
|
document.title = document.location.hostname;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="timeline-item-wrapper">
|
<div class="timeline-item-wrapper">
|
||||||
|
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="closeLightboxModal()"/>
|
||||||
<div class="timeline-item-description">
|
<div class="timeline-item-description">
|
||||||
<i class="avatar | small">
|
<i class="avatar | small">
|
||||||
<font-awesome-icon icon="user"/>
|
<font-awesome-icon icon="user"/>
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer" v-if="item.attachments.length">
|
<div class="card-footer" v-if="item.attachments.length">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="attachment in item.attachments" @click="openLightboxModalWith(attachment.hash)">
|
<li v-for="attachment in item.attachments" @click="openLightboxModalWith(attachment)">
|
||||||
<AuthenticatedImage :src="`/media/2/256/${attachment.hash}/`" :alt="attachment.name"
|
<AuthenticatedImage :src="`/media/2/256/${attachment.hash}/`" :alt="attachment.name"
|
||||||
v-if="attachment.mime_type.startsWith('image/')" cached/>
|
v-if="attachment.mime_type.startsWith('image/')" cached/>
|
||||||
<AuthenticatedDataLink :href="`/media/2/${attachment.hash}/`" :download="attachment.name"
|
<AuthenticatedDataLink :href="`/media/2/${attachment.hash}/`" :download="attachment.name"
|
||||||
|
@ -31,6 +32,26 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -38,11 +59,16 @@
|
||||||
|
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||||
import {mapMutations} from "vuex";
|
import Lightbox from "@/components/Lightbox.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelineMail',
|
name: 'TimelineMail',
|
||||||
components: {AuthenticatedImage, AuthenticatedDataLink},
|
components: {Lightbox, AuthenticatedImage, AuthenticatedDataLink},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
lightboxHash: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
'item': {
|
'item': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -59,7 +85,12 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(['openLightboxModalWith'])
|
openLightboxModalWith(attachment) {
|
||||||
|
this.lightboxHash = attachment.hash;
|
||||||
|
},
|
||||||
|
closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item.
|
||||||
|
this.lightboxHash = null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="timeline-item-wrapper">
|
<div class="timeline-item-wrapper">
|
||||||
|
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="closeLightboxModal()"/>
|
||||||
<div class="timeline-item-description">
|
<div class="timeline-item-description">
|
||||||
<i class="avatar | small">
|
<i class="avatar | small">
|
||||||
<font-awesome-icon icon="user"/>
|
<font-awesome-icon icon="user"/>
|
||||||
|
@ -18,7 +19,7 @@
|
||||||
<AuthenticatedImage v-if="item.item.file" cached
|
<AuthenticatedImage v-if="item.item.file" cached
|
||||||
:src="`/media/2/256/${item.item.file}/`"
|
:src="`/media/2/256/${item.item.file}/`"
|
||||||
class="d-block card-img-left"
|
class="d-block card-img-left"
|
||||||
@click="openLightboxModalWith(item.item.file)"
|
@click="openLightboxModalWith(item.item)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -27,10 +28,49 @@
|
||||||
<router-link :to="{name: 'item', params: {id: item.item.id}}">
|
<router-link :to="{name: 'item', params: {id: item.item.id}}">
|
||||||
<h6 class="card-title">{{ item.item.description }}</h6>
|
<h6 class="card-title">{{ item.item.description }}</h6>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<!--div class="row mx-auto mt-2">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-outline-success"
|
||||||
|
@click.stop="confirm('return Item?') && markItemReturned(item.item)"
|
||||||
|
title="returned">
|
||||||
|
<font-awesome-icon icon="check"/>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" @click.stop="openEditingModalWith(item.item)"
|
||||||
|
title="edit">
|
||||||
|
<font-awesome-icon icon="edit"/>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-danger"
|
||||||
|
@click.stop="confirm('delete Item?') && deleteItem(item.item)"
|
||||||
|
title="delete">
|
||||||
|
<font-awesome-icon icon="trash"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>{{ item }}</p-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -38,11 +78,16 @@
|
||||||
|
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||||
import {mapMutations} from "vuex";
|
import Lightbox from "@/components/Lightbox.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelineRelatedItem',
|
name: 'TimelineRelatedItem',
|
||||||
components: {AuthenticatedImage, AuthenticatedDataLink},
|
components: {Lightbox, AuthenticatedImage, AuthenticatedDataLink},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
lightboxHash: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
'item': {
|
'item': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -59,8 +104,13 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(['openLightboxModalWith'])
|
openLightboxModalWith(attachment) {
|
||||||
}
|
this.lightboxHash = attachment.hash;
|
||||||
|
},
|
||||||
|
closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item.
|
||||||
|
this.lightboxHash = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,13 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
|
import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue";
|
||||||
|
import Lightbox from "@/components/Lightbox.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelineRelatedTicket',
|
name: 'TimelineRelatedTicket',
|
||||||
components: {},
|
components: {Lightbox},
|
||||||
props: {
|
props: {
|
||||||
'item': {
|
'item': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import {createRouter, createWebHistory} from 'vue-router'
|
import {createRouter, createWebHistory} from 'vue-router'
|
||||||
import store from '@/store';
|
import store from '@/store';
|
||||||
|
|
||||||
import Item from "@/views/Item.vue";
|
import Items from './views/Items';
|
||||||
import Items from '@/views/Items';
|
import Boxes from './views/Boxes';
|
||||||
import Boxes from '@/views/Boxes';
|
import Files from './views/Files';
|
||||||
import Files from '@/views/Files';
|
import HowTo from './views/HowTo';
|
||||||
import HowTo from '@/views/HowTo';
|
|
||||||
import Login from '@/views/Login.vue';
|
import Login from '@/views/Login.vue';
|
||||||
import Register from '@/views/Register.vue';
|
import Register from '@/views/Register.vue';
|
||||||
import Dashboard from "@/views/admin/Dashboard.vue";
|
import Dashboard from "@/views/admin/Dashboard.vue";
|
||||||
|
|
|
@ -34,7 +34,6 @@ const store = createStore({
|
||||||
expiry: null,
|
expiry: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
lightboxHash: null,
|
|
||||||
thumbnailCache: {},
|
thumbnailCache: {},
|
||||||
fetchedData: {
|
fetchedData: {
|
||||||
events: 0,
|
events: 0,
|
||||||
|
@ -186,9 +185,6 @@ const store = createStore({
|
||||||
state.groups = groups;
|
state.groups = groups;
|
||||||
state.fetchedData = {...state.fetchedData, groups: Date.now()};
|
state.fetchedData = {...state.fetchedData, groups: Date.now()};
|
||||||
},
|
},
|
||||||
openLightboxModalWith(state, hash) {
|
|
||||||
state.lightboxHash = hash;
|
|
||||||
},
|
|
||||||
openAddBoxModal(state) {
|
openAddBoxModal(state) {
|
||||||
state.showAddBoxModal = true;
|
state.showAddBoxModal = true;
|
||||||
},
|
},
|
||||||
|
@ -353,6 +349,7 @@ const store = createStore({
|
||||||
},
|
},
|
||||||
async changeEvent({dispatch, getters, commit}, eventName) {
|
async changeEvent({dispatch, getters, commit}, eventName) {
|
||||||
await router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
|
await router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
|
||||||
|
//dispatch('loadEventItems');
|
||||||
},
|
},
|
||||||
async changeView({getters}, link) {
|
async changeView({getters}, link) {
|
||||||
await router.push({path: `/${getters.getEventSlug}/${link.path}/`});
|
await router.push({path: `/${getters.getEventSlug}/${link.path}/`});
|
||||||
|
|
|
@ -8,25 +8,15 @@
|
||||||
<AuthenticatedImage v-if="item.file" cached
|
<AuthenticatedImage v-if="item.file" cached
|
||||||
:src="`/media/2/256/${item.file}/`"
|
:src="`/media/2/256/${item.file}/`"
|
||||||
class="d-block card-img"
|
class="d-block card-img"
|
||||||
@click="openLightboxModalWith(item.file)"
|
@click="openLightboxModalWith(item)"
|
||||||
/>
|
/>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-subtitle text-secondary">id: {{ item.id }} box: {{ item.box }}</h6>
|
<h6 class="card-subtitle text-secondary">id: {{ item.id }} box: {{
|
||||||
|
item.box
|
||||||
|
}}</h6>
|
||||||
<h6 class="card-title">{{ item.description }}</h6>
|
<h6 class="card-title">{{ item.description }}</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
|
||||||
<InputPhoto
|
|
||||||
:model="editingItem"
|
|
||||||
field="file"
|
|
||||||
:on-capture="storeImage"
|
|
||||||
/>
|
|
||||||
<InputString
|
|
||||||
label="description"
|
|
||||||
:model="editingItem"
|
|
||||||
field="description"
|
|
||||||
:validation-fn="str => str && str.length > 0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +26,7 @@
|
||||||
<h3>Item #{{ item.id }} - {{ item.description }}</h3>
|
<h3>Item #{{ item.id }} - {{ item.description }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<Timeline :timeline="item.timeline">
|
<Timeline :timeline="item.timeline">
|
||||||
<!--template v-slot:timeline_action1>
|
<template v-slot:timeline_action1>
|
||||||
<span class="timeline-item-icon | faded-icon">
|
<span class="timeline-item-icon | faded-icon">
|
||||||
<font-awesome-icon icon="comment"/>
|
<font-awesome-icon icon="comment"/>
|
||||||
</span>
|
</span>
|
||||||
|
@ -51,35 +41,47 @@
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template-->
|
</template>
|
||||||
</Timeline>
|
</Timeline>
|
||||||
<div class="card-footer d-flex justify-content-between">
|
<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-secondary mr-2" @click="$router.go(-1)">Back</button>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-outline-success"
|
<button class="btn btn-outline-success"
|
||||||
@click.stop="confirm('return Item?') && markItemReturned(item)"
|
@click.stop="confirm('return Item?') && markItemReturned(item)"
|
||||||
title="returned">
|
title="returned">
|
||||||
<font-awesome-icon icon="check"/> mark returned
|
<font-awesome-icon icon="check"/>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" @click.stop="openEditingModalWith(item)"
|
||||||
|
title="edit">
|
||||||
|
<font-awesome-icon icon="edit"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-danger"
|
<button class="btn btn-outline-danger"
|
||||||
@click.stop="confirm('delete Item?') && deleteItem(item)"
|
@click.stop="confirm('delete Item?') && deleteItem(item)"
|
||||||
title="delete">
|
title="delete">
|
||||||
<font-awesome-icon icon="trash"/> delete
|
<font-awesome-icon icon="trash"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InputCombo
|
<InputCombo
|
||||||
label="box"
|
label="box"
|
||||||
:model="editingItem"
|
:model="item"
|
||||||
nameKey="box"
|
nameKey="box"
|
||||||
uniqueKey="cid"
|
uniqueKey="cid"
|
||||||
:options="boxes"
|
:options="boxes"
|
||||||
style="width: auto;"
|
|
||||||
/>
|
/>
|
||||||
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-success" @click="saveEditingItem()">Save Changes
|
<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(item)"
|
||||||
|
:disabled="(selected_state == item.state)">
|
||||||
|
Change Status
|
||||||
</button>
|
</button>
|
||||||
{{ editingItem }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,29 +105,21 @@
|
||||||
</AsyncLoader>
|
</AsyncLoader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapActions, mapGetters, mapMutations, mapState} from 'vuex';
|
import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
import Timeline from "@/components/Timeline.vue";
|
import Timeline from "@/components/Timeline.vue";
|
||||||
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
||||||
import AsyncLoader from "@/components/AsyncLoader.vue";
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||||
import InputCombo from "@/components/inputs/InputCombo.vue";
|
import InputCombo from "@/components/inputs/InputCombo.vue";
|
||||||
import InputString from "@/components/inputs/InputString.vue";
|
import InputString from "@/components/inputs/InputString.vue";
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
import InputPhoto from "@/components/inputs/InputPhoto.vue";
|
|
||||||
import Modal from "@/components/Modal.vue";
|
|
||||||
import EditItem from "@/components/EditItem.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Item',
|
name: 'Item',
|
||||||
components: {
|
components: {AuthenticatedImage, InputString, InputCombo, AsyncLoader, ClipboardButton, Timeline},
|
||||||
EditItem,
|
|
||||||
Modal, InputPhoto, AuthenticatedImage, InputString, InputCombo, AsyncLoader, ClipboardButton, Timeline
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
newComment: "",
|
newComment: ""
|
||||||
editingItem: {},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -137,40 +131,33 @@ export default {
|
||||||
return ret ? ret : {};
|
return ret ? ret : {};
|
||||||
},
|
},
|
||||||
boxes() {
|
boxes() {
|
||||||
|
console.log(this.getBoxes);
|
||||||
return this.getBoxes.map(obj => ({cid: obj.cid, box: obj.name}));
|
return this.getBoxes.map(obj => ({cid: obj.cid, box: obj.name}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['deleteItem', 'markItemReturned', 'updateTicketPartial', 'postComment']),
|
...mapActions(['deleteItem', 'markItemReturned', 'updateTicketPartial', 'postComment']),
|
||||||
...mapActions(['loadTickets', 'fetchTicketStates', 'loadUsers', 'scheduleAfterInit', 'updateItem']),
|
...mapActions(['loadTickets', 'fetchTicketStates', 'loadUsers', 'scheduleAfterInit']),
|
||||||
...mapActions(['claimShippingVoucher', 'fetchShippingVouchers', 'loadEventItems', 'loadBoxes']),
|
...mapActions(['claimShippingVoucher', 'fetchShippingVouchers', 'loadEventItems', 'loadBoxes']),
|
||||||
...mapMutations(['openLightboxModalWith']),
|
changeTicketStatus(item) {
|
||||||
|
item.state = this.selected_state;
|
||||||
|
this.updateTicketPartial({
|
||||||
|
id: item.id,
|
||||||
|
state: this.selected_state,
|
||||||
|
})
|
||||||
|
},
|
||||||
addCommentAndClear: async function () {
|
addCommentAndClear: async function () {
|
||||||
await this.postComment({
|
await this.postComment({
|
||||||
id: this.ticket.id,
|
id: this.ticket.id,
|
||||||
message: this.newComment
|
message: this.newComment
|
||||||
})
|
})
|
||||||
this.newComment = "";
|
this.newComment = "";
|
||||||
},
|
}
|
||||||
closeEditingModal() {
|
|
||||||
this.editingItem = null;
|
|
||||||
},
|
|
||||||
async saveEditingItem() { // Saves the edited copy of the item.
|
|
||||||
await this.updateItem(this.editingItem);
|
|
||||||
this.editingItem = {...this.item}
|
|
||||||
},
|
|
||||||
storeImage(image) {
|
|
||||||
this.item.dataImage = image;
|
|
||||||
},
|
|
||||||
confirm(message) {
|
|
||||||
return window.confirm(message);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.scheduleAfterInit(() => [Promise.all([this.loadEventItems(), this.loadBoxes()]).then(() => {
|
this.scheduleAfterInit(() => [Promise.all([this.loadEventItems(), this.loadBoxes()]).then(() => {
|
||||||
this.selected_state = this.item.state;
|
this.selected_state = this.item.state;
|
||||||
this.selected_assignee = this.item.assigned_to
|
this.selected_assignee = this.item.assigned_to
|
||||||
this.editingItem = {...this.item}
|
|
||||||
})]);
|
})]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<AsyncLoader :loaded="isItemsLoaded">
|
<AsyncLoader :loaded="isItemsLoaded">
|
||||||
<div class="container-fluid px-xl-5 mt-3">
|
<div class="container-fluid px-xl-5 mt-3">
|
||||||
|
<Modal title="Edit Item" v-if="editingItem" @close="closeEditingModal()">
|
||||||
|
<template #body>
|
||||||
|
<EditItem
|
||||||
|
:item="editingItem"
|
||||||
|
badge="id"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #buttons>
|
||||||
|
<button type="button" class="btn btn-secondary" @click="closeEditingModal()">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-success" @click="saveEditingItem()">Save Changes</button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
<Lightbox v-if="lightboxHash" :hash="lightboxHash" @close="closeLightboxModal()"/>
|
||||||
<div class="row" v-if="layout === 'table'">
|
<div class="row" v-if="layout === 'table'">
|
||||||
<div class="col-xl-8 offset-xl-2">
|
<div class="col-xl-8 offset-xl-2">
|
||||||
<Table
|
<Table
|
||||||
|
@ -34,7 +47,7 @@
|
||||||
:items="getEventItems"
|
:items="getEventItems"
|
||||||
:keyName="'id'"
|
:keyName="'id'"
|
||||||
v-slot="{ item }"
|
v-slot="{ item }"
|
||||||
@itemActivated="item => openLightboxModalWith(item.file)"
|
@itemActivated="showItemDetail"
|
||||||
>
|
>
|
||||||
<AuthenticatedImage v-if="item.file" cached
|
<AuthenticatedImage v-if="item.file" cached
|
||||||
:src="`/media/2/256/${item.file}/`"
|
:src="`/media/2/256/${item.file}/`"
|
||||||
|
@ -49,7 +62,7 @@
|
||||||
@click.stop="confirm('return Item?') && markItemReturned(item)" title="returned">
|
@click.stop="confirm('return Item?') && markItemReturned(item)" title="returned">
|
||||||
<font-awesome-icon icon="check"/>
|
<font-awesome-icon icon="check"/>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary" @click.stop="showItemDetail(item)"
|
<button class="btn btn-outline-secondary" @click.stop="openEditingModalWith(item)"
|
||||||
title="edit">
|
title="edit">
|
||||||
<font-awesome-icon icon="edit"/>
|
<font-awesome-icon icon="edit"/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -71,7 +84,8 @@ import Table from '@/components/Table';
|
||||||
import Cards from '@/components/Cards';
|
import Cards from '@/components/Cards';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import EditItem from '@/components/EditItem';
|
import EditItem from '@/components/EditItem';
|
||||||
import {mapActions, mapGetters, mapMutations} from 'vuex';
|
import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
|
import Lightbox from '../components/Lightbox';
|
||||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
import AsyncLoader from "@/components/AsyncLoader.vue";
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
@ -82,16 +96,32 @@ export default {
|
||||||
lightboxHash: null,
|
lightboxHash: null,
|
||||||
editingItem: null,
|
editingItem: null,
|
||||||
}),
|
}),
|
||||||
components: {AsyncLoader, AuthenticatedImage, Table, Cards, Modal, EditItem},
|
components: {AsyncLoader, AuthenticatedImage, Lightbox, Table, Cards, Modal, EditItem},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState([]),
|
||||||
...mapGetters(['getEventItems', 'isItemsLoaded', 'layout']),
|
...mapGetters(['getEventItems', 'isItemsLoaded', 'layout']),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['deleteItem', 'markItemReturned', 'loadEventItems', 'updateItem', 'scheduleAfterInit']),
|
...mapActions(['deleteItem', 'markItemReturned', 'loadEventItems', 'updateItem', 'scheduleAfterInit']),
|
||||||
...mapMutations(['openLightboxModalWith']),
|
|
||||||
showItemDetail(item) {
|
showItemDetail(item) {
|
||||||
router.push({name: 'item', params: {id: item.id}});
|
router.push({name: 'item', params: {id: item.id}});
|
||||||
},
|
},
|
||||||
|
openLightboxModalWith(item) {
|
||||||
|
this.lightboxHash = item.file;
|
||||||
|
},
|
||||||
|
closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item.
|
||||||
|
this.lightboxHash = null;
|
||||||
|
},
|
||||||
|
openEditingModalWith(item) { // Opens the editing modal with a copy of the selected item.
|
||||||
|
this.editingItem = item;
|
||||||
|
},
|
||||||
|
closeEditingModal() {
|
||||||
|
this.editingItem = null;
|
||||||
|
},
|
||||||
|
saveEditingItem() { // Saves the edited copy of the item.
|
||||||
|
this.updateItem(this.editingItem);
|
||||||
|
this.closeEditingModal();
|
||||||
|
},
|
||||||
confirm(message) {
|
confirm(message) {
|
||||||
return window.confirm(message);
|
return window.confirm(message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
<AuthenticatedImage v-if="item.file" cached
|
<AuthenticatedImage v-if="item.file" cached
|
||||||
:src="`/media/2/256/${item.file}/`"
|
:src="`/media/2/256/${item.file}/`"
|
||||||
class="d-block card-img"
|
class="d-block card-img"
|
||||||
@click="openLightboxModalWith(item.file)"
|
@click="openLightboxModalWith(item)"
|
||||||
/>
|
/>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!--h6 class="card-title text-info"><span class="badge badge-primary">{{ item.relation_status }}</span></--h6-->
|
<!--h6 class="card-title text-info"><span class="badge badge-primary">{{ item.relation_status }}</span></--h6-->
|
||||||
|
@ -126,7 +126,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapActions, mapGetters, mapMutations, mapState} from 'vuex';
|
import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
import Timeline from "@/components/Timeline.vue";
|
import Timeline from "@/components/Timeline.vue";
|
||||||
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
||||||
import AsyncLoader from "@/components/AsyncLoader.vue";
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||||
|
@ -166,7 +166,6 @@ export default {
|
||||||
...mapActions(['deleteItem', 'markItemReturned', 'sendMail', 'updateTicketPartial', 'postComment']),
|
...mapActions(['deleteItem', 'markItemReturned', 'sendMail', 'updateTicketPartial', 'postComment']),
|
||||||
...mapActions(['loadTickets', 'fetchTicketStates', 'loadUsers', 'scheduleAfterInit']),
|
...mapActions(['loadTickets', 'fetchTicketStates', 'loadUsers', 'scheduleAfterInit']),
|
||||||
...mapActions(['claimShippingVoucher', 'fetchShippingVouchers']),
|
...mapActions(['claimShippingVoucher', 'fetchShippingVouchers']),
|
||||||
...mapMutations(['openLightboxModalWith']),
|
|
||||||
changeTicketStatus() {
|
changeTicketStatus() {
|
||||||
this.ticket.state = this.selected_state;
|
this.ticket.state = this.selected_state;
|
||||||
this.updateTicketPartial({
|
this.updateTicketPartial({
|
||||||
|
|
|
@ -55,22 +55,24 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Cards from '@/components/Cards';
|
import Cards from '@/components/Cards';
|
||||||
|
import Modal from '@/components/Modal';
|
||||||
|
import EditItem from '@/components/EditItem';
|
||||||
import {mapActions, mapGetters, mapState} from 'vuex';
|
import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
|
import Lightbox from '../components/Lightbox';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import CollapsableCards from "@/components/CollapsableCards.vue";
|
import CollapsableCards from "@/components/CollapsableCards.vue";
|
||||||
import AsyncLoader from "@/components/AsyncLoader.vue";
|
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||||
import router from "@/router";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Tickets',
|
name: 'Tickets',
|
||||||
components: {AsyncLoader, Table, Cards, CollapsableCards},
|
components: {AsyncLoader, Lightbox, Table, Cards, Modal, EditItem, CollapsableCards},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['getEventTickets', 'isTicketsLoaded', 'stateInfo', 'getEventSlug', 'layout']),
|
...mapGetters(['getEventTickets', 'isTicketsLoaded', 'stateInfo', 'getEventSlug', 'layout']),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['loadTickets', 'fetchTicketStates', 'scheduleAfterInit']),
|
...mapActions(['loadTickets', 'fetchTicketStates', 'scheduleAfterInit']),
|
||||||
gotoDetail(ticket) {
|
gotoDetail(ticket) {
|
||||||
router.push({name: 'ticket', params: {id: ticket.id}});
|
this.$router.push({name: 'ticket', params: {id: ticket.id}});
|
||||||
},
|
},
|
||||||
formatTicket(ticket) {
|
formatTicket(ticket) {
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Add table
Reference in a new issue