Skip to content

Commit

Permalink
invoice client
Browse files Browse the repository at this point in the history
  • Loading branch information
mhd-zaid committed Dec 3, 2023
1 parent e5fb1cd commit 58160ad
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 51 deletions.
4 changes: 2 additions & 2 deletions api/src/routes/ordersRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ export default (
}

const orders = await Order.findAll({
where: { user: user },
include: "products",
where: { userId: user.id },
include: ["products","user","payment"],
});

if (orders.length === 0) {
Expand Down
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.7",
"axios": "^1.5.0",
"html2pdf.js": "^0.9.0",
"pinia": "^2.1.6",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
Expand Down
189 changes: 189 additions & 0 deletions client/src/components/profile/OrderPdf.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<script lang="ts" setup>
import { defineProps } from 'vue'
import axiosInstance from '@/utils/axiosInstance';
const props = defineProps(['data'])
const formatDate = () => {
const today = new Date()
const day = String(today.getDate()).padStart(2, '0')
const month = String(today.getMonth() + 1).padStart(2, '0')
const year = today.getFullYear()
return `${day}-${month}-${year}`
}
</script>
<template>
<div class="invoice-box">
<table>
<tr class="top">
<td colspan="4">
<table>
<tr>
<td class="title">
<img
src="../../../public/images/sneakpeak_logo_black.png"
alt="Company logo"
style="width: 100%; max-width: 300px"
/>
</td>
<td></td>
<td></td>
<td v-if="props.data.payment">
Facture #{{props.data.payment.id.toUpperCase() }}<br />
Généré le: {{ formatDate() }}<br />
</td>
</tr>
</table>
</td>
</tr>

<tr class="information">
<td colspan="4">
<table>
<tr>
<td>
Sneak Peak,<br />
242 Rue du Faubourg Saint-Antoine, <br />
75012 Paris
</td>
<td></td>
<td></td>
<td v-if="props.data.user">
{{ props.data.user.firstname }} {{ props.data.user.lastname.toUpperCase() }}<br />
{{ props.data.user.email }} <br />
{{ props.data.deliveryAddress.split(',')[0] }} <br />
{{ props.data.deliveryAddress.split(',')[1] }} <br />
{{ props.data.deliveryAddress.split(',')[2] }}
</td>
</tr>
</table>
</td>
</tr>

<tr class="heading">
<td> Méthode de Payment :</td>
<td></td>
<td></td>
<td>Carte</td>
</tr>
<tr><br></tr>
<tr class="heading">
<td>Référence</td>
<td>Produit</td>
<td>Quantité</td>
<td>Prix Unitaire</td>
</tr>

<tr class="item" v-if="props.data.products" v-for="product in props.data.products">
<td>{{ product.sku }}</td>
<td>{{ product.name }} - {{ product.size }}</td>
<td>{{ product.Orders_Products.quantity }}</td>
<td> {{ (product.Orders_Products.price / 100).toFixed(2) }} €</td>
</tr>
<tr><br></tr>
<tr class="total">
<td></td>
<td></td>
<td></td>
<td>Total: {{ props.data.total() }} </td>
</tr>
</table>
</div>
</template>
<style scoped>
h1 {
font-weight: 300;
margin-bottom: 0px;
padding-bottom: 0px;
color: #000;
}
h3 {
font-weight: 300;
margin-top: 10px;
margin-bottom: 20px;
font-style: italic;
color: #555;
}
a {
color: #06f;
}
.invoice-box {
max-width: 800px;
margin: auto;
padding: 30px;
font-size: 16px;
line-height: 24px;
font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
color: #555;
}
.invoice-box table {
width: 100%;
line-height: inherit;
text-align: left;
border-collapse: collapse;
}
.invoice-box table td {
padding: 5px;
vertical-align: top;
}
.invoice-box table tr td:nth-child(4) {
text-align: right;
}
.invoice-box table tr.top table td {
padding-bottom: 20px;
}
.invoice-box table tr.top table td.title {
font-size: 45px;
line-height: 45px;
color: #333;
}
.invoice-box table tr.information table td {
padding-bottom: 40px;
}
.invoice-box table tr.heading td {
background: #eee;
border-bottom: 1px solid #ddd;
font-weight: bold;
}
.invoice-box table tr.details td {
padding-bottom: 20px;
}
.invoice-box table tr.item td {
border-bottom: 1px solid #eee;
}
.invoice-box table tr.item.last td {
border-bottom: none;
}
.invoice-box table tr.total td:nth-child(4) {
border-top: 2px solid #eee;
font-weight: bold;
}
@media only screen and (max-width: 600px) {
.invoice-box table tr.top table td {
width: 100%;
display: block;
text-align: center;
}
.invoice-box table tr.information table td {
width: 100%;
display: block;
text-align: center;
}
}
</style>
123 changes: 74 additions & 49 deletions client/src/sections/profile/HistorySection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,68 +7,96 @@ import {
} from '@headlessui/vue'
import {EllipsisVerticalIcon} from '@heroicons/vue/24/outline'
import {CheckCircleIcon} from '@heroicons/vue/20/solid'
import axiosInstance from '@/utils/axiosInstance'
import {getProductImage} from "@/types/ProductImageType";
import { onMounted, ref } from 'vue';
import OrderPdf from "@/components/profile/OrderPdf.vue";
import html2pdf from 'html2pdf.js'
const orders = [
{
number: 'WU88191111',
href: '#',
invoiceHref: '#',
createdDate: 'Jul 6, 2021',
createdDatetime: '2021-07-06',
deliveredDate: 'July 12, 2021',
deliveredDatetime: '2021-07-12',
total: '$160.00',
products: [
{
id: 1,
name: 'Micro Backpack',
description:
'Are you a minimalist looking for a compact carry option? The Micro Backpack is the perfect size for your essential everyday carry items. Wear it like a backpack or carry it like a satchel for all-day use.',
href: '#',
price: '$70.00',
imageSrc: 'https://tailwindui.com/img/ecommerce-images/order-history-page-03-product-01.jpg',
imageAlt:
'Moss green canvas compact backpack with double top zipper, zipper front pouch, and matching carry handle and backpack straps.',
},
],
},
]
const token = localStorage.getItem('token')
const isAuthenticated = !!token
let userId = ''
if (isAuthenticated) {
const payload = JSON.parse(atob(token.split('.')[1]))
userId = payload.userId
}
const getUserOrders = async () => {
const {data} = await axiosInstance.get(`/orders/user/${userId}`)
data.forEach((order: any) => {
order.products.forEach((product: any) => {
product.imageSrc = getProductImage(product,2)
product.imageAlt = product.name
product.price = ((product.Orders_Products.price / 100) * product.Orders_Products.quantity).toFixed(2)
product.href = `/products/${product.id}`
})
order.total = () => {
let total = 0.00
order.products.forEach((product: any) => {
total += parseFloat(product.price);
})
return total.toFixed(2)
}
})
return data.filter((order: any) => order.status !== 'payment pending' && order.status !== 'payment failed')
}
const orders = ref()
onMounted(async () => {
orders.value = await getUserOrders()
})
const downloadInvoice = async (orderId:string) => {
const template = document.getElementById('invoice-template')
if(template){
template.style.display = 'block'
await html2pdf(template, {
margin: [1,1],
filename: `facture-${orderId}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2,letterRendering: true },
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' },
pagebreak: { mode: ['avoid-all','css','legacy'] }
})
template.style.display = 'none'
}
}
</script>
<template>
<section aria-labelledby="recent-heading" class="mt-16">
<h2 id="recent-heading" class="sr-only">Recent orders</h2>
<div class="mx-auto max-w-7xl sm:px-2 lg:px-8">
<div class="mx-auto max-w-2xl space-y-8 sm:px-4 lg:max-w-4xl lg:px-0">
<div v-for="order in orders" :key="order.number"
<div v-for="order in orders" :key="order.id"
class="border-b border-t border-gray-200 bg-white shadow-sm sm:rounded-lg sm:border">
<h3 class="sr-only">
Order placed on
<time :datetime="order.createdDatetime">{{ order.createdDate }}</time>
<time :datetime="order.createdAt">{{ order.createdAt }}</time>
</h3>

<div class="flex items-center border-b border-gray-200 p-4 sm:grid sm:grid-cols-4 sm:gap-x-6 sm:p-6">
<dl class="grid flex-1 grid-cols-2 gap-x-6 text-sm sm:col-span-3 sm:grid-cols-3 lg:col-span-2">
<div>
<dt class="font-medium text-gray-900">Order number</dt>
<dd class="mt-1 text-gray-500">{{ order.number }}</dd>
<dd class="mt-1 text-gray-500">{{ order.id }}</dd>
</div>
<div class="hidden sm:block">
<dt class="font-medium text-gray-900">Date placed</dt>
<dd class="mt-1 text-gray-500">
<time :datetime="order.createdDatetime">{{ order.createdDate }}</time>
</dd>
<div>
<dt class="font-medium text-gray-900"></dt>
<dd class="mt-1 font-medium text-gray-900"></dd>
</div>
<div>
<dt class="font-medium text-gray-900">Total amount</dt>
<dd class="mt-1 font-medium text-gray-900">{{ order.total }}</dd>
<dd class="mt-1 font-medium text-gray-900">{{ order.total() }}</dd>
</div>
</dl>

<Menu as="div" class="relative flex justify-end lg:hidden">
<div class="flex items-center">
<MenuButton class="-m-2 flex items-center p-2 text-gray-400 hover:text-gray-500">
<span class="sr-only">Options for order {{ order.number }}</span>
<span class="sr-only">Options for order {{ order.id }}</span>
<EllipsisVerticalIcon class="h-6 w-6" aria-hidden="true"/>
</MenuButton>
</div>
Expand Down Expand Up @@ -99,13 +127,18 @@ const orders = [
<a :href="order.href"
class="flex items-center justify-center rounded-md border border-gray-300 bg-white px-2.5 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
<span>View Order</span>
<span class="sr-only">{{ order.number }}</span>
<span class="sr-only">{{ order.id }}</span>
</a>
<a :href="order.invoiceHref"
<button
v-on:click="downloadInvoice(order.id)"
class="flex items-center justify-center rounded-md border border-gray-300 bg-white px-2.5 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
<span>View Invoice</span>
<span class="sr-only">for order {{ order.number }}</span>
</a>
<span>Dowload Invoice</span>
<span class="sr-only">for order {{ order.id }}</span>
</button>
<!-- template PDF -->
<div id="invoice-template" style="display: none;">
<OrderPdf :data="order" />
</div>
</div>
</div>

Expand All @@ -121,21 +154,13 @@ const orders = [
<div class="ml-6 flex-1 text-sm">
<div class="font-medium text-gray-900 sm:flex sm:justify-between">
<h5>{{ product.name }}</h5>
<p class="mt-2 sm:mt-0">{{ product.price }}</p>
<p class="mt-2 sm:mt-0">{{ product.price }}</p>
</div>
<p class="hidden text-gray-500 sm:mt-2 sm:block">{{ product.description }}</p>
</div>
</div>

<div class="mt-6 sm:flex sm:justify-between">
<div class="flex items-center">
<CheckCircleIcon class="h-5 w-5 text-green-500" aria-hidden="true"/>
<p class="ml-2 text-sm font-medium text-gray-500">
Delivered on
<time :datetime="order.deliveredDatetime">{{ order.deliveredDate }}</time>
</p>
</div>

<div
class="mt-6 flex items-center space-x-4 divide-x divide-gray-200 border-t border-gray-200 pt-4 text-sm font-medium sm:ml-4 sm:mt-0 sm:border-none sm:pt-0">
<div class="flex flex-1 justify-center">
Expand Down

0 comments on commit 58160ad

Please sign in to comment.