Feat: redesign facture comme dans le facutre
This commit is contained in:
parent
8171a20d41
commit
8ee7d8f8e9
@ -1,103 +1,129 @@
|
||||
<template>
|
||||
<div v-if="loading" class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<!-- Loading -->
|
||||
<div v-if="loading" class="detail-state">
|
||||
<div class="spinner-ring"></div>
|
||||
<p>Chargement de la facture…</p>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-else-if="error" class="detail-state detail-state--error">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<p>{{ error }}</p>
|
||||
<button class="btn-retry" @click="reload">
|
||||
<i class="fas fa-redo me-2"></i>Réessayer
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="error" class="text-center py-5 text-danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<invoice-detail-template v-else-if="invoice">
|
||||
<template #header>
|
||||
<invoice-header
|
||||
:invoice-number="invoice.invoice_number"
|
||||
:date="invoice.invoice_date"
|
||||
/>
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3">
|
||||
<div>
|
||||
<h6 class="mb-1">Détails Facture</h6>
|
||||
<p class="text-sm mb-0">
|
||||
Facture n°
|
||||
<b>{{ invoice.invoice_number || "—" }}</b>
|
||||
du
|
||||
<b>{{ formatDate(invoice.invoice_date) }}</b>
|
||||
</p>
|
||||
<p class="text-sm mb-0">
|
||||
Échéance :
|
||||
<b>{{ formatDate(invoice.due_date) }}</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center flex-wrap gap-2">
|
||||
<soft-badge :color="statusBadgeColor(invoice.status)" variant="gradient">
|
||||
<i :class="statusIcon(invoice.status) + ' me-1'"></i>
|
||||
{{ getStatusLabel(invoice.status) }}
|
||||
</soft-badge>
|
||||
<soft-button color="secondary" variant="gradient" class="mb-0">
|
||||
Export PDF
|
||||
</soft-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #lines>
|
||||
<invoice-lines-table :lines="invoice.lines" />
|
||||
<template #product>
|
||||
<div class="d-flex">
|
||||
<div class="inv-visual me-3">
|
||||
<i class="fas fa-file-invoice-dollar"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-lg mb-0 mt-1">{{ invoice.client?.name || "Client inconnu" }}</h6>
|
||||
<p class="text-sm mb-3">{{ invoice.lines?.length || 0 }} ligne(s) dans cette facture.</p>
|
||||
<soft-badge :color="statusBadgeColor(invoice.status)" variant="gradient" size="sm">
|
||||
{{ getStatusLabel(invoice.status) }}
|
||||
</soft-badge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cta>
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="inv-status-select-wrap">
|
||||
<label class="form-label text-xs mb-1">Changer le statut</label>
|
||||
<select
|
||||
class="form-select inv-status-select"
|
||||
:value="selectedStatus"
|
||||
:disabled="updating"
|
||||
@change="onStatusSelect"
|
||||
>
|
||||
<option v-for="s in availableStatuses" :key="s" :value="s">
|
||||
{{ getStatusLabel(s) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm mt-2 mb-0 text-end">
|
||||
Modifier le statut de la facture directement depuis cette section.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #timeline>
|
||||
<div>
|
||||
<h6 class="mb-3 text-sm">Historique</h6>
|
||||
<div v-if="invoice.history && invoice.history.length > 0">
|
||||
<div
|
||||
v-for="(entry, index) in invoice.history"
|
||||
:key="index"
|
||||
class="mb-2"
|
||||
>
|
||||
<span class="text-xs text-secondary">
|
||||
{{ formatDate(entry.changed_at) }}
|
||||
</span>
|
||||
<p class="text-xs mb-0">{{ entry.comment }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="text-xs text-secondary">Aucun historique</p>
|
||||
</div>
|
||||
<h6 class="mb-3">Suivi facture</h6>
|
||||
<invoice-timeline :history="invoice.history" />
|
||||
</template>
|
||||
|
||||
<template #billing>
|
||||
<div>
|
||||
<h6 class="mb-3 text-sm">Informations Client</h6>
|
||||
<p class="text-sm mb-1">
|
||||
<strong>{{
|
||||
invoice.client ? invoice.client.name : "Client inconnu"
|
||||
}}</strong>
|
||||
</p>
|
||||
<p class="text-xs text-secondary mb-1">
|
||||
{{ invoice.client ? invoice.client.email : "" }}
|
||||
</p>
|
||||
<p class="text-xs text-secondary mb-0">
|
||||
{{ invoice.client ? invoice.client.phone : "" }}
|
||||
</p>
|
||||
<template #payment>
|
||||
<h6 class="mb-3">Lignes de facture</h6>
|
||||
<invoice-lines-table :lines="invoice.lines" />
|
||||
|
||||
<h6 class="mb-3 mt-4">Informations Client</h6>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item border-0 d-flex p-4 mb-2 bg-gray-100 border-radius-lg">
|
||||
<div class="d-flex flex-column">
|
||||
<h6 class="mb-3 text-sm">{{ invoice.client?.name || "Client inconnu" }}</h6>
|
||||
<span class="mb-2 text-xs">
|
||||
Adresse email :
|
||||
<span class="text-dark ms-2 font-weight-bold">{{ invoice.client?.email || "—" }}</span>
|
||||
</span>
|
||||
<span class="mb-2 text-xs">
|
||||
Téléphone :
|
||||
<span class="text-dark ms-2 font-weight-bold">{{ invoice.client?.phone || "—" }}</span>
|
||||
</span>
|
||||
<span class="text-xs">
|
||||
Référence facture :
|
||||
<span class="text-dark ms-2 font-weight-bold">{{ invoice.invoice_number || "—" }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template #summary>
|
||||
<invoice-summary
|
||||
:ht="invoice.total_ht"
|
||||
:tva="invoice.total_tva"
|
||||
:ttc="invoice.total_ttc"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="position-relative d-inline-block me-2">
|
||||
<soft-button
|
||||
color="secondary"
|
||||
variant="gradient"
|
||||
@click="dropdownOpen = !dropdownOpen"
|
||||
>
|
||||
{{ getStatusLabel(invoice.status) }}
|
||||
<i class="fas fa-chevron-down ms-2"></i>
|
||||
</soft-button>
|
||||
<ul
|
||||
v-if="dropdownOpen"
|
||||
class="dropdown-menu show position-absolute"
|
||||
style="top: 100%; left: 0; z-index: 1000"
|
||||
>
|
||||
<li v-for="status in availableStatuses" :key="status">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
:class="{ active: status === invoice.status }"
|
||||
href="javascript:;"
|
||||
@click="
|
||||
changeStatus(status);
|
||||
dropdownOpen = false;
|
||||
"
|
||||
>
|
||||
{{ getStatusLabel(status) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h6 class="mb-3">Résumé Facture</h6>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="mb-2 text-sm">Total HT :</span>
|
||||
<span class="text-dark font-weight-bold ms-2">{{ formatCurrency(invoice.total_ht) }}</span>
|
||||
</div>
|
||||
|
||||
<soft-button color="info" variant="outline">
|
||||
<i class="fas fa-file-pdf me-1"></i> Télécharger PDF
|
||||
</soft-button>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="mb-2 text-sm">TVA :</span>
|
||||
<span class="text-dark ms-2 font-weight-bold">{{ formatCurrency(invoice.total_tva) }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<span class="mb-2 text-lg">Total TTC :</span>
|
||||
<span class="text-dark text-lg ms-2 font-weight-bold">{{ formatCurrency(invoice.total_ttc) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</invoice-detail-template>
|
||||
@ -105,14 +131,13 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, defineProps } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useInvoiceStore } from "@/stores/invoiceStore";
|
||||
import { useNotificationStore } from "@/stores/notification";
|
||||
import InvoiceDetailTemplate from "@/components/templates/Invoice/InvoiceDetailTemplate.vue";
|
||||
import InvoiceHeader from "@/components/molecules/Invoice/InvoiceHeader.vue";
|
||||
import InvoiceTimeline from "@/components/molecules/Invoice/InvoiceTimeline.vue";
|
||||
import InvoiceLinesTable from "@/components/molecules/Invoice/InvoiceLinesTable.vue";
|
||||
import InvoiceSummary from "@/components/molecules/Invoice/InvoiceSummary.vue";
|
||||
import SoftButton from "@/components/SoftButton.vue";
|
||||
import SoftBadge from "@/components/SoftBadge.vue";
|
||||
|
||||
const props = defineProps({
|
||||
invoiceId: {
|
||||
@ -121,30 +146,49 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const invoiceStore = useInvoiceStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
const invoice = ref(null);
|
||||
const loading = ref(true);
|
||||
const updating = ref(false);
|
||||
const error = ref(null);
|
||||
const dropdownOpen = ref(false);
|
||||
const selectedStatus = ref("brouillon");
|
||||
|
||||
onMounted(async () => {
|
||||
const load = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const fetchedInvoice = await invoiceStore.fetchInvoice(props.invoiceId);
|
||||
invoice.value = fetchedInvoice;
|
||||
invoice.value = await invoiceStore.fetchInvoice(props.invoiceId);
|
||||
selectedStatus.value = invoice.value?.status || "brouillon";
|
||||
} catch (e) {
|
||||
error.value = "Impossible de charger la facture.";
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return "-";
|
||||
return new Date(dateString).toLocaleDateString("fr-FR");
|
||||
const reload = () => load();
|
||||
onMounted(load);
|
||||
|
||||
/* ── Helpers ── */
|
||||
const formatDate = (d) =>
|
||||
d
|
||||
? new Date(d).toLocaleDateString("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})
|
||||
: "—";
|
||||
|
||||
const formatCurrency = (value) => {
|
||||
const amount = Number(value || 0);
|
||||
return new Intl.NumberFormat("fr-FR", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
minimumFractionDigits: 2,
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const availableStatuses = [
|
||||
@ -158,8 +202,7 @@ const availableStatuses = [
|
||||
"avoir",
|
||||
];
|
||||
|
||||
const getStatusLabel = (status) => {
|
||||
const labels = {
|
||||
const statusLabels = {
|
||||
brouillon: "Brouillon",
|
||||
emise: "Émise",
|
||||
envoyee: "Envoyée",
|
||||
@ -169,40 +212,129 @@ const getStatusLabel = (status) => {
|
||||
annulee: "Annulée",
|
||||
avoir: "Avoir",
|
||||
};
|
||||
return labels[status] || status;
|
||||
|
||||
const statusIcons = {
|
||||
brouillon: "fas fa-pencil-alt",
|
||||
emise: "fas fa-file-export",
|
||||
envoyee: "fas fa-paper-plane",
|
||||
partiellement_payee: "fas fa-coins",
|
||||
payee: "fas fa-check-circle",
|
||||
echue: "fas fa-clock",
|
||||
annulee: "fas fa-ban",
|
||||
avoir: "fas fa-undo",
|
||||
};
|
||||
|
||||
/* eslint-disable require-atomic-updates */
|
||||
const changeStatus = async (newStatus) => {
|
||||
if (!invoice.value?.id) return;
|
||||
const statusBadgeColor = (status) => {
|
||||
const map = {
|
||||
brouillon: "warning",
|
||||
emise: "info",
|
||||
envoyee: "info",
|
||||
partiellement_payee: "warning",
|
||||
payee: "success",
|
||||
echue: "danger",
|
||||
annulee: "dark",
|
||||
avoir: "secondary",
|
||||
};
|
||||
return map[status] || "secondary";
|
||||
};
|
||||
|
||||
const currentInvoiceId = invoice.value.id;
|
||||
const getStatusLabel = (s) => statusLabels[s] || s;
|
||||
const statusIcon = (s) => statusIcons[s] || "fas fa-circle";
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const updated = await invoiceStore.updateInvoice({
|
||||
id: currentInvoiceId,
|
||||
status: newStatus,
|
||||
});
|
||||
const onStatusSelect = (event) => {
|
||||
const newStatus = event.target.value;
|
||||
selectedStatus.value = newStatus;
|
||||
if (!invoice.value?.id || newStatus === invoice.value.status) return;
|
||||
changeStatus(invoice.value.id, newStatus);
|
||||
};
|
||||
|
||||
if (invoice.value?.id === currentInvoiceId) {
|
||||
/* ── Status Update ── */
|
||||
const changeStatus = (id, newStatus) => {
|
||||
if (!id || updating.value) return;
|
||||
updating.value = true;
|
||||
invoiceStore
|
||||
.updateInvoice({ id, status: newStatus })
|
||||
.then((updated) => {
|
||||
if (`${props.invoiceId}` !== `${id}`) return;
|
||||
invoice.value = updated;
|
||||
|
||||
selectedStatus.value = updated?.status || newStatus;
|
||||
notificationStore.success(
|
||||
"Statut mis à jour",
|
||||
`La facture est maintenant "${getStatusLabel(newStatus)}"`,
|
||||
3000
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to update status", e);
|
||||
notificationStore.error(
|
||||
"Erreur",
|
||||
"Impossible de mettre à jour le statut",
|
||||
3000
|
||||
);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
notificationStore.error("Erreur", "Impossible de mettre à jour le statut", 3000);
|
||||
})
|
||||
.finally(() => {
|
||||
updating.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ── States ── */
|
||||
.detail-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
color: #8898aa;
|
||||
font-size: .9rem;
|
||||
gap: .6rem;
|
||||
}
|
||||
|
||||
.detail-state--error i {
|
||||
font-size: 2.5rem;
|
||||
color: #f5365c;
|
||||
}
|
||||
|
||||
.spinner-ring {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border: 3px solid #e9ecef;
|
||||
border-top-color: #5e72e4;
|
||||
border-radius: 50%;
|
||||
animation: spin .8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.btn-retry {
|
||||
margin-top: .25rem;
|
||||
padding: .4rem 1.1rem;
|
||||
border-radius: 8px;
|
||||
border: 1.5px solid #f5365c;
|
||||
background: none;
|
||||
color: #f5365c;
|
||||
font-size: .8rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: background .15s;
|
||||
}
|
||||
.btn-retry:hover { background: #fff0f3; }
|
||||
|
||||
.inv-visual {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #2dce89, #11cdef);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 1.3rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.inv-status-select-wrap {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.inv-status-select {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div>
|
||||
<h6 class="mb-3">Suivi de la Facture</h6>
|
||||
<div class="timeline-scrollable">
|
||||
<div class="timeline timeline-one-side">
|
||||
<div
|
||||
v-for="(item, index) in history"
|
||||
:key="index"
|
||||
class="timeline-block mb-3"
|
||||
>
|
||||
<span class="timeline-step">
|
||||
<i :class="getStatusIcon(item.new_status)"></i>
|
||||
</span>
|
||||
<div class="timeline-content">
|
||||
<h6 class="text-dark text-sm font-weight-bold mb-0">
|
||||
{{ getStatusLabel(item.new_status) }}
|
||||
</h6>
|
||||
<p class="text-secondary font-weight-bold text-xs mt-1 mb-0">
|
||||
{{ formatDate(item.changed_at) }}
|
||||
<span v-if="item.changed_by">par {{ item.changed_by }}</span>
|
||||
</p>
|
||||
<p v-if="item.comment" class="text-sm mt-2 mb-0">
|
||||
{{ item.comment }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="history.length === 0" class="text-sm text-secondary">
|
||||
Aucun historique disponible.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
history: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return "-";
|
||||
const date = new Date(dateString);
|
||||
const options = {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
};
|
||||
return date.toLocaleDateString("fr-FR", options);
|
||||
};
|
||||
|
||||
const getStatusIcon = (status) => {
|
||||
const map = {
|
||||
brouillon: "ni ni-bell-55 text-secondary",
|
||||
emise: "ni ni-send text-info",
|
||||
envoyee: "ni ni-email-83 text-info",
|
||||
partiellement_payee: "ni ni-money-coins text-warning",
|
||||
payee: "ni ni-check-bold text-success text-gradient",
|
||||
echue: "ni ni-time-alarm text-danger",
|
||||
annulee: "ni ni-fat-remove text-danger text-gradient",
|
||||
avoir: "ni ni-archive-2 text-dark",
|
||||
};
|
||||
return map[status] || "ni ni-bell-55 text-secondary";
|
||||
};
|
||||
|
||||
const getStatusLabel = (status) => {
|
||||
const labels = {
|
||||
brouillon: "Facture créée (Brouillon)",
|
||||
emise: "Facture émise",
|
||||
envoyee: "Facture envoyée",
|
||||
partiellement_payee: "Partiellement payée",
|
||||
payee: "Facture payée",
|
||||
echue: "Facture échue",
|
||||
annulee: "Facture annulée",
|
||||
avoir: "Avoir généré",
|
||||
};
|
||||
return labels[status] || status;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.timeline-scrollable {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.timeline-scrollable::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.timeline-scrollable::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.timeline-scrollable::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.timeline-scrollable::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
</style>
|
||||
@ -1,42 +1,41 @@
|
||||
<template>
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header p-3 pb-0">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-3 pt-0">
|
||||
<hr class="horizontal dark mt-0 mb-4" />
|
||||
|
||||
<!-- Product Lines Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<slot name="lines"></slot>
|
||||
<div class="col-lg-6 col-md-6 col-12">
|
||||
<slot name="product"></slot>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 col-md-6 col-12 my-auto text-end">
|
||||
<slot name="cta"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="horizontal dark mt-4 mb-4" />
|
||||
|
||||
<div class="row">
|
||||
<!-- Tracking/Timeline Section -->
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-3 col-md-6 col-12">
|
||||
<slot name="timeline"></slot>
|
||||
</div>
|
||||
|
||||
<!-- Billing Info Section -->
|
||||
<div class="col-lg-5 col-md-6 col-12">
|
||||
<slot name="billing"></slot>
|
||||
<slot name="payment"></slot>
|
||||
</div>
|
||||
|
||||
<!-- Summary Section -->
|
||||
<div class="col-lg-3 col-12 ms-auto">
|
||||
<div class="col-lg-4 col-12 ms-auto">
|
||||
<slot name="summary"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-3">
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -128,7 +128,6 @@ watch(
|
||||
|
||||
// Load data on component mount
|
||||
onMounted(() => {
|
||||
console.log("test");
|
||||
fetchIntervention();
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user