From 343040fa6db817f20aa4fa543f98efaed1296532 Mon Sep 17 00:00:00 2001 From: jedi Date: Wed, 27 Nov 2024 19:39:58 +0100 Subject: [PATCH 1/3] add basic view for item history --- web/src/components/AuthenticatedImage.vue | 28 ++-- web/src/components/CollapsableCards.vue | 6 +- web/src/components/EditItem.vue | 17 +- web/src/components/Timeline.vue | 76 +++------ web/src/components/TimelinePlacement.vue | 85 ++++++++++ web/src/components/TimelineRelatedItem.vue | 14 +- web/src/components/TimelineRelatedTicket.vue | 98 +++++++++++ web/src/components/inputs/InputCombo.vue | 61 ++++--- web/src/router.js | 2 +- web/src/store.js | 25 ++- web/src/views/Item.vue | 168 +++++++++++++++++++ web/src/views/Items.vue | 20 ++- web/src/views/Ticket.vue | 127 ++++++++++---- web/src/views/Tickets.vue | 12 +- 14 files changed, 570 insertions(+), 169 deletions(-) create mode 100644 web/src/components/TimelinePlacement.vue create mode 100644 web/src/components/TimelineRelatedTicket.vue create mode 100644 web/src/views/Item.vue diff --git a/web/src/components/AuthenticatedImage.vue b/web/src/components/AuthenticatedImage.vue index 9e1a963..8b463b0 100644 --- a/web/src/components/AuthenticatedImage.vue +++ b/web/src/components/AuthenticatedImage.vue @@ -42,19 +42,27 @@ export default { url: this.src, data: this.image_data }); + }, + deferImage() { + setTimeout(() => { + if (this.cached) { + const c = this.getThumbnail(this.src); + if (c) { + this.image_data = c; + return; + } + } + this.loadImage(); + }, 0); + } + }, + watch: { + src: function (newVal, oldVal) { + this.deferImage() } }, mounted() { - setTimeout(() => { - if (this.cached) { - const c = this.getThumbnail(this.src); - if (c) { - this.image_data = c; - return; - } - } - this.loadImage(); - }, 0); + this.deferImage(); } } \ No newline at end of file diff --git a/web/src/components/CollapsableCards.vue b/web/src/components/CollapsableCards.vue index d1edab7..d38206a 100644 --- a/web/src/components/CollapsableCards.vue +++ b/web/src/components/CollapsableCards.vue @@ -52,7 +52,7 @@ export default { }; }, created() { - const query = this.$router.currentRoute ? (this.$router.currentRoute.query ? this.$router.currentRoute.query.collapsed : null) : null; + const query = this.route ? (this.route.query ? this.route.query.collapsed : null) : null; if (query !== null && query !== undefined) { this.collapsed = this.unpackInt(parseInt(query), this.sections.length); } else { @@ -84,8 +84,8 @@ export default { const encoded = this.packInt(this.collapsed).toString() if (this.route.query.collapsed !== encoded) this.$router.push({ - ...this.$router.currentRoute, - query: {...this.$router.currentRoute.query, collapsed: encoded} + ...this.route, + query: {...this.route.query, collapsed: encoded} }); }, deep: true, diff --git a/web/src/components/EditItem.vue b/web/src/components/EditItem.vue index 9bea6f9..833bfd5 100644 --- a/web/src/components/EditItem.vue +++ b/web/src/components/EditItem.vue @@ -12,13 +12,16 @@ field="description" :validation-fn="str => str && str.length > 0" /> - +
+ + +
diff --git a/web/src/components/Timeline.vue b/web/src/components/Timeline.vue index 88bfa94..41e1274 100644 --- a/web/src/components/Timeline.vue +++ b/web/src/components/Timeline.vue @@ -21,6 +21,9 @@ + + + @@ -30,40 +33,15 @@ + +

{{ item }}

  • - - - -
    -
    - - - - Save Comment - -
    -
    +
  • - - - -
    -
    - {{ newestMailSubject }} -
    -
    - - - - Send Mail - -
    -
    +
  • @@ -78,12 +56,20 @@ import TimelineAssignment from "@/components/TimelineAssignment.vue"; import TimelineRelatedItem from "@/components/TimelineRelatedItem.vue"; import TimelineShippingVoucher from "@/components/TimelineShippingVoucher.vue"; import AsyncButton from "@/components/inputs/AsyncButton.vue"; +import TimelinePlacement from "@/components/TimelinePlacement.vue"; +import TimelineRelatedTicket from "@/components/TimelineRelatedTicket.vue"; export default { name: 'Timeline', components: { - TimelineShippingVoucher, AsyncButton, - TimelineRelatedItem, TimelineAssignment, TimelineStateChange, TimelineComment, TimelineMail + TimelineRelatedTicket, + TimelinePlacement, + TimelineShippingVoucher, + TimelineRelatedItem, + TimelineAssignment, + TimelineStateChange, + TimelineComment, + TimelineMail }, props: { timeline: { @@ -91,33 +77,13 @@ export default { default: () => [] } }, - emits: ['sendMail', 'addComment'], - data: () => ({ - newMail: "", - newComment: "" - }), computed: { - ...mapGetters(['stateInfo']), - newestMailSubject() { - const mail = this.timeline.filter(item => item.type === 'mail').pop(); - return mail ? mail.subject : ""; - }, + ...mapGetters(['stateInfo']) }, - methods: { - ...mapActions(['sendMail', 'postComment']), - sendMailAndClear: async function () { - await this.sendMail(this.newMail); - this.newMail = ""; - }, - addCommentAndClear: async function () { - await this.postComment(this.newComment); - this.newComment = ""; - } - } }; - \ No newline at end of file diff --git a/web/src/components/TimelineRelatedItem.vue b/web/src/components/TimelineRelatedItem.vue index c17a3a8..b795853 100644 --- a/web/src/components/TimelineRelatedItem.vue +++ b/web/src/components/TimelineRelatedItem.vue @@ -5,8 +5,12 @@ - linked item #{{ item.item.uid }} on as {{ item.status }} + linked item #{{ + item.item.id + }} on as {{ + item.status + }}
    @@ -20,8 +24,10 @@
    -
    uid: {{ item.item.uid }} box: {{ item.item.box }}
    -
    {{ item.item.description }}
    +
    id: {{ item.item.id }} box: {{ item.item.box }}
    + +
    {{ item.item.description }}
    +
    @@ -33,7 +71,7 @@
    @@ -58,6 +96,30 @@
    +
    +
    +
    +
    Related
    +
    + +
    + +
    id: {{ item.id }} box: {{ + item.box + }}
    + +
    {{ item.description }}
    +
    +
    +
    +
    +
    +
    @@ -68,15 +130,19 @@ import {mapActions, mapGetters, 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: {AsyncLoader, ClipboardButton, Timeline}, + components: {AsyncButton, AuthenticatedImage, AsyncLoader, ClipboardButton, Timeline}, data() { return { selected_state: null, selected_assignee: null, shipping_voucher_type: null, + newMail: "", + newComment: "" } }, computed: { @@ -90,46 +156,51 @@ export default { 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']), - handleMail(mail) { - this.sendMail({ - id: this.ticket.id, - message: mail - }) - }, - handleComment(comment) { - this.postComment({ - id: this.ticket.id, - message: comment - }) - }, - changeTicketStatus(ticket) { - ticket.state = this.selected_state; + changeTicketStatus() { + this.ticket.state = this.selected_state; this.updateTicketPartial({ - id: ticket.id, + id: this.ticket.id, state: this.selected_state, }) }, - assignTicket(ticket) { - ticket.assigned_to = this.selected_assignee; + assignTicket() { + this.ticket.assigned_to = this.selected_assignee; this.updateTicketPartial({ - id: ticket.id, + 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") { + if (this.ticket.state === "pending_new") { this.selected_state = "pending_open"; - this.changeTicketStatus(this.ticket) + this.changeTicketStatus() } - ; this.selected_state = this.ticket.state; this.selected_assignee = this.ticket.assigned_to })]); diff --git a/web/src/views/Tickets.vue b/web/src/views/Tickets.vue index bc288f1..4a9144c 100644 --- a/web/src/views/Tickets.vue +++ b/web/src/views/Tickets.vue @@ -12,11 +12,11 @@ > @@ -39,11 +39,11 @@ {{ item.event }} From b109e5995ee376c7b43b48e738072c537f31ec64 Mon Sep 17 00:00:00 2001 From: jedi Date: Thu, 28 Nov 2024 21:58:26 +0100 Subject: [PATCH 2/3] add /item/comment endpoint and prefetch related models --- core/inventory/api_v2.py | 43 +++++++++++++++++++++++++++++++---- core/inventory/serializers.py | 15 +++++++++++- core/tickets/api_v2.py | 10 ++++++-- core/tickets/serializers.py | 9 +++++--- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/core/inventory/api_v2.py b/core/inventory/api_v2.py index 60f0292..64f9b27 100644 --- a/core/inventory/api_v2.py +++ b/core/inventory/api_v2.py @@ -1,12 +1,13 @@ from django.urls import re_path from django.contrib.auth.decorators import permission_required -from rest_framework import routers, viewsets +from rest_framework import routers, viewsets, status from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated -from inventory.models import Event, Container, Item -from inventory.serializers import EventSerializer, ContainerSerializer, ItemSerializer, SearchResultSerializer +from inventory.models import Event, Container, Item, Comment +from inventory.serializers import EventSerializer, ContainerSerializer, CommentSerializer, ItemSerializer, \ + SearchResultSerializer from base64 import b64decode @@ -22,6 +23,17 @@ class ContainerViewSet(viewsets.ModelViewSet): 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): query_tokens = query.split(' ') for item in items: @@ -49,6 +61,7 @@ def search_items(request, event_slug, query): @api_view(['GET', 'POST']) @permission_classes([IsAuthenticated]) def item(request, event_slug): + vs = ItemViewSet() try: event = None if event_slug != 'none': @@ -56,7 +69,7 @@ def item(request, event_slug): if request.method == 'GET': if not request.user.has_event_perm(event, 'view_item'): return Response(status=403) - return Response(ItemSerializer(Item.objects.filter(event=event), many=True).data) + return Response(ItemSerializer(vs.get_queryset().filter(event=event), many=True).data) elif request.method == 'POST': if not request.user.has_event_perm(event, 'add_item'): return Response(status=403) @@ -71,12 +84,31 @@ def item(request, event_slug): 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']) @permission_classes([IsAuthenticated]) def item_by_id(request, event_slug, id): try: event = Event.objects.get(slug=event_slug) - item = Item.objects.get(event=event, id=id) + item = ItemViewSet().get_queryset().get(event=event, id=id) if request.method == 'GET': if not request.user.has_event_perm(event, 'view_item'): return Response(status=403) @@ -117,5 +149,6 @@ urlpatterns = router.urls + [ re_path(r'^(?P[\w-]+)/items/$', item, name='item'), re_path(r'^(?P[\w-]+)/items/(?P[-A-Za-z0-9+/]*={0,3})/$', search_items, name='search_items'), re_path(r'^(?P[\w-]+)/item/$', item, name='item'), + re_path(r'^(?P[\w-]+)/item/(?P\d+)/comment/$', add_comment, name='add_comment'), re_path(r'^(?P[\w-]+)/item/(?P\d+)/$', item_by_id, name='item_by_id'), ] diff --git a/core/inventory/serializers.py b/core/inventory/serializers.py index 63f479b..90a75aa 100644 --- a/core/inventory/serializers.py +++ b/core/inventory/serializers.py @@ -3,7 +3,7 @@ from rest_framework import serializers from rest_framework.relations import SlugRelatedField from files.models import File -from inventory.models import Event, Container, Item +from inventory.models import Event, Container, Item, Comment from inventory.shared_serializers import BasicItemSerializer from mail.models import EventAddress from tickets.shared_serializers import BasicIssueSerializer @@ -38,6 +38,18 @@ class ContainerSerializer(serializers.ModelSerializer): 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): timeline = serializers.SerializerMethodField() dataImage = serializers.CharField(write_only=True, required=False) @@ -48,6 +60,7 @@ class ItemSerializer(BasicItemSerializer): fields = ['cid', 'box', 'id', 'description', 'file', 'dataImage', 'returned', 'event', 'related_issues', 'timeline'] read_only_fields = ['id'] + prefetch_related_fields = ['comments', 'issue_relation_changes', 'container_history'] def to_internal_value(self, data): container = None diff --git a/core/tickets/api_v2.py b/core/tickets/api_v2.py index 415e045..99dc008 100644 --- a/core/tickets/api_v2.py +++ b/core/tickets/api_v2.py @@ -21,7 +21,13 @@ from tickets.shared_serializers import RelationSerializer class IssueViewSet(viewsets.ModelViewSet): 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): @@ -171,5 +177,5 @@ urlpatterns = ([ re_path(r'^tickets/(?P\d+)/comment/$', add_comment, name='add_comment'), re_path(r'^(?P[\w-]+)/tickets/manual/$', manual_ticket, name='manual_ticket'), re_path(r'^(?P[\w-]+)/tickets/(?P[-A-Za-z0-9+/]*={0,3})/$', search_issues, - name='search_issues'), + name='search_issues'), ] + router.urls) diff --git a/core/tickets/serializers.py b/core/tickets/serializers.py index b7d8b28..b382b38 100644 --- a/core/tickets/serializers.py +++ b/core/tickets/serializers.py @@ -47,6 +47,8 @@ class IssueSerializer(BasicIssueSerializer): model = IssueThread fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity', 'uuid', 'related_items', 'event') 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): ret = super().to_internal_value(data) @@ -63,12 +65,14 @@ class IssueSerializer(BasicIssueSerializer): @staticmethod def get_last_activity(self): try: - last_state_change = max([t.timestamp for t in self.state_changes.all()]) if self.state_changes.exists() else None + last_state_change = max( + [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_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_relation = max([t.timestamp for t in self.item_relation_changes.all()]) if self.item_relation_changes.exists() else None + last_relation = max([t.timestamp for t in + 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 x is not None] return max(args) @@ -129,7 +133,6 @@ class IssueSerializer(BasicIssueSerializer): return sorted(timeline, key=lambda x: x['timestamp']) - class SearchResultSerializer(serializers.Serializer): search_score = serializers.IntegerField() item = IssueSerializer() From 0ac6b808166a9db4d2e8c8ce9802d90f4c551fbf Mon Sep 17 00:00:00 2001 From: jedi Date: Wed, 27 Nov 2024 19:39:58 +0100 Subject: [PATCH 3/3] add basic view for item history --- web/src/App.vue | 13 +- web/src/components/AuthenticatedImage.vue | 28 ++- web/src/components/CollapsableCards.vue | 6 +- web/src/components/EditItem.vue | 17 +- web/src/components/Timeline.vue | 76 +++----- web/src/components/TimelineMail.vue | 39 +--- web/src/components/TimelinePlacement.vue | 85 +++++++++ web/src/components/TimelineRelatedItem.vue | 74 ++------ web/src/components/TimelineRelatedTicket.vue | 94 ++++++++++ web/src/components/inputs/InputCombo.vue | 61 +++---- web/src/router.js | 11 +- web/src/store.js | 30 ++- web/src/views/Item.vue | 181 +++++++++++++++++++ web/src/views/Items.vue | 54 ++---- web/src/views/Ticket.vue | 130 ++++++++++--- web/src/views/Tickets.vue | 20 +- 16 files changed, 612 insertions(+), 307 deletions(-) create mode 100644 web/src/components/TimelinePlacement.vue create mode 100644 web/src/components/TimelineRelatedTicket.vue create mode 100644 web/src/views/Item.vue diff --git a/web/src/App.vue b/web/src/App.vue index bd4956f..d2c9f7d 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,5 +1,6 @@ diff --git a/web/src/components/Timeline.vue b/web/src/components/Timeline.vue index 88bfa94..41e1274 100644 --- a/web/src/components/Timeline.vue +++ b/web/src/components/Timeline.vue @@ -21,6 +21,9 @@ + + + @@ -30,40 +33,15 @@ + +

    {{ item }}

  • - - - -
    -
    - - - - Save Comment - -
    -
    +
  • - - - -
    -
    - {{ newestMailSubject }} -
    -
    - - - - Send Mail - -
    -
    +
  • @@ -78,12 +56,20 @@ import TimelineAssignment from "@/components/TimelineAssignment.vue"; import TimelineRelatedItem from "@/components/TimelineRelatedItem.vue"; import TimelineShippingVoucher from "@/components/TimelineShippingVoucher.vue"; import AsyncButton from "@/components/inputs/AsyncButton.vue"; +import TimelinePlacement from "@/components/TimelinePlacement.vue"; +import TimelineRelatedTicket from "@/components/TimelineRelatedTicket.vue"; export default { name: 'Timeline', components: { - TimelineShippingVoucher, AsyncButton, - TimelineRelatedItem, TimelineAssignment, TimelineStateChange, TimelineComment, TimelineMail + TimelineRelatedTicket, + TimelinePlacement, + TimelineShippingVoucher, + TimelineRelatedItem, + TimelineAssignment, + TimelineStateChange, + TimelineComment, + TimelineMail }, props: { timeline: { @@ -91,33 +77,13 @@ export default { default: () => [] } }, - emits: ['sendMail', 'addComment'], - data: () => ({ - newMail: "", - newComment: "" - }), computed: { - ...mapGetters(['stateInfo']), - newestMailSubject() { - const mail = this.timeline.filter(item => item.type === 'mail').pop(); - return mail ? mail.subject : ""; - }, + ...mapGetters(['stateInfo']) }, - methods: { - ...mapActions(['sendMail', 'postComment']), - sendMailAndClear: async function () { - await this.sendMail(this.newMail); - this.newMail = ""; - }, - addCommentAndClear: async function () { - await this.postComment(this.newComment); - this.newComment = ""; - } - } }; - \ No newline at end of file diff --git a/web/src/components/TimelineRelatedItem.vue b/web/src/components/TimelineRelatedItem.vue index c17a3a8..2670215 100644 --- a/web/src/components/TimelineRelatedItem.vue +++ b/web/src/components/TimelineRelatedItem.vue @@ -1,12 +1,15 @@ @@ -72,16 +38,11 @@ import AuthenticatedImage from "@/components/AuthenticatedImage.vue"; import AuthenticatedDataLink from "@/components/AuthenticatedDataLink.vue"; -import Lightbox from "@/components/Lightbox.vue"; +import {mapMutations} from "vuex"; export default { name: 'TimelineRelatedItem', - components: {Lightbox, AuthenticatedImage, AuthenticatedDataLink}, - data() { - return { - lightboxHash: null, - } - }, + components: {AuthenticatedImage, AuthenticatedDataLink}, props: { 'item': { type: Object, @@ -98,13 +59,8 @@ export default { }, methods: { - openLightboxModalWith(attachment) { - this.lightboxHash = attachment.hash; - }, - closeLightboxModal() { // Closes the editing modal and discards the edited copy of the item. - this.lightboxHash = null; - }, - }, + ...mapMutations(['openLightboxModalWith']) + } }; diff --git a/web/src/components/TimelineRelatedTicket.vue b/web/src/components/TimelineRelatedTicket.vue new file mode 100644 index 0000000..694a4e0 --- /dev/null +++ b/web/src/components/TimelineRelatedTicket.vue @@ -0,0 +1,94 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/inputs/InputCombo.vue b/web/src/components/inputs/InputCombo.vue index 50b5459..fc64d42 100644 --- a/web/src/components/inputs/InputCombo.vue +++ b/web/src/components/inputs/InputCombo.vue @@ -1,39 +1,36 @@ diff --git a/web/src/router.js b/web/src/router.js index bad2970..eb059c8 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -1,10 +1,11 @@ import {createRouter, createWebHistory} from 'vue-router' import store from '@/store'; -import Items from './views/Items'; -import Boxes from './views/Boxes'; -import Files from './views/Files'; -import HowTo from './views/HowTo'; +import Item from "@/views/Item.vue"; +import Items from '@/views/Items'; +import Boxes from '@/views/Boxes'; +import Files from '@/views/Files'; +import HowTo from '@/views/HowTo'; import Login from '@/views/Login.vue'; import Register from '@/views/Register.vue'; import Dashboard from "@/views/admin/Dashboard.vue"; @@ -27,7 +28,7 @@ const routes = [ {requiresAuth: true, requiresPermission: 'view_item'} }, { - path: '/:event/item/:uid/', name: 'item', component: Items, meta: + path: '/:event/item/:id/', name: 'item', component: Item, meta: {requiresAuth: true, requiresPermission: 'view_item'} }, { diff --git a/web/src/store.js b/web/src/store.js index 60dba61..2f5a60d 100644 --- a/web/src/store.js +++ b/web/src/store.js @@ -34,6 +34,7 @@ const store = createStore({ expiry: null, }, + lightboxHash: null, thumbnailCache: {}, fetchedData: { events: 0, @@ -145,38 +146,34 @@ const store = createStore({ setItems(state, {slug, items}) { state.loadedItems[slug] = items; state.loadedItems = {...state.loadedItems}; - console.log(state.loadedItems) }, replaceItems(state, items) { const groups = Object.groupBy(items, i => i.event ? i.event : 'none') for (const [key, value] of Object.entries(groups)) state.loadedItems[key] = value; state.loadedItems = {...state.loadedItems}; - console.log(state.loadedItems) }, updateItem(state, updatedItem) { - const item = state.loadedItems[updatedItem.event?updatedItem.event:'none'].filter( - ({uid}) => uid === updatedItem.uid)[0]; + const item = state.loadedItems[updatedItem.event ? updatedItem.event : 'none'].filter( + ({id}) => id === updatedItem.id)[0]; Object.assign(item, updatedItem); }, removeItem(state, item) { - state.loadedItems[item.event?item.event:'none'] = state.loadedItems[item.event].filter(it => it !== item); + state.loadedItems[item.event ? item.event : 'none'] = state.loadedItems[item.event].filter(it => it !== item); }, appendItem(state, item) { - state.loadedItems[item.event?item.event:'none'].push(item); + state.loadedItems[item.event ? item.event : 'none'].push(item); }, setTickets(state, {slug, tickets}) { state.loadedTickets[slug] = tickets; state.loadedTickets = {...state.loadedTickets}; - console.log(state.loadedTickets) }, replaceTickets(state, tickets) { const groups = Object.groupBy(tickets, t => t.event ? t.event : 'none') for (const [key, value] of Object.entries(groups)) state.loadedTickets[key] = value; state.loadedTickets = {...state.loadedTickets}; - console.log(state.loadedTickets) }, updateTicket(state, updatedTicket) { - const ticket = state.loadedTickets[updatedTicket.event?updatedTicket.event:'none'].filter( + const ticket = state.loadedTickets[updatedTicket.event ? updatedTicket.event : 'none'].filter( ({id}) => id === updatedTicket.id)[0]; Object.assign(ticket, updatedTicket); state.loadedTickets = {...state.loadedTickets}; @@ -189,6 +186,9 @@ const store = createStore({ state.groups = groups; state.fetchedData = {...state.fetchedData, groups: Date.now()}; }, + openLightboxModalWith(state, hash) { + state.lightboxHash = hash; + }, openAddBoxModal(state) { state.showAddBoxModal = true; }, @@ -336,14 +336,13 @@ const store = createStore({ const {data, success} = await getters.session.delete(`/2/events/${event_id}/`); if (success) { await dispatch('loadEvents') - commit('replaceEvents', [...state.events.filter(e => e.eid !== event_id)]) + commit('replaceEvents', [...state.events.filter(e => e.id !== event_id)]) } }, async updateEvent({commit, dispatch, state}, {id, partial_event}){ - console.log(id, partial_event); const {data, success} = await http.patch(`/2/events/${id}/`, partial_event, state.user.token); if (success) { - commit('replaceEvents', [...state.events.filter(e => e.eid !== id), data]) + commit('replaceEvents', [...state.events.filter(e => e.id !== id), data]) } }, async fetchTicketStates({commit, state, getters}) { @@ -354,7 +353,6 @@ const store = createStore({ }, async changeEvent({dispatch, getters, commit}, eventName) { await router.push({path: `/${eventName.slug}/${getters.getActiveView}/`}); - //dispatch('loadEventItems'); }, async changeView({getters}, link) { await router.push({path: `/${getters.getEventSlug}/${link.path}/`}); @@ -405,16 +403,16 @@ const store = createStore({ async updateItem({commit, getters, state}, item) { const { data, success - } = await getters.session.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item); + } = await getters.session.put(`/2/${getters.getEventSlug}/item/${item.id}/`, item); commit('updateItem', data); }, async markItemReturned({commit, getters, state}, item) { - await getters.session.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}, + await getters.session.patch(`/2/${getters.getEventSlug}/item/${item.id}/`, {returned: true}, state.user.token); commit('removeItem', item); }, async deleteItem({commit, getters, state}, item) { - await getters.session.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item); + await getters.session.delete(`/2/${getters.getEventSlug}/item/${item.id}/`, item); commit('removeItem', item); }, async postItem({commit, getters, state}, item) { diff --git a/web/src/views/Item.vue b/web/src/views/Item.vue new file mode 100644 index 0000000..91cbb61 --- /dev/null +++ b/web/src/views/Item.vue @@ -0,0 +1,181 @@ + + + + + + diff --git a/web/src/views/Items.vue b/web/src/views/Items.vue index 2f9e5ed..21b11d1 100644 --- a/web/src/views/Items.vue +++ b/web/src/views/Items.vue @@ -1,26 +1,13 @@