should be good

This commit is contained in:
nessar committed 2024-10-02 03:10:36 +02:00
1 parent 37d57a9ffb
commit 3ac089410c
5 files changed
+419 -153

No files matched your search

+14 -11
View File
@@ -1,13 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvoiceGen</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+400 -22
View File
@@ -1,30 +1,408 @@
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
<div class="no-print center">
<button class="no-print" onclick="window.print()">Enregister la facture</button>
<button class="no-print" @click="addProduct()">Ajouter un produit</button>
<button class="no-print" @click="removeProduct()">Supprimer un produit</button>
<button class="no-print" @click="save()">Sauvegarder la configuration</button>
</div>
<div class="invoice" @drop.prevent="onDrop" @dragover.prevent="onDragOver">
<div class="no-print fullscreen" v-if="dragOver" @dragleave.prevent="onDragLeave">
<h1>Drop here</h1>
</div>
<div class="content">
<div class="header">
<div class="invoiceNumber">
<h3>Facture n°{{ new Date().getFullYear() + '-' + invoiceNumber.toString() }}</h3>
<input class="no-print" type="number" v-model="invoiceNumber" />
</div>
<h3>Date: {{ new Date().toLocaleDateString("fr-FR") }}</h3>
</div>
<div class="fromTo">
<div class="from">
<h3>De:</h3>
<div class='info'>
<p>{{ company.name }}</p><input type="text" class="no-print" v-model="company.name">
</div>
<div class='info'>
<p>{{ company.address }}</p><input type="text" class="no-print" v-model="company.address">
</div>
<div class='info'>
<p>{{ company.zip }}, {{ company.city }}</p>
<input type="text" class="no-print" v-model="company.zip">
<input type="text" class="no-print" v-model="company.city">
</div>
<div class='info'>
<p>{{ company.country }}</p><input type="text" class="no-print" v-model="company.country">
</div>
<div class='info'>
<p>{{ company.mail }}</p><input type="text" class="no-print" v-model="company.mail">
</div>
</div>
<div class="to">
<h3>À:</h3>
<div class='info'>
<p>{{ client.name }}</p><input type="text" class="no-print" v-model="client.name">
</div>
<div class='info'>
<p>{{ client.address }}</p><input type="text" class="no-print" v-model="client.address">
</div>
<div class='info'>
<p>{{ client.zip }}, {{ client.city }}</p>
<input type="text" class="no-print" v-model="client.zip">
<input type="text" class="no-print" v-model="client.city">
</div>
<div class='info'>
<p>{{ client.country }}</p><input type="text" class="no-print" v-model="client.country">
</div>
<div class='info'>
<p>{{ client.mail }}</p><input type="text" class="no-print" v-model="client.mail">
</div>
</div>
</div>
<div class="products">
<table>
<thead>
<tr>
<th>Désignation</th>
<th>Date de fin</th>
<th>Prix u. HT</th>
<th>Qté</th>
<th>Total HT</th>
</tr>
</thead>
<tbody>
<tr v-for="sale in sales">
<div class='sale'>
<td>{{ sale.name }}</td><input type="text" class="no-print smallInput"
v-model="sale.name" />
</div>
<div class='sale'>
<td>{{ sale.date }}</td><input type="text" class="no-print smallInput"
v-model="sale.date" />
</div>
<div class='sale'>
<td>{{ sale.price }} €</td><input type="number" class="no-print smallInput"
v-model="sale.price" />
</div>
<div class='sale'>
<td>{{ sale.quantity }}</td><input type="number" class="no-print smallInput"
v-model="sale.quantity" />
</div>
<div class='sale'>
<td>{{ sale.price * sale.quantity }} €</td>
</div>
</tr>
</tbody>
<tfoot>
<tr style="font-weight: bold;">
<td>TOTAL HT EN EUROS</td>
<td>{{ sales.reduce((acc, sale) => acc + sale.price * sale.quantity, 0) }} €</td>
</tr>
<tr>
<td style="width: 60%;">TVA non applicable, art. 293 B du CGI</td>
</tr>
</tfoot>
</table>
</div>
<div class="payment">
<h3>IBAN {{ company.name }}</h3>
<div class="iban">
<p>{{ company.iban }}</p>
<input type="text" class="no-print" v-model="company.iban">
</div>
<p>La facture est payable sous 30 jours</p>
<p>Escompte pour paiement anticipé: néant</p>
<p>Tout règlement effectué après expiration du délai donnera lieu, à titre de pénalité de retard, à
l'application d'un intérêt égal à celui appliqué par la Banque Centrale Européenne à son opération
de refinancement la plus récente, majoré de 10 points de pourcentage, ainsi qu'à une indemnité
forfaitaire pour frais de recouvrement d'un montant de 40 €.<br /> Les pénalités de retard sont
exigibles sans qu'un rappel soit nécessaire.</p>
</div>
<div class="footer">
<p>{{ company.name }} - {{ company.address }} - {{ company.zip }} - {{ company.country }}</p>
<p>Siret: {{ company.siret }} - APE: {{ company.APE }}</p>
<div class="siretAPEinput no-print">
<input type="text" v-model="company.siret" />
<input type="text" v-model="company.APE" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
data() {
return {
invoiceNumber: 1,
company: {
name: 'Acme Corp',
address: '1234 Main Street',
city: 'Springfield',
country: 'France',
zip: '62701',
mail: 'mail@mail.mail',
iban: 'FR76 3000 4000 5000 6000 7000 8000',
siret: 'xxx xxx xxx xxxxx',
APE: '0000X'
},
client: {
name: 'Jane Doe',
address: '1234 Main Street',
city: 'Springfield',
country: 'France',
zip: '62701',
mail: 'mail@mail.mail'
},
sales: [
{
name: 'Produit 1',
date: '01/01/2021',
price: 100,
quantity: 2,
},
{
name: 'Produit 2',
date: '01/01/2021',
price: 200,
quantity: 1,
},
{
name: 'Produit 3',
date: '01/01/2021',
price: 300,
quantity: 1,
},
],
dragOver: false,
};
},
methods: {
addProduct() {
this.sales.push({
name: '',
date: '',
price: 0,
quantity: 0,
});
},
removeProduct() {
this.sales.pop();
},
// save in a file on the computer
save() {
const data = {
invoiceNumber: this.invoiceNumber,
company: this.company,
client: this.client,
sales: this.sales,
};
const a = document.createElement('a');
const file = new Blob([JSON.stringify(data)], { type: 'application/json' });
a.href = URL.createObjectURL(file);
a.download = `invoice-${this.invoiceNumber}.json`;
a.click();
},
async onDrop(event: DragEvent) {
this.dragOver = false;
if (event.dataTransfer) {
const f = event.dataTransfer?.files[0];
const data = JSON.parse(await f.text());
this.invoiceNumber = data.invoiceNumber;
this.company = data.company;
this.client = data.client;
this.sales = data.sales;
}
},
onDragOver() {
this.dragOver = true;
},
onDragLeave() {
this.dragOver = false;
},
updateTitle() {
document.title = 'Facture-' + new Date().getFullYear() + '-' + this.invoiceNumber;
}
},
mounted() {
this.updateTitle();
},
watch: {
invoiceNumber() {
this.updateTitle();
}
}
});
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
.fullscreen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: white;
display: flex;
justify-content: center;
align-items: center;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
.footer {
display: flex;
flex-direction: column;
align-self: center;
text-align: center;
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
.footer p {
margin: 0;
font-weight: bold;
}
.iban {
display: flex;
flex-direction: column
}
.products {
width: 100%;
}
.smallInput {
width: 80%
}
.products table {
display: flex;
flex-direction: column;
width: 100%;
border: 1px solid black;
padding: 10px;
}
.products table thead tr,
.products table tbody tr {
display: flex;
flex-direction: row;
justify-content: space-between;
text-align: left;
border-bottom: 1px solid black;
}
.products table thead th {
width: 20%;
}
.products table tfoot tr {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.products table tfoot td {
width: 20%;
}
.products table tbody {
display: flex;
flex-direction: column;
}
.sale {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 20%;
}
.fromTo {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.from,
.to {
display: flex;
flex-direction: column;
max-width: 33.333%;
}
.info {
display: flex;
flex-direction: column;
}
.from p,
.to p {
margin: 0px;
}
.center {
display: flex;
justify-content: center;
width: 100%;
justify-content: space-evenly;
padding: 10px;
}
.center button {
padding: 10px;
font-size: 16px;
}
@media print {
.no-print {
display: none;
}
}
.content {
display: flex;
flex-direction: column;
width: 100%;
height: calc(100% - 50px * 2);
padding: 50px;
justify-content: space-between;
}
.invoice {
display: flex;
flex-direction: row;
width: 210mm;
height: 297mm;
border: 1px solid black;
}
.header {
display: flex;
flex-direction: column;
width: 100%;
}
.invoiceNumber {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
h3 {
margin-bottom: 5px;
}
.invoiceNumber input {
margin-left: 10px;
width: 50px;
}
</style>
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

-41
View File
@@ -1,41 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
+5 -78
View File
@@ -1,79 +1,6 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
font-family: sans-serif;
max-width: 800px;
margin: auto;
box-sizing: border-box;
}