From a4d4caf77cdd73be4a662483c485f0b733fca63e Mon Sep 17 00:00:00 2001
From: jedi <git@m.j3d1.de>
Date: Sun, 17 Nov 2024 00:16:54 +0100
Subject: [PATCH] show tickets filtered by active event

---
 core/core/settings.py         |   2 +
 core/tickets/serializers.py   |   2 -
 web/src/components/Navbar.vue |  17 +-
 web/src/store.js              | 288 ++++++++++++++++------------------
 web/src/utils.js              |  10 +-
 web/src/views/Items.vue       |  10 +-
 web/src/views/Ticket.vue      |  25 +--
 web/src/views/Tickets.vue     |  19 ++-
 8 files changed, 175 insertions(+), 198 deletions(-)

diff --git a/core/core/settings.py b/core/core/settings.py
index 2d4a818..6796112 100644
--- a/core/core/settings.py
+++ b/core/core/settings.py
@@ -15,9 +15,11 @@ import sys
 import dotenv
 from pathlib import Path
 
+
 def truthy_str(s):
     return s.lower() in ['true', '1', 't', 'y', 'yes', 'yeah', 'yup', 'certainly', 'sure', 'positive', 'uh-huh', '👍']
 
+
 # Build paths inside the project like this: BASE_DIR / 'subdir'.
 BASE_DIR = Path(__file__).resolve().parent.parent
 
diff --git a/core/tickets/serializers.py b/core/tickets/serializers.py
index f5b7ef6..9f14e7e 100644
--- a/core/tickets/serializers.py
+++ b/core/tickets/serializers.py
@@ -55,8 +55,6 @@ class IssueSerializer(serializers.ModelSerializer):
         ret = super().to_internal_value(data)
         if 'state' in data:
             ret['state'] = data['state']
-#        if 'assigned_to' in data:
-#            ret['assigned_to'] = data['assigned_to']
         return ret
 
     def validate(self, attrs):
diff --git a/web/src/components/Navbar.vue b/web/src/components/Navbar.vue
index eb7504b..ccfb0f0 100644
--- a/web/src/components/Navbar.vue
+++ b/web/src/components/Navbar.vue
@@ -49,12 +49,12 @@
                 </button>
             </div>
             <button type="button" class="btn text-nowrap btn-success mr-1" @click="$emit('addItemClicked')"
-                    v-if="isItemView()">
+                    v-if="isItemView()  && getEventSlug !== 'all'">
                 <font-awesome-icon icon="plus"/>
                 <span class="d-none d-md-inline">&nbsp;Add Item</span>
             </button>
             <button type="button" class="btn text-nowrap btn-success mr-1" @click="$emit('addTicketClicked')"
-                    v-if="isTicketView()">
+                    v-if="isTicketView() && getEventSlug !== 'all'">
                 <font-awesome-icon icon="plus"/>
                 <span class="d-none d-md-inline">&nbsp;Add Ticket</span>
             </button>
@@ -65,19 +65,6 @@
         </button>
         <div class="collapse navbar-collapse" id="navbarSupportedContent">
             <ul class="navbar-nav ml-auto">
-                <!--li class="nav-item dropdown">
-                    <button class="btn nav-link dropdown-toggle" type="button" id="dropdownMenuButton2"
-                            data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                        {{ getActiveView }}
-                    </button>
-                    <ul class="dropdown-menu bg-dark" aria-labelledby="dropdownMenuButton2">
-                        <li class="" v-for="(link, index) in views" v-bind:key="index"
-                            :class="{ active: link.path === getActiveView }">
-                            <a class="nav-link text-nowrap" href="#" @click="changeView(link)">{{ link.title }}</a>
-                        </li>
-                    </ul>
-                </li-->
-
                 <li class="nav-item" v-for="(link, index) in links" v-bind:key="index">
                     <a class="nav-link text-nowrap" :href="link.path" @click.prevent="navigateTo(link.path)">
                         {{ link.title }}
diff --git a/web/src/store.js b/web/src/store.js
index 3140a99..dcc3aea 100644
--- a/web/src/store.js
+++ b/web/src/store.js
@@ -3,7 +3,7 @@ import router from './router';
 
 import * as base64 from 'base-64';
 import * as utf8 from 'utf8';
-import {ticketStateColorLookup, ticketStateIconLookup, http} from "@/utils";
+import {ticketStateColorLookup, ticketStateIconLookup, http, http_session} from "@/utils";
 import sharedStatePlugin from "@/shared-state-plugin";
 import persistentStatePlugin from "@/persistent-state-plugin";
 
@@ -11,16 +11,17 @@ const store = createStore({
     state: {
         keyIncrement: 0,
         events: [],
-        loadedItems: [],
-        itemCache: {},
+        items: [],
         loadedBoxes: [],
         toasts: [],
-        tickets: [],
         users: [],
         groups: [],
         state_options: [],
         shippingVouchers: [],
 
+        loadedItems: {},
+        loadedTickets: {},
+
         lastEvent: 'all',
         lastUsed: {},
         searchQuery: '',
@@ -62,7 +63,14 @@ const store = createStore({
     },
     getters: {
         route: state => router.currentRoute.value,
+        session: state => http_session(state.user.token),
         getEventSlug: state => router.currentRoute.value.params.event ? router.currentRoute.value.params.event : state.lastEvent,
+        getAllItems: state => Object.values(state.loadedItems).flat(),
+        getAllTickets: state => Object.values(state.loadedTickets).flat(),
+        getEventItems: (state, getters) => getters.getEventSlug === 'all' ? getters.getAllItems : getters.getAllItems.filter(t => t.event === getters.getEventSlug || (t.event == null && getters.getEventSlug === 'none')),
+        getEventTickets: (state, getters) => getters.getEventSlug === 'all' ? getters.getAllTickets : getters.getAllTickets.filter(t => t.event === getters.getEventSlug || (t.event == null && getters.getEventSlug === 'none')),
+        isItemsLoaded: (state, getters) => (getters.getEventSlug === 'all' || getters.getEventSlug === 'none') ? !!state.loadedItems : Object.keys(state.loadedItems).includes(getters.getEventSlug),
+        isTicketsLoaded: (state, getters) => (getters.getEventSlug === 'all' || getters.getEventSlug === 'none') ? !!state.loadedTickets : Object.keys(state.loadedTickets).includes(getters.getEventSlug),
         getActiveView: state => router.currentRoute.value.name || 'items',
         getFilters: state => router.currentRoute.value.query,
         getBoxes: state => state.loadedBoxes,
@@ -130,35 +138,48 @@ const store = createStore({
         changeView(state, {view, slug}) {
             router.push({path: `/${slug}/${view}`});
         },
-        replaceLoadedItems(state, newItems) {
-            state.loadedItems = newItems;
-            state.fetchedData = {...state.fetchedData, items: Date.now()}; // TODO: manage caching items for different events and search results correctly
-        },
-        setItemCache(state, {slug, items}) {
-            state.itemCache[slug] = items;
-        },
         replaceBoxes(state, loadedBoxes) {
             state.loadedBoxes = loadedBoxes;
             state.fetchedData = {...state.fetchedData, boxes: Date.now()};
         },
+        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.filter(({uid}) => uid === updatedItem.uid)[0];
+            const item = state.loadedItems[updatedItem.event?updatedItem.event:'none'].filter(
+                ({uid}) => uid === updatedItem.uid)[0];
             Object.assign(item, updatedItem);
         },
         removeItem(state, item) {
-            state.loadedItems = state.loadedItems.filter(it => it !== item);
+            state.loadedItems[item.event?item.event:'none'] = state.loadedItems[item.event].filter(it => it !== item);
         },
         appendItem(state, item) {
-            state.loadedItems.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) {
-            state.tickets = tickets;
-            state.fetchedData = {...state.fetchedData, tickets: Date.now()};
+            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.tickets.filter(({id}) => id === updatedTicket.id)[0];
+            const ticket = state.loadedTickets[updatedTicket.event?updatedTicket.event:'none'].filter(
+                ({id}) => id === updatedTicket.id)[0];
             Object.assign(ticket, updatedTicket);
-            state.tickets = [...state.tickets];
+            state.loadedTickets = {...state.loadedTickets};
         },
         replaceUsers(state, users) {
             state.users = users;
@@ -249,7 +270,7 @@ const store = createStore({
                 return false;
             }
         },
-        async reloadToken({commit, state, getters}) {
+        async reloadToken({commit, state}) {
             try {
                 if (state.user.username && state.user.password) {
                     const data = await fetch('/api/2/login/', {
@@ -297,59 +318,51 @@ const store = createStore({
         async fetchImage({state}, url) {
             return await fetch(url, {headers: {'Authorization': `Token ${state.user.token}`}});
         },
-        async loadUserInfo({commit, state}) {
-            const {data, success} = await http.get('/2/self/', state.user.token);
+        async loadUserInfo({commit, getters}) {
+            const {data, success} = await getters.session.get('/2/self/');
             commit('setPermissions', data.permissions);
         },
-        async loadEvents({commit, state}) {
+        async loadEvents({commit, state, getters}) {
             if (!state.user.token) return;
             if (state.fetchedData.events > Date.now() - 1000 * 60 * 60 * 24) return;
-            const {data, success} = await http.get('/2/events/', state.user.token);
-            if (data && success)
-                commit('replaceEvents', data);
+            const {data, success} = await getters.session.get('/2/events/');
+            if (data && success) commit('replaceEvents', data);
         },
-        async createEvent({commit, dispatch, state}, event) {
-            const {data, success} = await http.post('/2/events/', event, state.user.token);
-            if (data && success)
-                commit('replaceEvents', [...state.events, data]);
+        async createEvent({commit, dispatch, state, getters}, event) {
+            const {data, success} = await getters.session.post('/2/events/', event);
+            if (data && success) commit('replaceEvents', [...state.events, data]);
         },
-        async deleteEvent({commit, dispatch, state}, event_id) {
-            const {data, success} = await http.delete(`/2/events/${event_id}/`, state.user.token);
+        async deleteEvent({commit, dispatch, state, getters}, event_id) {
+            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)])
             }
         },
-        async fetchTicketStates({commit, state}) {
+        async fetchTicketStates({commit, state, getters}) {
             if (!state.user.token) return;
             if (state.fetchedData.states > Date.now() - 1000 * 60 * 60 * 24) return;
-            const {data, success} = await http.get('/2/tickets/states/', state.user.token);
-            if (data && success)
-                commit('replaceTicketStates', data);
+            const {data, success} = await getters.session.get('/2/tickets/states/');
+            if (data && success) commit('replaceTicketStates', data);
         },
-        changeEvent({dispatch, getters, commit}, eventName) {
-            router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
-            dispatch('loadEventItems');
+        async changeEvent({dispatch, getters, commit}, eventName) {
+            await router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
+            //dispatch('loadEventItems');
         },
-        changeView({getters}, link) {
-            router.push({path: `/${getters.getEventSlug}/${link.path}/`});
+        async changeView({getters}, link) {
+            await router.push({path: `/${getters.getEventSlug}/${link.path}/`});
         },
-        showBoxContent({getters}, box) {
-            router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
+        async showBoxContent({getters}, box) {
+            await router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
         },
         async loadEventItems({commit, getters, state}) {
             if (!state.user.token) return;
             if (state.fetchedData.items > Date.now() - 1000 * 60 * 60 * 24) return;
             try {
-                commit('replaceLoadedItems', []);
                 const slug = getters.getEventSlug;
-                if (slug in state.itemCache) {
-                    commit('replaceLoadedItems', state.itemCache[slug]);
-                }
-                const {data, success} = await http.get(`/2/${slug}/items/`, state.user.token);
+                const {data, success} = await getters.session.get(`/2/${slug}/items/`);
                 if (data && success) {
-                    commit('replaceLoadedItems', data);
-                    commit('setItemCache', {slug, items: data});
+                    commit('setItems', {slug, items: data});
                 }
             } catch (e) {
                 console.error("Error loading items");
@@ -357,131 +370,124 @@ const store = createStore({
         },
         async searchEventItems({commit, getters, state}, query) {
             const encoded_query = base64.encode(utf8.encode(query));
-
-            const {data, success} = await http.get(`/2/${getters.getEventSlug}/items/${encoded_query}/`,
-                state.user.token);
-            if (data && success)
-                commit('replaceLoadedItems', data);
+            const slug = getters.getEventSlug;
+            const {
+                data, success
+            } = await getters.session.get(`/2/${slug}/items/${encoded_query}/`);
+            if (data && success) {
+                commit('setItems', {slug, items: data});
+            }
         },
-        async loadBoxes({commit, state}) {
+        async loadBoxes({commit, state, getters}) {
             if (!state.user.token) return;
             if (state.fetchedData.boxes > Date.now() - 1000 * 60 * 60 * 24) return;
-            const {data, success} = await http.get('/2/boxes/', state.user.token);
-            if (data && success)
-                commit('replaceBoxes', data);
+            const {data, success} = await getters.session.get('/2/boxes/');
+            if (data && success) commit('replaceBoxes', data);
         },
-        async createBox({commit, dispatch, state}, box) {
-            const {data, success} = await http.post('/2/boxes/', box, state.user.token);
+        async createBox({commit, dispatch, state, getters}, box) {
+            const {data, success} = await getters.session.post('/2/boxes/', box);
             commit('replaceBoxes', data);
             dispatch('loadBoxes').then(() => {
                 commit('closeAddBoxModal');
             });
         },
-        async deleteBox({commit, dispatch, state}, box_id) {
-            await http.delete(`/2/boxes/${box_id}/`, state.user.token);
+        async deleteBox({commit, dispatch, state, getters}, box_id) {
+            await getters.session.delete(`/2/boxes/${box_id}/`);
             dispatch('loadBoxes');
         },
         async updateItem({commit, getters, state}, item) {
             const {
-                data,
-                success
-            } = await http.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
+                data, success
+            } = await getters.session.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
             commit('updateItem', data);
         },
         async markItemReturned({commit, getters, state}, item) {
-            await http.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}, state.user.token);
+            await getters.session.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true},
+                state.user.token);
             commit('removeItem', item);
         },
         async deleteItem({commit, getters, state}, item) {
-            await http.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
+            await getters.session.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
             commit('removeItem', item);
         },
         async postItem({commit, getters, state}, item) {
             commit('updateLastUsed', {box: item.box, cid: item.cid});
-            const {data, success} = await http.post(`/2/${getters.getEventSlug}/item/`, item, state.user.token);
+            const {data, success} = await getters.session.post(`/2/${getters.getEventSlug}/item/`, item);
             commit('appendItem', data);
         },
-        async loadTickets({commit, state}) {
+        async loadTickets({commit, state, getters}) {
             if (!state.user.token) return;
-            if (state.fetchedData.tickets > Date.now() - 1000 * 60 * 60 * 24) return;
-            const {data, success} = await http.get('/2/tickets/', state.user.token);
-            if (data && success)
-                commit('replaceTickets', data);
+            //if (state.fetchedData.tickets > Date.now() - 1000 * 60 * 60 * 24) return;
+            const {data, success} = await getters.session.get('/2/tickets/');
+            if (data && success) commit('replaceTickets', data);
         },
         async searchEventTickets({commit, getters, state}, query) {
             const encoded_query = base64.encode(utf8.encode(query));
 
-            const {data, success} = await http.get(`/2/${getters.getEventSlug}/tickets/${encoded_query}/`,
-                state.user.token);
-            if (data && success)
-                commit('replaceTickets', data);
+            const {
+                data, success
+            } = await getters.session.get(`/2/${getters.getEventSlug}/tickets/${encoded_query}/`);
+            if (data && success) commit('replaceTickets', data);
         },
-        async sendMail({commit, dispatch, state}, {id, message}) {
-            const {data, success} = await http.post(`/2/tickets/${id}/reply/`, {message}, state.user.token);
+        async sendMail({commit, dispatch, state, getters}, {id, message}) {
+            const {data, success} = await getters.session.post(`/2/tickets/${id}/reply/`, {message},
+                state.user.token);
             if (data && success) {
                 state.fetchedData.tickets = 0;
                 await dispatch('loadTickets');
             }
         },
-        async postManualTicket({commit, dispatch, state}, {sender, message, title,}) {
-            const {data, success} = await http.post(`/2/tickets/manual/`, {
-                name: title,
-                sender,
-                body: message,
-                recipient: 'mail@c3lf.de'
-            }, state.user.token);
+        async postManualTicket({commit, dispatch, state, getters}, {sender, message, title,}) {
+            const {data, success} = await getters.session.post(`/2/tickets/manual/`, {
+                name: title, sender, body: message, recipient: 'mail@c3lf.de'
+            });
             await dispatch('loadTickets');
         },
-        async postComment({commit, dispatch, state}, {id, message}) {
-            const {data, success} = await http.post(`/2/tickets/${id}/comment/`, {comment: message}, state.user.token);
+        async postComment({commit, dispatch, state, getters}, {id, message}) {
+            const {data, success} = await getters.session.post(`/2/tickets/${id}/comment/`, {comment: message});
             if (data && success) {
                 state.fetchedData.tickets = 0;
                 await dispatch('loadTickets');
             }
         },
-        async loadUsers({commit, state}) {
+        async loadUsers({commit, state, getters}) {
             if (!state.user.token) return;
             if (state.fetchedData.users > Date.now() - 1000 * 60 * 60 * 24) return;
-            const {data, success} = await http.get('/2/users/', state.user.token);
-            if (data && success)
-                commit('replaceUsers', data);
+            const {data, success} = await getters.session.get('/2/users/');
+            if (data && success) commit('replaceUsers', data);
         },
-        async loadGroups({commit, state}) {
+        async loadGroups({commit, state, getters}) {
             if (!state.user.token) return;
             if (state.fetchedData.groups > Date.now() - 1000 * 60 * 60 * 24) return;
-            const {data, success} = await http.get('/2/groups/', state.user.token);
-            if (data && success)
-                commit('replaceGroups', data);
+            const {data, success} = await getters.session.get('/2/groups/');
+            if (data && success) commit('replaceGroups', data);
         },
-        async updateTicket({commit, state}, ticket) {
-            const {data, success} = await http.put(`/2/tickets/${ticket.id}/`, ticket, state.user.token);
+        async updateTicket({commit, state, getters}, ticket) {
+            const {data, success} = await getters.session.put(`/2/tickets/${ticket.id}/`, ticket);
             commit('updateTicket', data);
         },
-        async updateTicketPartial({commit, state}, {id, ...ticket}) {
-            const {data, success} = await http.patch(`/2/tickets/${id}/`, ticket, state.user.token);
+        async updateTicketPartial({commit, state, getters}, {id, ...ticket}) {
+            const {data, success} = await getters.session.patch(`/2/tickets/${id}/`, ticket);
             commit('updateTicket', data);
         },
-        async fetchShippingVouchers({commit, state}) {
+        async fetchShippingVouchers({commit, state, getters}) {
             if (!state.user.token) return;
             if (state.fetchedData.shippingVouchers > Date.now() - 1000 * 60 * 60 * 24) return;
-            const {data, success} = await http.get('/2/shipping_vouchers/', state.user.token);
+            const {data, success} = await getters.session.get('/2/shipping_vouchers/');
             if (data && success) {
                 commit('setShippingVouchers', data);
             }
         },
-        async createShippingVoucher({dispatch, state}, code) {
-            const {data, success} = await http.post('/2/shipping_vouchers/', code, state.user.token);
+        async createShippingVoucher({dispatch, state, getters}, code) {
+            const {data, success} = await getters.session.post('/2/shipping_vouchers/', code);
             if (data && success) {
                 state.fetchedData.shippingVouchers = 0;
                 dispatch('fetchShippingVouchers');
             }
         },
-        async claimShippingVoucher({dispatch, state}, {ticket, shipping_voucher_type}) {
+        async claimShippingVoucher({dispatch, state, getters}, {ticket, shipping_voucher_type}) {
             const id = state.shippingVouchers.filter(voucher => voucher.type === shipping_voucher_type && voucher.issue_thread === null)[0].id;
-            const {
-                data,
-                success
-            } = await http.patch(`/2/shipping_vouchers/${id}/`, {issue_thread: ticket}, state.user.token);
+            const {data, success} = await getters.session.patch(`/2/shipping_vouchers/${id}/`, {issue_thread: ticket});
             if (data && success) {
                 state.fetchedData.shippingVouchers = 0;
                 state.fetchedData.tickets = 0;
@@ -489,58 +495,30 @@ const store = createStore({
             }
         }
     },
-    plugins: [
-        persistentStatePlugin({ // TODO change remember to some kind of enable field
-            prefix: "lf_",
-            debug: false,
-            isLoadedKey: "persistent_loaded",
-            state: [
-                "remember",
-                "user",
-                "events",
-                "lastUsed",
-            ]
-        }),
-        sharedStatePlugin({
-            debug: false,
-            isLoadedKey: "shared_loaded",
-            clearingMutation: "logout",
-            afterInit: "afterSharedInit",
-            state: [
-                "test",
-                "state_options",
-                "fetchedData",
-                "tickets",
-                "users",
-                "groups",
-                "loadedBoxes",
-                "loadedItems",
-                "shippingVouchers",
-            ],
-            watch: [
-                "test",
-                "state_options",
-                "fetchedData",
-                "tickets",
-                "users",
-                "groups",
-                "loadedBoxes",
-                "loadedItems",
-                "shippingVouchers",
-            ],
-            mutations: [
-                //"replaceTickets",
-            ],
-        }),
-    ],
+    plugins: [persistentStatePlugin({ // TODO change remember to some kind of enable field
+        prefix: "lf_",
+        debug: false,
+        isLoadedKey: "persistent_loaded",
+        state: ["remember", "user", "events", "lastUsed",]
+    }), sharedStatePlugin({
+        debug: false,
+        isLoadedKey: "shared_loaded",
+        clearingMutation: "logout",
+        afterInit: "afterSharedInit",
+        state: ["test", "state_options", "fetchedData", "loadedItems", "users", "groups", "loadedBoxes", "loadedTickets", "shippingVouchers",],
+        watch: ["test", "state_options", "fetchedData", "loadedItems", "users", "groups", "loadedBoxes", "loadedTickets", "shippingVouchers",],
+        mutations: [//"replaceTickets",
+        ],
+    }),],
 });
 
 store.watch((state) => state.user, (user) => {
     if (store.getters.isLoggedIn) {
-        if (router.currentRoute.value.name === 'login' && router.currentRoute.value.query.redirect)
+        if (router.currentRoute.value.name === 'login' && router.currentRoute.value.query.redirect) {
             router.push(router.currentRoute.value.query.redirect);
-        else if (router.currentRoute.value.name === 'login')
+        } else if (router.currentRoute.value.name === 'login') {
             router.push('/');
+        }
     } else {
         if (router.currentRoute.value.name !== 'login') {
             router.push({
diff --git a/web/src/utils.js b/web/src/utils.js
index 5910521..ebc911a 100644
--- a/web/src/utils.js
+++ b/web/src/utils.js
@@ -100,4 +100,12 @@ const http = {
     }
 }
 
-export {ticketStateColorLookup, ticketStateIconLookup, http};
\ No newline at end of file
+const http_session = token => ({
+    get: async (url) => await http.get(url, token),
+    post: async (url, data) => await http.post(url, data, token),
+    put: async (url, data) => await http.put(url, data, token),
+    patch: async (url, data) => await http.patch(url, data, token),
+    delete: async (url) => await http.delete(url, token),
+});
+
+export {ticketStateColorLookup, ticketStateIconLookup, http, http_session};
\ No newline at end of file
diff --git a/web/src/views/Items.vue b/web/src/views/Items.vue
index 5abcf5d..2f9e5ed 100644
--- a/web/src/views/Items.vue
+++ b/web/src/views/Items.vue
@@ -1,5 +1,5 @@
 <template>
-    <AsyncLoader :loaded="loadedItems.length > 0">
+    <AsyncLoader :loaded="isItemsLoaded">
         <div class="container-fluid px-xl-5 mt-3">
             <Modal title="Edit Item" v-if="editingItem" @close="closeEditingModal()">
                 <template #body>
@@ -18,7 +18,7 @@
                 <div class="col-xl-8 offset-xl-2">
                     <Table
                         :columns="['uid', 'description', 'box']"
-                        :items="loadedItems"
+                        :items="getEventItems"
                         :keyName="'uid'"
                         @itemActivated="openLightboxModalWith($event)"
                     >
@@ -44,7 +44,7 @@
             <Cards
                 v-if="layout === 'cards'"
                 :columns="['uid', 'description', 'box']"
-                :items="loadedItems"
+                :items="getEventItems"
                 :keyName="'uid'"
                 v-slot="{ item }"
                 @itemActivated="openLightboxModalWith($event)"
@@ -97,8 +97,8 @@ export default {
     }),
     components: {AsyncLoader, AuthenticatedImage, Lightbox, Table, Cards, Modal, EditItem},
     computed: {
-        ...mapState(['loadedItems']),
-        ...mapGetters(['layout']),
+        ...mapState([]),
+        ...mapGetters(['getEventItems', 'isItemsLoaded', 'layout']),
     },
     methods: {
         ...mapActions(['deleteItem', 'markItemReturned', 'loadEventItems', 'updateItem', 'scheduleAfterInit']),
diff --git a/web/src/views/Ticket.vue b/web/src/views/Ticket.vue
index f666ee8..d08fb2e 100644
--- a/web/src/views/Ticket.vue
+++ b/web/src/views/Ticket.vue
@@ -80,11 +80,11 @@ export default {
         }
     },
     computed: {
-        ...mapState(['tickets', 'state_options', 'users']),
-        ...mapGetters(['availableShippingVoucherTypes']),
+        ...mapState(['state_options', 'users']),
+        ...mapGetters(['availableShippingVoucherTypes', 'getAllTickets', 'route']),
         ticket() {
-            const id = parseInt(this.$route.params.id)
-            const ret = this.tickets.find(ticket => ticket.id === id);
+            const id = parseInt(this.route.params.id)
+            const ret = this.getAllTickets.find(ticket => ticket.id === id);
             return ret ? ret : {};
         },
         shippingEmail() {
@@ -124,14 +124,15 @@ export default {
         },
     },
     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.ticket)
-			};
-			this.selected_state = this.ticket.state;
-			this.selected_assignee = this.ticket.assigned_to
-		})]);
+        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.ticket)
+            }
+            ;
+            this.selected_state = this.ticket.state;
+            this.selected_assignee = this.ticket.assigned_to
+        })]);
     }
 };
 </script>
diff --git a/web/src/views/Tickets.vue b/web/src/views/Tickets.vue
index 5e971a0..bc288f1 100644
--- a/web/src/views/Tickets.vue
+++ b/web/src/views/Tickets.vue
@@ -1,11 +1,12 @@
 <template>
-    <AsyncLoader :loaded="tickets.length > 0">
+    <AsyncLoader :loaded="isTicketsLoaded">
         <div class="container-fluid px-xl-5 mt-3">
             <div class="row">
                 <div class="col-xl-8 offset-xl-2">
                     <Table
-                        :columns="['id', 'name', 'state', 'last_activity', 'assigned_to', 'actions', 'actions2']"
-                        :items="tickets.map(formatTicket)"
+                        :columns="['id', 'name', 'state', 'last_activity', 'assigned_to',
+                                    ...(getEventSlug==='all'?['event']:[])]"
+                        :items="getEventTickets.map(formatTicket)"
                         :keyName="'id'"
                         v-if="layout === 'table'"
                     >
@@ -21,8 +22,9 @@
                     </Table>
                 </div>
             </div>
-            <CollapsableCards v-if="layout === 'tasks'" :items="tickets"
-                              :columns="['id', 'name', 'last_activity', 'assigned_to']"
+            <CollapsableCards v-if="layout === 'tasks'" :items="getEventTickets"
+                              :columns="['id', 'name', 'last_activity', 'assigned_to',
+                                        ...(getEventSlug==='all'?['event']:[])]"
                               :keyName="'state'" :sections="['pending_new', 'pending_open','pending_shipping',
                           'pending_physical_confirmation','pending_return','pending_postponed'].map(stateInfo)">
                 <template #section_header="{index, section, count}">
@@ -34,6 +36,7 @@
                         <td>{{ item.name }}</td>
                         <td>{{ item.last_activity }}</td>
                         <td>{{ item.assigned_to }}</td>
+                        <td v-if="getEventSlug==='all'">{{ item.event }}</td>
                         <td>
                             <div class="btn-group">
                                 <a class="btn btn-primary" :href="'/'+ getEventSlug + '/ticket/' + item.id" title="view"
@@ -64,8 +67,7 @@ export default {
     name: 'Tickets',
     components: {AsyncLoader, Lightbox, Table, Cards, Modal, EditItem, CollapsableCards},
     computed: {
-        ...mapState(['tickets']),
-        ...mapGetters(['stateInfo', 'getEventSlug', 'layout']),
+        ...mapGetters(['getEventTickets', 'isTicketsLoaded', 'stateInfo', 'getEventSlug', 'layout']),
     },
     methods: {
         ...mapActions(['loadTickets', 'fetchTicketStates', 'scheduleAfterInit']),
@@ -79,7 +81,8 @@ export default {
                 state: this.stateInfo(ticket.state).text,
                 stateColor: this.stateInfo(ticket.state).color,
                 last_activity: ticket.last_activity,
-                assigned_to: ticket.assigned_to
+                assigned_to: ticket.assigned_to,
+                event: ticket.event
             };
         }
     },