361 lines
9.5 KiB
Vue

<template>
<form @submit.prevent="submitForm">
<!-- Row 1: N° Facture, Fournisseur, Date -->
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label">N° Facture Fournisseur *</label>
<soft-input
v-model="formData.number"
type="text"
placeholder="Ex: FR-2026-001"
required
/>
</div>
<div class="col-md-4">
<label class="form-label">Fournisseur *</label>
<select
v-model="formData.supplierId"
class="form-select"
required
@change="updateSupplierInfo"
>
<option value="">-- Sélectionner un fournisseur --</option>
<option
v-for="supplier in suppliers"
:key="supplier.id"
:value="supplier.id"
>
{{ supplier.name }}
</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Date facture *</label>
<soft-input v-model="formData.date" type="date" required />
</div>
</div>
<!-- Row 2: Statut, Notes -->
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Statut</label>
<select v-model="formData.status" class="form-select">
<option value="brouillon">Brouillon</option>
<option value="en_attente">En attente de paiement</option>
<option value="payee">Payée</option>
<option value="annulee">Annulée</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Notes</label>
<textarea
v-model="formData.notes"
class="form-control"
placeholder="Remarques éventuelles..."
rows="2"
></textarea>
</div>
</div>
<!-- Articles Section -->
<div class="mb-4">
<div class="flex items-center justify-between mb-4">
<label
class="peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-lg font-bold"
>Lignes de facture</label
>
<button
class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-8 rounded-md px-3 text-xs"
type="button"
@click="addLine"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-plus w-4 h-4 mr-2"
>
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
Ajouter ligne
</button>
</div>
<div class="space-y-3 mt-3">
<div
v-for="(line, index) in formData.lines"
:key="index"
class="row g-2 align-items-end bg-light p-3 rounded mx-0"
>
<div class="col-md-5">
<label class="form-label text-xs mb-1">Désignation *</label>
<soft-input
v-model="line.designation"
type="text"
placeholder="Nom de l'article ou prestation"
required
/>
</div>
<div class="col-md-2">
<label class="form-label text-xs mb-1">Quantité *</label>
<soft-input
v-model.number="line.quantity"
type="number"
placeholder="Qté"
:min="1"
required
/>
</div>
<div class="col-md-2">
<label class="form-label text-xs mb-1">Prix HT *</label>
<soft-input
v-model.number="line.priceHt"
type="number"
placeholder="0.00"
step="0.01"
required
/>
</div>
<div class="col-md-2 text-end">
<label class="form-label text-xs mb-1 d-block">Sous-total HT</label>
<span class="text-sm font-weight-bold">
{{ formatCurrency(line.quantity * line.priceHt) }}
</span>
</div>
<div class="col-md-1 text-end">
<button
type="button"
class="btn btn-sm btn-link text-danger mb-0"
:disabled="formData.lines.length === 1"
@click="removeLine(index)"
>
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Totaux -->
<div class="row mb-4">
<div class="col-md-5 ms-auto">
<div class="card bg-gray-100 shadow-none border">
<div class="card-body p-3">
<div class="d-flex justify-content-between mb-2">
<span class="text-sm">Total HT:</span>
<span class="text-sm font-weight-bold">{{
formatCurrency(calculateTotalHt())
}}</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-sm">TVA (20%):</span>
<span class="text-sm font-weight-bold">{{
formatCurrency(calculateTotalTva())
}}</span>
</div>
<hr class="horizontal dark my-2" />
<div class="d-flex justify-content-between">
<span class="text-base font-weight-bold">Total TTC:</span>
<span class="text-base font-weight-bold text-info">{{
formatCurrency(calculateTotalTtc())
}}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Boutons d'action -->
<div class="d-flex justify-content-end gap-3 mt-4">
<soft-button
type="button"
color="secondary"
variant="outline"
@click="$emit('cancel')"
>
Annuler
</soft-button>
<soft-button type="submit" color="success">
Enregistrer la facture
</soft-button>
</div>
</form>
</template>
<script setup>
import { ref, defineEmits } from "vue";
import SoftInput from "@/components/SoftInput.vue";
import SoftButton from "@/components/SoftButton.vue";
const emit = defineEmits(["submit", "cancel"]);
const suppliers = [
{ id: "1", name: "Produits Funéraires Pro" },
{ id: "2", name: "Thanatos Supply" },
{ id: "3", name: "ISOFROID" },
{ id: "4", name: "EEP Co EUROPE" },
{ id: "5", name: "NEXTECH MEDICAL" },
{ id: "6", name: "ACTION" },
{ id: "7", name: "E.LECLERC" },
];
const formData = ref({
number: "",
supplierId: "",
supplierName: "",
date: new Date().toISOString().split("T")[0],
status: "brouillon",
notes: "",
lines: [
{
designation: "",
quantity: 1,
priceHt: 0,
},
],
});
const updateSupplierInfo = () => {
const supplier = suppliers.find((s) => s.id === formData.value.supplierId);
if (supplier) {
formData.value.supplierName = supplier.name;
}
};
const formatCurrency = (value) => {
return new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR",
}).format(value);
};
const calculateTotalHt = () => {
return formData.value.lines.reduce((sum, line) => {
return sum + line.quantity * (line.priceHt || 0);
}, 0);
};
const calculateTotalTva = () => {
return calculateTotalHt() * 0.2;
};
const calculateTotalTtc = () => {
return calculateTotalHt() + calculateTotalTva();
};
const addLine = () => {
formData.value.lines.push({
designation: "",
quantity: 1,
priceHt: 0,
});
};
const removeLine = (index) => {
if (formData.value.lines.length > 1) {
formData.value.lines.splice(index, 1);
}
};
const submitForm = () => {
if (!formData.value.supplierId) {
alert("Veuillez sélectionner un fournisseur");
return;
}
const payload = {
...formData.value,
totalHt: calculateTotalHt(),
totalTva: calculateTotalTva(),
totalTtc: calculateTotalTtc(),
// Map lines to include totalHt for store consistency
lines: formData.value.lines.map((line) => ({
...line,
totalHt: line.quantity * line.priceHt,
})),
};
emit("submit", payload);
};
</script>
<style scoped>
/* Scoped styles for the custom button from user request */
.bg-primary {
background-color: var(--bs-primary, #cb0c9f);
}
.text-primary-foreground {
color: #fff;
}
.bg-primary\:hover {
background-color: #b30b8c;
}
/* Tailwind-like utilities used in the provided snippet */
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.mb-4 {
margin-bottom: 1.5rem;
}
.text-lg {
font-size: 1.125rem;
}
.font-bold {
font-weight: 700;
}
.gap-2 {
gap: 0.5rem;
}
.whitespace-nowrap {
white-space: nowrap;
}
.transition-colors {
transition-property: background-color, border-color, color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.shadow {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.rounded-md {
border-radius: 0.375rem;
}
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.h-8 {
height: 2rem;
}
.text-xs {
font-size: 0.75rem;
}
.font-medium {
font-weight: 500;
}
.space-y-3 > div + div {
margin-top: 1rem;
}
.form-label {
font-size: 0.875rem;
font-weight: 500;
margin-bottom: 0.5rem;
}
</style>