Add "My Invoices" feature with table and pagination components
Introduced a new "My Invoices" section in the user dashboard, including an InvoiceCollectionsTable for managing invoices and a pagination component. Updated navigation menus, routing, and associated utilities to support this feature.
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 111 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,305 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, defineProps, defineEmits, watch, computed, onMounted, Component} from 'vue';
|
||||
import '@creativebulma/bulma-divider/dist/bulma-divider.min.css';
|
||||
const props = defineProps({
|
||||
color_class: {
|
||||
type: String,
|
||||
default: "has-text-grey"
|
||||
},
|
||||
is_loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon_class: {
|
||||
type: String,
|
||||
default: "fas fa-circle"
|
||||
},
|
||||
error_icon_class: {
|
||||
type: String,
|
||||
default: "fas fa-exclamation-triangle"
|
||||
},
|
||||
error_class: {
|
||||
type: String,
|
||||
default: "has-text-danger"
|
||||
},
|
||||
error: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
error_message: {
|
||||
type: String,
|
||||
default: "An error occurred"
|
||||
},
|
||||
dropdown_direction: {
|
||||
type: String,
|
||||
default: "down" // Alternative values: "up", "left", "right"
|
||||
},
|
||||
loading_message: {
|
||||
type: String,
|
||||
default: "Loading..."
|
||||
},
|
||||
loading_icon_class: {
|
||||
type: String,
|
||||
default: "fas fa-circle-notch"
|
||||
},
|
||||
loading_timeout_ms: {
|
||||
type: Number,
|
||||
default: 0 // 5 seconds
|
||||
},
|
||||
dropdown_content: {
|
||||
type: Object as () => {
|
||||
title: string|null;
|
||||
buttons_title: string|null;
|
||||
content: Array<{
|
||||
text: string;
|
||||
action: () => void;
|
||||
icon?: string;
|
||||
classes?: Array<string>;
|
||||
button?: boolean;
|
||||
button_text?: string;
|
||||
button_classes?: Array<string>;
|
||||
disabled?: boolean;
|
||||
v_centered?: boolean;
|
||||
}>;
|
||||
buttons: Array<{
|
||||
text: string;
|
||||
action: () => void;
|
||||
icon?: string;
|
||||
classes?: Array<string>;
|
||||
button_text?: string;
|
||||
button_classes?: Array<string>;
|
||||
disabled?: boolean;
|
||||
v_centered?: boolean;
|
||||
}>;
|
||||
},
|
||||
default: () => ({
|
||||
title: null,
|
||||
content: [
|
||||
/**
|
||||
{ text: "Item #1", action: () => {}, icon: "fas fa-circle-notch", class: "has-text-info" },
|
||||
{ text: "Item #2", action: () => {}, icon: "fas fa-circle-notch", class: "has-text-info" },
|
||||
{ text: "Item #3", action: () => {}, icon: "fas fa-circle-notch", class: "has-text-info" }
|
||||
*/
|
||||
],
|
||||
buttons_title: null,
|
||||
buttons: [
|
||||
/**
|
||||
{ text: "Example button #1", action: () => {}, icon: "fas fa-circle-notch", class: ["has-text-info", "is-small"], button_text: "Button #1", disabled: false },
|
||||
{ text: "Example button #2", action: () => {}, icon: "fas fa-circle-notch", class: "has-text-info" },
|
||||
{ text: "Example button #3", action: () => {}, icon: "fas fa-circle-notch", class: "has-text-info" }
|
||||
*/
|
||||
]
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(['onClick', 'onHover', 'onLeave', 'onScroll', 'onCopyToClipboard', 'onLoadingTimeout']);
|
||||
|
||||
const is_currently_hovered = ref(false);
|
||||
|
||||
const onClick = () => {
|
||||
emits('onClick');
|
||||
};
|
||||
const onHover = () => {
|
||||
is_currently_hovered.value = true;
|
||||
emits('onHover');
|
||||
};
|
||||
const onLeave = () => {
|
||||
is_currently_hovered.value = false;
|
||||
emits('onLeave');
|
||||
};
|
||||
const onScroll = () => {
|
||||
emits('onScroll');
|
||||
};
|
||||
const onLoadingTimeout = () => {
|
||||
// Check if the element is still loading
|
||||
if (props.is_loading) {
|
||||
console.warn('Loading timeout reached, but the element is still loading.');
|
||||
// If it is still loading, emit the loading timeout event
|
||||
emits('onLoadingTimeout');
|
||||
}
|
||||
}
|
||||
const copyToClipboard = (text) => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
emits('onCopyToClipboard', text);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
// Watch for scrolling
|
||||
watch(() => props.is_loading, (newValue) => {
|
||||
if (newValue) {
|
||||
window.addEventListener('scroll', onScroll);
|
||||
} else {
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for loading timeout
|
||||
watch(() => props.loading_timeout_ms, (newValue) => {
|
||||
if (newValue > 0) {
|
||||
setTimeout(() => {
|
||||
onLoadingTimeout();
|
||||
}, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for loading state
|
||||
watch(() => props.is_loading, (newValue) => {
|
||||
if (newValue) {
|
||||
// If loading, set a timeout to emit the loading timeout event
|
||||
setTimeout(() => {
|
||||
onLoadingTimeout();
|
||||
}, props.loading_timeout_ms);
|
||||
}
|
||||
});
|
||||
|
||||
// Start the loading timeout when the component is mounted
|
||||
onMounted(() => {
|
||||
if (props.is_loading && props.loading_timeout_ms > 0) {
|
||||
setTimeout(() => {
|
||||
onLoadingTimeout();
|
||||
}, props.loading_timeout_ms);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="color-indicator" @click="onClick" @mouseover="onHover" @mouseleave="onLeave">
|
||||
<span class="icon is-small">
|
||||
<!-- Loading -->
|
||||
<template v-if="is_loading">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</template>
|
||||
<!-- Error state -->
|
||||
<template v-else-if="error">
|
||||
<i :class="[error_icon_class, error_class]"></i>
|
||||
</template>
|
||||
<!-- Not loading and not hovered -->
|
||||
<template v-else-if="!is_loading && !is_currently_hovered">
|
||||
<i :class="[icon_class, color_class]"></i>
|
||||
</template>
|
||||
<!-- Not loading and hovered -->
|
||||
<template v-else-if="!is_loading && is_currently_hovered">
|
||||
<i :class="[icon_class, color_class]"></i>
|
||||
</template>
|
||||
<!-- Loading and hovered -->
|
||||
<template v-else-if="is_loading && is_currently_hovered">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</template>
|
||||
<!-- Not loading and hovered -->
|
||||
<template v-else-if="!is_loading && is_currently_hovered">
|
||||
<i :class="[icon_class, color_class]"></i>
|
||||
</template>
|
||||
<!-- Not loading and not hovered -->
|
||||
<template v-else-if="!is_loading && !is_currently_hovered">
|
||||
<i :class="[icon_class, color_class]"></i>
|
||||
</template>
|
||||
</span>
|
||||
<!-- Dropdown menu -->
|
||||
<div class="dropdown" :class="{ 'is-active': is_currently_hovered, 'is-up': dropdown_direction === 'up', 'is-left': dropdown_direction === 'left', 'is-right': dropdown_direction === 'right' }">
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-content" v-if="is_currently_hovered">
|
||||
<!-- Error message, with copy button -->
|
||||
<template v-if="error">
|
||||
<div class="dropdown-item has-text-danger">
|
||||
<span>
|
||||
<span class="is-pulled-right">
|
||||
<button class="button is-small is-light" @click="() => { copyToClipboard(error_message) }">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</span>
|
||||
<span class="has-text-left">
|
||||
<strong>Error:</strong>
|
||||
<br />
|
||||
{{ error_message }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Loading message -->
|
||||
<template v-else-if="is_loading">
|
||||
<div class="dropdown-item has-text-info">
|
||||
<strong>Loading...</strong>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Normal state -->
|
||||
<template v-else>
|
||||
<!-- Dropdown content -->
|
||||
<template v-if="dropdown_content && dropdown_content.content">
|
||||
<!-- Dropdown label -->
|
||||
<template v-if="dropdown_content.title">
|
||||
<div class="dropdown-item has-text-info">
|
||||
<strong>{{ dropdown_content.title }}</strong>
|
||||
</div>
|
||||
<!-- Divider -->
|
||||
<hr class="dropdown-divider" />
|
||||
</template>
|
||||
<template v-for="(item, index) in dropdown_content.content" :key="index">
|
||||
<!-- Add divider if not the first item -->
|
||||
<hr class="dropdown-divider" v-if="index > 0" />
|
||||
<!-- Dropdown item -->
|
||||
<div class="dropdown-item">
|
||||
<div class="columns is-mobile" :class="{ 'is-vcentered': item.v_centered }">
|
||||
<!-- Optional text -->
|
||||
<div class="column is-auto-fill">
|
||||
<span class="is-pulled-left">
|
||||
{{ item.text }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Optional button -->
|
||||
<div class="column is-auto-fill" v-if="item.button">
|
||||
<span class="is-pulled-right">
|
||||
<button class="button is-small is-light" @click="() => { item.action() }" v-bind:disabled="item.disabled" v-bind:class="item.button_classes">
|
||||
<!-- Optional button icon -->
|
||||
<span class="icon" v-if="item.icon"><i :class="[item.icon, (item.classes ? item.classes : [])]"></i></span>
|
||||
<!-- Optional button text, breaks lines if too long -->
|
||||
<span v-if="item.button_text" v-bind:class="{ 'ml-2': item.icon }">{{ item.button_text }}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<!-- Dropdown buttons -->
|
||||
<template v-if="dropdown_content && dropdown_content.buttons">
|
||||
<!-- Dropdown label -->
|
||||
<div class="dropdown-item has-text-info" v-if="dropdown_content.buttons_title">
|
||||
<strong>{{ dropdown_content.buttons_title }}</strong>
|
||||
<!-- Divider -->
|
||||
<hr class="dropdown-divider" />
|
||||
</div>
|
||||
<template v-for="(button, index) in dropdown_content.buttons" :key="index">
|
||||
<div class="dropdown-item">
|
||||
<span class="is-pulled-right">
|
||||
<button class="button is-small is-light" @click="() => { button.action() }" v-bind:disabled="button.disabled" v-bind:class="button.button_classes">
|
||||
<span class="icon"><i :class="[button.icon, (button.classes ? button.classes : [])]"></i></span>
|
||||
<!-- Optional button text -->
|
||||
<template v-if="button.button_text">
|
||||
<span v-if="button.icon" v-bind:class="{ 'ml-2': button.icon }">{{ button.button_text }}</span>
|
||||
</template>
|
||||
</button>
|
||||
</span>
|
||||
<span class="has-text-left">
|
||||
{{ button.text }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<!-- If there's neither content nor buttons, show a message -->
|
||||
<template v-if="(!dropdown_content || !dropdown_content.content || dropdown_content.content.length === 0) && (!dropdown_content.buttons || dropdown_content.buttons.length === 0)">
|
||||
<div class="dropdown-item has-text-info">
|
||||
<strong>No content available</strong>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -18,7 +18,7 @@ window.addEventListener('resize', () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="py-2">
|
||||
<div class="columns is-vcentered is-multiline is-mobile">
|
||||
<!-- Change the number of items per page -->
|
||||
<div class="column is-narrow my-3" :class="{ 'is-hidden': isSmall }">
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import {
|
||||
isLoaded,
|
||||
isLoading,
|
||||
list,
|
||||
loadList,
|
||||
metaCurrentPage,
|
||||
metaItemsPerPage,
|
||||
metaTotalItems,
|
||||
setEndpoint,
|
||||
setMetaItemsPerPage,
|
||||
setFilter,
|
||||
getFilter,
|
||||
setPage,
|
||||
search,
|
||||
metaSearch,
|
||||
setOrder,
|
||||
meta
|
||||
} from "@/components/pagination/paginatedList.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
|
||||
|
||||
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
|
||||
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
|
||||
import BookingsTable from "@/components/displays/user/bookings/bookingsTable.vue";
|
||||
import {departments} from "@/components/pagination/departmentTabs.vue";
|
||||
import {ref, watch} from "vue";
|
||||
import { Colors } from "@/ThemeConfig.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import InvoiceCollectionsTable from "@/components/displays/user/invoicecollections/InvoiceCollectionsTable.vue";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
setEndpoint("/user/invoices", true);
|
||||
setOrder("created_at", "desc");
|
||||
loadList();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input @input="search($event.target.value)" class="input mt-2" type="text" placeholder="Søg efter faktura" v-model="metaSearch" />
|
||||
<PaginationDisplay :metaItemsPerPage="metaItemsPerPage" :loadFunction="loadList" :isLoading="isLoading" :setMetaItemsPerPage="setMetaItemsPerPage">
|
||||
<template #paginationColumns></template>
|
||||
</PaginationDisplay>
|
||||
<InvoiceCollectionsTable :objects="list" :meta="meta" class="mb-4" />
|
||||
<PaginationNavigation :currentPage="metaCurrentPage" :totalPages="Math.ceil(metaTotalItems / metaItemsPerPage)" :loadFunction="loadList" :setPage="setPage" :isLoading="isLoading" class="mt-3" />
|
||||
<LoadButtonWhileAwait class="is-dark" :isLoading="isLoading" :loadFunction="loadList" icon="fas fa-sync-alt">Opdater</LoadButtonWhileAwait>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<script setup>
|
||||
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
|
||||
import { loadList } from "@/components/pagination/paginatedList.vue";
|
||||
|
||||
const props = defineProps({
|
||||
objects: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
import { ref } from 'vue';
|
||||
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
|
||||
|
||||
const su_object = SessionUser.objects.collectedOrderInvoices;
|
||||
|
||||
const isInvoiceCancelled = (object) => {
|
||||
// Check if the invoice has been canceled (There's no orders attached to it)
|
||||
if (object.orders.length === 0) {
|
||||
return true
|
||||
}
|
||||
};
|
||||
|
||||
const canUserEditObject = (object) => {
|
||||
// Check if the invoice has been booked in Economic, if so, we don't want the user to be able to edit it. (It has already been sent to the customer)
|
||||
if (object.economic_invoice_booked_id || object.economic_invoice_booked_id === 0) {
|
||||
return false
|
||||
}
|
||||
// Since the object is not marked as closed, we don't want the user making changes that might need to be redone.
|
||||
if (!object.closed_at) {
|
||||
return false
|
||||
}
|
||||
// Check if the invoice has been canceled (There's no orders attached to it)
|
||||
if (isInvoiceCancelled(object)) {
|
||||
return false
|
||||
}
|
||||
// All checks passed, we want the user to be able to edit the object.
|
||||
return true
|
||||
};
|
||||
|
||||
const getObjectColor = (object, is_text) => {
|
||||
let color = 'grey';
|
||||
if (object.closed_at) {
|
||||
color = 'warning';
|
||||
}
|
||||
if (object.economic_invoice_booked_id || object.economic_invoice_booked_id === 0) {
|
||||
color = 'success';
|
||||
}
|
||||
if (isInvoiceCancelled(object)) {
|
||||
color = 'danger';
|
||||
}
|
||||
return is_text ? ('has-text-' + color) : ('is-' + color);
|
||||
};
|
||||
|
||||
const getObjectStatus = (object) => {
|
||||
let status = 'Ikke behandlet';
|
||||
if (object.closed_at) {
|
||||
status = 'Ikke faktureret';
|
||||
}
|
||||
if (object.economic_invoice_booked_id || object.economic_invoice_booked_id === 0) {
|
||||
status = 'Faktureret';
|
||||
}
|
||||
if (isInvoiceCancelled(object)) {
|
||||
return 'Annulleret';
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
const dropdown_content_requirements = (object) => {
|
||||
let requirements = [];
|
||||
// Check if the user requires a PO number
|
||||
|
||||
};
|
||||
|
||||
const dropdown_content = (object) => {
|
||||
return {
|
||||
title: SessionUser.functions.ucFirst(SessionUser.objects.collectedOrderInvoices.meta.labels.single) + ' ' + object.id,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
// Invoice status
|
||||
{
|
||||
text: 'Status',
|
||||
action: () => {},
|
||||
classes: [...[(getObjectColor(object, false))]],
|
||||
button: true,
|
||||
button_text: getObjectStatus(object),
|
||||
button_classes: [...[(getObjectColor(object, true))], ...['is-text']],
|
||||
v_centered: true,
|
||||
},
|
||||
// Orders attached to the invoice
|
||||
{
|
||||
text: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.multiple),
|
||||
action: () => {},
|
||||
classes: ['has-text-grey'],
|
||||
button: true,
|
||||
button_classes: ['has-text-grey', 'is-text'],
|
||||
v_centered: true,
|
||||
button_text: object.orders.length > 0 ? object.orders.length : SessionUser.objects.global.language.no_data,
|
||||
},
|
||||
],
|
||||
buttons: []
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="is-narrow"><!--Color indicator--></th>
|
||||
<th>{{su_object.columns.id.label}}</th>
|
||||
<th>{{su_object.columns.created_at.label}}</th>
|
||||
<th>{{su_object.columns.po_number.label}}</th>
|
||||
<th>{{SessionUser.objects.global.language.total}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="object in props.objects" :key="object.id">
|
||||
<tr>
|
||||
<td><ColorIndicator
|
||||
:color_class="getObjectColor(object, true)"
|
||||
:dropdown_content="dropdown_content(object)"
|
||||
/></td>
|
||||
<td>{{ object.id }}</td>
|
||||
<td>{{ object.created_at }}</td>
|
||||
<!-- PO Number -->
|
||||
<EditableTableColumn
|
||||
:object="object"
|
||||
:loadList="loadList"
|
||||
column="po_number"
|
||||
:parse-function="(value) => object.po_number"
|
||||
:edit-function="SessionUser.objects.collectedOrderInvoices.showEditObjectFieldForm"
|
||||
:permission-check-function="() => canUserEditObject(object)"
|
||||
/>
|
||||
<td>{{ SessionUser.functions.currency.toLocal(object.total_net_amount) }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="10">Viser {{ props.objects.length }} transaktioner</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<!-- If there are no objects, show a message -->
|
||||
<div class="notification is-dark mt-6 mb-6" v-if="props.objects.length === 0">
|
||||
Der er ingen transaktioner at vise
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -33,7 +33,16 @@ const menu_items = ref([
|
||||
route: '/user/bookings',
|
||||
icon: 'fas fa-calendar-alt',
|
||||
children: [],
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Mine fakturaer',
|
||||
route: '/user/invoices',
|
||||
icon: 'fas fa-file-invoice',
|
||||
children: [],
|
||||
permissions: [
|
||||
'user_invoices'
|
||||
]
|
||||
},
|
||||
]);
|
||||
|
||||
</script>
|
||||
|
||||
@@ -159,6 +159,14 @@ export const CollectedOrderInvoices = {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
po_number: {
|
||||
label: "PO nummer",
|
||||
type: "string",
|
||||
sortable: true,
|
||||
creation: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
closed_at: {
|
||||
label: "Lukket",
|
||||
type: "date",
|
||||
@@ -228,6 +236,14 @@ export const CollectedOrderInvoices = {
|
||||
parseInt(external_id)
|
||||
)
|
||||
},
|
||||
po_number: async (id, po_number) => {
|
||||
return ObjectsGlobal.set.column(
|
||||
CollectedOrderInvoices.meta.endpoint,
|
||||
id,
|
||||
"po_number",
|
||||
po_number
|
||||
)
|
||||
},
|
||||
},
|
||||
get: {
|
||||
all: async () => {
|
||||
|
||||
@@ -99,6 +99,7 @@ import DepartmentTimeBookingsTypes
|
||||
import DepartmentModulesSetup
|
||||
from "@/views/dashboards/superUserDashboard/department/modules/DepartmentModulesSetup.vue";
|
||||
import Vehicle from "@/views/dashboards/superUserDashboard/vehicle/Vehicle.vue";
|
||||
import MyInvoices from "@/views/dashboards/userDashboard/invoices/MyInvoices.vue";
|
||||
|
||||
// Export the router as router
|
||||
export const router = createRouter({
|
||||
@@ -153,6 +154,12 @@ export const router = createRouter({
|
||||
component: MyOrders,
|
||||
meta: { middleware: authMiddleware }
|
||||
},
|
||||
{
|
||||
name: 'myinvoices',
|
||||
path: '/user/invoices',
|
||||
component: MyInvoices,
|
||||
meta: { middleware: authMiddleware }
|
||||
},
|
||||
{
|
||||
name: 'myorder',
|
||||
path: '/user/orders/:orderId',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
|
||||
import { Colors } from "@/ThemeConfig.vue";
|
||||
import {defineProps, ref} from "vue";
|
||||
import {defineProps, ref, computed, watch} from "vue";
|
||||
import { defineSlots } from "vue";
|
||||
import UserNavigationTop from "@/components/displays/user/UserNavigationTop.vue";
|
||||
import Footer from "@/components/global/Footer.vue";
|
||||
@@ -18,6 +18,18 @@ const IS_EXPANDED = ref(false)
|
||||
const TOGGLE_EXPANDED = () => {
|
||||
IS_EXPANDED.value = !IS_EXPANDED.value
|
||||
}
|
||||
|
||||
const isMobile = ref(window.innerWidth < 768)
|
||||
|
||||
const handleResize = () => {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
}
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
|
||||
const applyScreenDependingClasses = (shouldApply, classes) => {
|
||||
return shouldApply ? classes : []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -107,7 +119,10 @@ const TOGGLE_EXPANDED = () => {
|
||||
:style="{ 'background-color': Colors.global.backgroundColor, 'color': Colors.global.textColor }"
|
||||
style="min-height: 100vh;"
|
||||
>
|
||||
<div class="pl-6 pr-6 pb-6">
|
||||
<div v-bind:class="[
|
||||
...applyScreenDependingClasses(!isMobile, ['pl-6', 'pr-6', 'pb-6']),
|
||||
...applyScreenDependingClasses(isMobile, ['pl-2', 'pr-2', 'pb-2']),
|
||||
]">
|
||||
<!-- Buttons slot -->
|
||||
<template v-if="slots.buttons">
|
||||
<slot name="buttons"></slot>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
|
||||
import BookingsPagination from "@/components/displays/pagination/models/UserDashboard/BookingsPagination.vue";
|
||||
import UserDashboardPageWrapper from "@/views/dashboards/userDashboard/UserDashboardPageWrapper.vue";
|
||||
import MyInvoicesPagination from "@/components/displays/pagination/models/UserDashboard/MyInvoicesPagination.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UserDashboardPageWrapper title="Mine fakturaer" subtitle="Se dine fakturaer">
|
||||
<MyInvoicesPagination />
|
||||
</UserDashboardPageWrapper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user