Ventes et modules transverses: devis, factures, messages, stats et webmailing
This commit is contained in:
parent
dc87b0f720
commit
11750a3ffc
@ -9,9 +9,7 @@
|
|||||||
<small class="text-muted">Communiquez avec votre équipe</small>
|
<small class="text-muted">Communiquez avec votre équipe</small>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="badge bg-info">
|
<span class="badge bg-info"> {{ unreadCount }} non lu(s) </span>
|
||||||
{{ unreadCount }} non lu(s)
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -22,19 +20,21 @@
|
|||||||
<button
|
<button
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
:class="{ active: activeTab === 'inbox' }"
|
:class="{ active: activeTab === 'inbox' }"
|
||||||
@click="activeTab = 'inbox'"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
@click="activeTab = 'inbox'"
|
||||||
>
|
>
|
||||||
<i class="fas fa-inbox"></i> Réception
|
<i class="fas fa-inbox"></i> Réception
|
||||||
<span class="badge bg-danger ms-2" v-if="unreadCount > 0">{{ unreadCount }}</span>
|
<span v-if="unreadCount > 0" class="badge bg-danger ms-2">{{
|
||||||
|
unreadCount
|
||||||
|
}}</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button
|
<button
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
:class="{ active: activeTab === 'compose' }"
|
:class="{ active: activeTab === 'compose' }"
|
||||||
@click="activeTab = 'compose'"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
@click="activeTab = 'compose'"
|
||||||
>
|
>
|
||||||
<i class="fas fa-pen"></i> Nouveau message
|
<i class="fas fa-pen"></i> Nouveau message
|
||||||
</button>
|
</button>
|
||||||
@ -43,8 +43,8 @@
|
|||||||
<button
|
<button
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
:class="{ active: activeTab === 'sent' }"
|
:class="{ active: activeTab === 'sent' }"
|
||||||
@click="activeTab = 'sent'"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
@click="activeTab = 'sent'"
|
||||||
>
|
>
|
||||||
<i class="fas fa-paper-plane"></i> Envoyés
|
<i class="fas fa-paper-plane"></i> Envoyés
|
||||||
</button>
|
</button>
|
||||||
@ -72,17 +72,10 @@
|
|||||||
@form-data-change="updateNewMessage"
|
@form-data-change="updateNewMessage"
|
||||||
/>
|
/>
|
||||||
<div class="mt-4 d-flex justify-content-end gap-2">
|
<div class="mt-4 d-flex justify-content-end gap-2">
|
||||||
<soft-button
|
<soft-button color="secondary" variant="outline" @click="resetForm">
|
||||||
color="secondary"
|
|
||||||
variant="outline"
|
|
||||||
@click="resetForm"
|
|
||||||
>
|
|
||||||
<i class="fas fa-redo"></i> Réinitialiser
|
<i class="fas fa-redo"></i> Réinitialiser
|
||||||
</soft-button>
|
</soft-button>
|
||||||
<soft-button
|
<soft-button color="success" @click="sendMessage">
|
||||||
color="success"
|
|
||||||
@click="sendMessage"
|
|
||||||
>
|
|
||||||
<i class="fas fa-check"></i> Envoyer
|
<i class="fas fa-check"></i> Envoyer
|
||||||
</soft-button>
|
</soft-button>
|
||||||
</div>
|
</div>
|
||||||
@ -260,7 +253,9 @@ const sendMessage = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipient = users.value.find((u) => u.id === parseInt(newMessage.value.recipientId));
|
const recipient = users.value.find(
|
||||||
|
(u) => u.id === parseInt(newMessage.value.recipientId)
|
||||||
|
);
|
||||||
|
|
||||||
const sentMessage = {
|
const sentMessage = {
|
||||||
id: sent.value.length + 101,
|
id: sent.value.length + 101,
|
||||||
|
|||||||
@ -6,37 +6,54 @@
|
|||||||
</soft-button>
|
</soft-button>
|
||||||
</template>
|
</template>
|
||||||
<template #header-pagination>
|
<template #header-pagination>
|
||||||
<div class="d-flex justify-content-center" v-if="pagination && pagination.last_page > 1">
|
<div
|
||||||
<soft-pagination color="success" size="sm">
|
v-if="pagination && pagination.last_page > 1"
|
||||||
<soft-pagination-item
|
class="d-flex justify-content-center"
|
||||||
prev
|
>
|
||||||
:disabled="pagination.current_page <= 1"
|
<soft-pagination color="success" size="sm">
|
||||||
@click="changePage(pagination.current_page - 1)"
|
<soft-pagination-item
|
||||||
/>
|
prev
|
||||||
|
:disabled="pagination.current_page <= 1"
|
||||||
<soft-pagination-item
|
@click="changePage(pagination.current_page - 1)"
|
||||||
v-for="page in visiblePages"
|
/>
|
||||||
:key="page"
|
|
||||||
:label="page.toString()"
|
<soft-pagination-item
|
||||||
:active="pagination.current_page === page"
|
v-for="page in visiblePages"
|
||||||
@click="changePage(page)"
|
:key="page"
|
||||||
/>
|
:label="page.toString()"
|
||||||
|
:active="pagination.current_page === page"
|
||||||
<soft-pagination-item
|
@click="changePage(page)"
|
||||||
next
|
/>
|
||||||
:disabled="pagination.current_page >= pagination.last_page"
|
|
||||||
@click="changePage(pagination.current_page + 1)"
|
<soft-pagination-item
|
||||||
/>
|
next
|
||||||
</soft-pagination>
|
:disabled="pagination.current_page >= pagination.last_page"
|
||||||
|
@click="changePage(pagination.current_page + 1)"
|
||||||
|
/>
|
||||||
|
</soft-pagination>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #select-filter>
|
<template #select-filter>
|
||||||
<soft-button color="dark" variant="outline" class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<soft-button
|
||||||
|
color="dark"
|
||||||
|
variant="outline"
|
||||||
|
class="dropdown-toggle"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
<i class="fas fa-filter me-2"></i> Filtrer
|
<i class="fas fa-filter me-2"></i> Filtrer
|
||||||
</soft-button>
|
</soft-button>
|
||||||
<ul class="dropdown-menu dropdown-menu-lg-start px-2 py-3">
|
<ul class="dropdown-menu dropdown-menu-lg-start px-2 py-3">
|
||||||
<li><a class="dropdown-item border-radius-md" href="javascript:;">Par date</a></li>
|
<li>
|
||||||
<li><a class="dropdown-item border-radius-md" href="javascript:;">Par statut</a></li>
|
<a class="dropdown-item border-radius-md" href="javascript:;"
|
||||||
|
>Par date</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item border-radius-md" href="javascript:;"
|
||||||
|
>Par statut</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
<template #intervention-other-action>
|
<template #intervention-other-action>
|
||||||
@ -104,7 +121,7 @@ const props = defineProps({
|
|||||||
pagination: {
|
pagination: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
@ -115,8 +132,9 @@ const go = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const changePage = (page) => {
|
const changePage = (page) => {
|
||||||
if (typeof page !== 'number') return;
|
if (typeof page !== "number") return;
|
||||||
if (page < 1 || (props.pagination && page > props.pagination.last_page)) return;
|
if (page < 1 || (props.pagination && page > props.pagination.last_page))
|
||||||
|
return;
|
||||||
if (page === props.pagination.current_page) return;
|
if (page === props.pagination.current_page) return;
|
||||||
emit("page-change", page);
|
emit("page-change", page);
|
||||||
};
|
};
|
||||||
@ -126,7 +144,11 @@ const visiblePages = computed(() => {
|
|||||||
const { current_page, last_page } = props.pagination;
|
const { current_page, last_page } = props.pagination;
|
||||||
const delta = 2;
|
const delta = 2;
|
||||||
const range = [];
|
const range = [];
|
||||||
for (let i = Math.max(2, current_page - delta); i <= Math.min(last_page - 1, current_page + delta); i++) {
|
for (
|
||||||
|
let i = Math.max(2, current_page - delta);
|
||||||
|
i <= Math.min(last_page - 1, current_page + delta);
|
||||||
|
i++
|
||||||
|
) {
|
||||||
range.push(i);
|
range.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,50 +164,56 @@ const visiblePages = computed(() => {
|
|||||||
if (last_page > 0) finalRange.push(1);
|
if (last_page > 0) finalRange.push(1);
|
||||||
// Simplified logic for now: just range around current if total pages is large, else all
|
// Simplified logic for now: just range around current if total pages is large, else all
|
||||||
if (last_page <= 7) {
|
if (last_page <= 7) {
|
||||||
return Array.from({length: last_page}, (_, i) => i + 1);
|
return Array.from({ length: last_page }, (_, i) => i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complex logic if needed, but for now let's do simple sliding window
|
// Complex logic if needed, but for now let's do simple sliding window
|
||||||
// [1] ... [current-1] [current] [current+1] ... [last]
|
// [1] ... [current-1] [current] [current+1] ... [last]
|
||||||
|
|
||||||
let pages = [1];
|
let pages = [1];
|
||||||
|
|
||||||
let start = Math.max(2, current_page - 1);
|
let start = Math.max(2, current_page - 1);
|
||||||
let end = Math.min(last_page - 1, current_page + 1);
|
let end = Math.min(last_page - 1, current_page + 1);
|
||||||
|
|
||||||
if (current_page <= 3) {
|
if (current_page <= 3) {
|
||||||
end = 4; // Show 1, 2, 3, 4 ... Last
|
end = 4; // Show 1, 2, 3, 4 ... Last
|
||||||
}
|
}
|
||||||
if (current_page >= last_page - 2) {
|
if (current_page >= last_page - 2) {
|
||||||
start = last_page - 3;
|
start = last_page - 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start > 2) {
|
if (start > 2) {
|
||||||
// no dots, just gap logic handled by UI usually, but here we return numbers
|
// no dots, just gap logic handled by UI usually, but here we return numbers
|
||||||
// SoftPaginationItem expects label, maybe I handle dots logic elsewhere?
|
// SoftPaginationItem expects label, maybe I handle dots logic elsewhere?
|
||||||
// Let's stick to simple window:
|
// Let's stick to simple window:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-implement simplified:
|
// Re-implement simplified:
|
||||||
const p = [];
|
const p = [];
|
||||||
const total = last_page;
|
const total = last_page;
|
||||||
if (total <= 7) {
|
if (total <= 7) {
|
||||||
for(let i=1; i<=total; i++) p.push(i);
|
for (let i = 1; i <= total; i++) p.push(i);
|
||||||
} else {
|
} else {
|
||||||
p.push(1);
|
p.push(1);
|
||||||
if (current_page > 3) p.push('...');
|
if (current_page > 3) p.push("...");
|
||||||
|
|
||||||
let midStart = Math.max(2, current_page - 1);
|
let midStart = Math.max(2, current_page - 1);
|
||||||
let midEnd = Math.min(total - 1, current_page + 1);
|
let midEnd = Math.min(total - 1, current_page + 1);
|
||||||
|
|
||||||
// pinned logic adjustment
|
// pinned logic adjustment
|
||||||
if (current_page < 4) { midStart = 2; midEnd = 4; }
|
if (current_page < 4) {
|
||||||
if (current_page > total - 3) { midStart = total - 3; midEnd = total - 1; }
|
midStart = 2;
|
||||||
|
midEnd = 4;
|
||||||
for(let i=midStart; i<=midEnd; i++) p.push(i);
|
}
|
||||||
|
if (current_page > total - 3) {
|
||||||
if (current_page < total - 2) p.push('...');
|
midStart = total - 3;
|
||||||
p.push(total);
|
midEnd = total - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = midStart; i <= midEnd; i++) p.push(i);
|
||||||
|
|
||||||
|
if (current_page < total - 2) p.push("...");
|
||||||
|
p.push(total);
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,7 +23,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<h6 class="mb-3 text-sm">Historique</h6>
|
<h6 class="mb-3 text-sm">Historique</h6>
|
||||||
<div v-if="invoice.history && invoice.history.length > 0">
|
<div v-if="invoice.history && invoice.history.length > 0">
|
||||||
<div v-for="(entry, index) in invoice.history" :key="index" class="mb-2">
|
<div
|
||||||
|
v-for="(entry, index) in invoice.history"
|
||||||
|
:key="index"
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
<span class="text-xs text-secondary">
|
<span class="text-xs text-secondary">
|
||||||
{{ formatDate(entry.changed_at) }}
|
{{ formatDate(entry.changed_at) }}
|
||||||
</span>
|
</span>
|
||||||
@ -38,13 +42,15 @@
|
|||||||
<div>
|
<div>
|
||||||
<h6 class="mb-3 text-sm">Informations Client</h6>
|
<h6 class="mb-3 text-sm">Informations Client</h6>
|
||||||
<p class="text-sm mb-1">
|
<p class="text-sm mb-1">
|
||||||
<strong>{{ invoice.client ? invoice.client.name : 'Client inconnu' }}</strong>
|
<strong>{{
|
||||||
|
invoice.client ? invoice.client.name : "Client inconnu"
|
||||||
|
}}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-secondary mb-1">
|
<p class="text-xs text-secondary mb-1">
|
||||||
{{ invoice.client ? invoice.client.email : '' }}
|
{{ invoice.client ? invoice.client.email : "" }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-secondary mb-0">
|
<p class="text-xs text-secondary mb-0">
|
||||||
{{ invoice.client ? invoice.client.phone : '' }}
|
{{ invoice.client ? invoice.client.phone : "" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -68,17 +74,20 @@
|
|||||||
{{ getStatusLabel(invoice.status) }}
|
{{ getStatusLabel(invoice.status) }}
|
||||||
<i class="fas fa-chevron-down ms-2"></i>
|
<i class="fas fa-chevron-down ms-2"></i>
|
||||||
</soft-button>
|
</soft-button>
|
||||||
<ul
|
<ul
|
||||||
v-if="dropdownOpen"
|
v-if="dropdownOpen"
|
||||||
class="dropdown-menu show position-absolute"
|
class="dropdown-menu show position-absolute"
|
||||||
style="top: 100%; left: 0; z-index: 1000;"
|
style="top: 100%; left: 0; z-index: 1000"
|
||||||
>
|
>
|
||||||
<li v-for="status in availableStatuses" :key="status">
|
<li v-for="status in availableStatuses" :key="status">
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
:class="{ active: status === invoice.status }"
|
:class="{ active: status === invoice.status }"
|
||||||
href="javascript:;"
|
href="javascript:;"
|
||||||
@click="changeStatus(status); dropdownOpen = false;"
|
@click="
|
||||||
|
changeStatus(status);
|
||||||
|
dropdownOpen = false;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ getStatusLabel(status) }}
|
{{ getStatusLabel(status) }}
|
||||||
</a>
|
</a>
|
||||||
@ -178,9 +187,9 @@ const changeStatus = async (newStatus) => {
|
|||||||
|
|
||||||
if (invoice.value?.id === currentInvoiceId) {
|
if (invoice.value?.id === currentInvoiceId) {
|
||||||
invoice.value = updated;
|
invoice.value = updated;
|
||||||
|
|
||||||
notificationStore.success(
|
notificationStore.success(
|
||||||
'Statut mis à jour',
|
"Statut mis à jour",
|
||||||
`La facture est maintenant "${getStatusLabel(newStatus)}"`,
|
`La facture est maintenant "${getStatusLabel(newStatus)}"`,
|
||||||
3000
|
3000
|
||||||
);
|
);
|
||||||
@ -188,8 +197,8 @@ const changeStatus = async (newStatus) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to update status", e);
|
console.error("Failed to update status", e);
|
||||||
notificationStore.error(
|
notificationStore.error(
|
||||||
'Erreur',
|
"Erreur",
|
||||||
'Impossible de mettre à jour le statut',
|
"Impossible de mettre à jour le statut",
|
||||||
3000
|
3000
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -51,17 +51,20 @@
|
|||||||
{{ getStatusLabel(quote.status) }}
|
{{ getStatusLabel(quote.status) }}
|
||||||
<i class="fas fa-chevron-down ms-2"></i>
|
<i class="fas fa-chevron-down ms-2"></i>
|
||||||
</soft-button>
|
</soft-button>
|
||||||
<ul
|
<ul
|
||||||
v-if="dropdownOpen"
|
v-if="dropdownOpen"
|
||||||
class="dropdown-menu show position-absolute"
|
class="dropdown-menu show position-absolute"
|
||||||
style="top: 100%; left: 0; z-index: 1000;"
|
style="top: 100%; left: 0; z-index: 1000"
|
||||||
>
|
>
|
||||||
<li v-for="status in availableStatuses" :key="status">
|
<li v-for="status in availableStatuses" :key="status">
|
||||||
<a
|
<a
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
:class="{ active: status === quote.status }"
|
:class="{ active: status === quote.status }"
|
||||||
href="javascript:;"
|
href="javascript:;"
|
||||||
@click="changeStatus(status); dropdownOpen = false;"
|
@click="
|
||||||
|
changeStatus(status);
|
||||||
|
dropdownOpen = false;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ getStatusLabel(status) }}
|
{{ getStatusLabel(status) }}
|
||||||
</a>
|
</a>
|
||||||
@ -161,10 +164,10 @@ const changeStatus = async (newStatus) => {
|
|||||||
// Only update if we're still viewing the same quote
|
// Only update if we're still viewing the same quote
|
||||||
if (quote.value?.id === currentQuoteId) {
|
if (quote.value?.id === currentQuoteId) {
|
||||||
quote.value = updated;
|
quote.value = updated;
|
||||||
|
|
||||||
// Show success notification
|
// Show success notification
|
||||||
notificationStore.success(
|
notificationStore.success(
|
||||||
'Statut mis à jour',
|
"Statut mis à jour",
|
||||||
`Le devis est maintenant "${getStatusLabel(newStatus)}"`,
|
`Le devis est maintenant "${getStatusLabel(newStatus)}"`,
|
||||||
3000
|
3000
|
||||||
);
|
);
|
||||||
@ -172,12 +175,12 @@ const changeStatus = async (newStatus) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to update status", e);
|
console.error("Failed to update status", e);
|
||||||
notificationStore.error(
|
notificationStore.error(
|
||||||
'Erreur',
|
"Erreur",
|
||||||
'Impossible de mettre à jour le statut',
|
"Impossible de mettre à jour le statut",
|
||||||
3000
|
3000
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -113,9 +113,7 @@ const practitioners = ref([
|
|||||||
const applyFilter = (filterData) => {
|
const applyFilter = (filterData) => {
|
||||||
filterStartDate.value = filterData.startDate;
|
filterStartDate.value = filterData.startDate;
|
||||||
filterEndDate.value = filterData.endDate;
|
filterEndDate.value = filterData.endDate;
|
||||||
alert(
|
alert(`Filtre appliqué: ${filterData.startDate} à ${filterData.endDate}`);
|
||||||
`Filtre appliqué: ${filterData.startDate} à ${filterData.endDate}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportPDF = () => {
|
const exportPDF = () => {
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
<template #webmailing-header>
|
<template #webmailing-header>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-0">
|
<h3 class="mb-0"><i class="fas fa-envelope"></i> Webmailing</h3>
|
||||||
<i class="fas fa-envelope"></i> Webmailing
|
<small class="text-muted"
|
||||||
</h3>
|
>Gérez vos campagnes d'email marketing</small
|
||||||
<small class="text-muted">Gérez vos campagnes d'email marketing</small>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -17,8 +17,8 @@
|
|||||||
<button
|
<button
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
:class="{ active: activeTab === 'compose' }"
|
:class="{ active: activeTab === 'compose' }"
|
||||||
@click="activeTab = 'compose'"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
@click="activeTab = 'compose'"
|
||||||
>
|
>
|
||||||
<i class="fas fa-pen"></i> Composer
|
<i class="fas fa-pen"></i> Composer
|
||||||
</button>
|
</button>
|
||||||
@ -27,8 +27,8 @@
|
|||||||
<button
|
<button
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
:class="{ active: activeTab === 'history' }"
|
:class="{ active: activeTab === 'history' }"
|
||||||
@click="activeTab = 'history'"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
@click="activeTab = 'history'"
|
||||||
>
|
>
|
||||||
<i class="fas fa-history"></i> Historique
|
<i class="fas fa-history"></i> Historique
|
||||||
</button>
|
</button>
|
||||||
@ -45,17 +45,10 @@
|
|||||||
@form-data-change="updateFormData"
|
@form-data-change="updateFormData"
|
||||||
/>
|
/>
|
||||||
<div class="mt-4 d-flex justify-content-end gap-2">
|
<div class="mt-4 d-flex justify-content-end gap-2">
|
||||||
<soft-button
|
<soft-button color="secondary" variant="outline" @click="resetForm">
|
||||||
color="secondary"
|
|
||||||
variant="outline"
|
|
||||||
@click="resetForm"
|
|
||||||
>
|
|
||||||
<i class="fas fa-redo"></i> Réinitialiser
|
<i class="fas fa-redo"></i> Réinitialiser
|
||||||
</soft-button>
|
</soft-button>
|
||||||
<soft-button
|
<soft-button color="success" @click="sendEmail">
|
||||||
color="success"
|
|
||||||
@click="sendEmail"
|
|
||||||
>
|
|
||||||
<i class="fas fa-paper-plane"></i> Envoyer
|
<i class="fas fa-paper-plane"></i> Envoyer
|
||||||
</soft-button>
|
</soft-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,21 +8,13 @@
|
|||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
>
|
>
|
||||||
<option value="">-- Sélectionner un type --</option>
|
<option value="">-- Sélectionner un type --</option>
|
||||||
<option value="text">
|
<option value="text"><i class="fas fa-comment"></i> Texte</option>
|
||||||
<i class="fas fa-comment"></i> Texte
|
|
||||||
</option>
|
|
||||||
<option value="phone">
|
<option value="phone">
|
||||||
<i class="fas fa-phone"></i> Appel téléphonique
|
<i class="fas fa-phone"></i> Appel téléphonique
|
||||||
</option>
|
</option>
|
||||||
<option value="email">
|
<option value="email"><i class="fas fa-envelope"></i> Email</option>
|
||||||
<i class="fas fa-envelope"></i> Email
|
<option value="meeting"><i class="fas fa-calendar"></i> Réunion</option>
|
||||||
</option>
|
<option value="note"><i class="fas fa-sticky-note"></i> Note</option>
|
||||||
<option value="meeting">
|
|
||||||
<i class="fas fa-calendar"></i> Réunion
|
|
||||||
</option>
|
|
||||||
<option value="note">
|
|
||||||
<i class="fas fa-sticky-note"></i> Note
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -22,7 +22,11 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<select v-model="localPeriod" class="form-select" @change="handlePeriodChange">
|
<select
|
||||||
|
v-model="localPeriod"
|
||||||
|
class="form-select"
|
||||||
|
@change="handlePeriodChange"
|
||||||
|
>
|
||||||
<option value="">-- Période personnalisée --</option>
|
<option value="">-- Période personnalisée --</option>
|
||||||
<option value="today">Aujourd'hui</option>
|
<option value="today">Aujourd'hui</option>
|
||||||
<option value="week">Cette semaine</option>
|
<option value="week">Cette semaine</option>
|
||||||
|
|||||||
@ -54,9 +54,7 @@ const trendClass = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const trendIcon = computed(() => {
|
const trendIcon = computed(() => {
|
||||||
return props.trendPositive
|
return props.trendPositive ? "fas fa-arrow-up" : "fas fa-arrow-down";
|
||||||
? "fas fa-arrow-up"
|
|
||||||
: "fas fa-arrow-down";
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,9 @@
|
|||||||
multiple
|
multiple
|
||||||
@change="handleFileChange"
|
@change="handleFileChange"
|
||||||
/>
|
/>
|
||||||
<small class="text-muted">Formats acceptés: PDF, DOC, DOCX, XLS, XLSX, JPG, PNG</small>
|
<small class="text-muted"
|
||||||
|
>Formats acceptés: PDF, DOC, DOCX, XLS, XLSX, JPG, PNG</small
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -24,15 +24,9 @@
|
|||||||
class="form-select"
|
class="form-select"
|
||||||
@change="emitFormData"
|
@change="emitFormData"
|
||||||
>
|
>
|
||||||
<option value="low">
|
<option value="low"><i class="fas fa-arrow-down"></i> Basse</option>
|
||||||
<i class="fas fa-arrow-down"></i> Basse
|
<option value="normal"><i class="fas fa-minus"></i> Normale</option>
|
||||||
</option>
|
<option value="high"><i class="fas fa-arrow-up"></i> Haute</option>
|
||||||
<option value="normal">
|
|
||||||
<i class="fas fa-minus"></i> Normale
|
|
||||||
</option>
|
|
||||||
<option value="high">
|
|
||||||
<i class="fas fa-arrow-up"></i> Haute
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,10 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-section mb-4">
|
<div class="form-section mb-4">
|
||||||
<message-content
|
<message-content v-model="formData.content" @blur="emitFormData" />
|
||||||
v-model="formData.content"
|
|
||||||
@blur="emitFormData"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
|
|||||||
@ -4,7 +4,11 @@
|
|||||||
<i class="fas fa-info-circle"></i> Aucun message
|
<i class="fas fa-info-circle"></i> Aucun message
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-for="message in messages" :key="message.id" class="message-item mb-3">
|
<div
|
||||||
|
v-for="message in messages"
|
||||||
|
:key="message.id"
|
||||||
|
class="message-item mb-3"
|
||||||
|
>
|
||||||
<div class="message-header">
|
<div class="message-header">
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<div>
|
<div>
|
||||||
@ -14,19 +18,15 @@
|
|||||||
{{ getTypeLabel(message.type) }}
|
{{ getTypeLabel(message.type) }}
|
||||||
</span>
|
</span>
|
||||||
</h6>
|
</h6>
|
||||||
<small class="text-muted">{{ formatDate(message.createdDate) }}</small>
|
<small class="text-muted">{{
|
||||||
|
formatDate(message.createdDate)
|
||||||
|
}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<span
|
<span v-if="message.priority === 'high'" class="badge bg-danger">
|
||||||
v-if="message.priority === 'high'"
|
|
||||||
class="badge bg-danger"
|
|
||||||
>
|
|
||||||
<i class="fas fa-exclamation-circle"></i> Haute priorité
|
<i class="fas fa-exclamation-circle"></i> Haute priorité
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span v-if="message.isUrgent" class="badge bg-warning">
|
||||||
v-if="message.isUrgent"
|
|
||||||
class="badge bg-warning"
|
|
||||||
>
|
|
||||||
<i class="fas fa-fire"></i> Urgent
|
<i class="fas fa-fire"></i> Urgent
|
||||||
</span>
|
</span>
|
||||||
<span :class="getStatusClass(message.read)">
|
<span :class="getStatusClass(message.read)">
|
||||||
@ -57,13 +57,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-actions mt-2">
|
<div class="message-actions mt-2">
|
||||||
<button class="btn btn-sm btn-outline-primary" @click="markAsRead(message.id)">
|
<button
|
||||||
|
class="btn btn-sm btn-outline-primary"
|
||||||
|
@click="markAsRead(message.id)"
|
||||||
|
>
|
||||||
<i class="fas fa-envelope-open"></i> Marquer comme lu
|
<i class="fas fa-envelope-open"></i> Marquer comme lu
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-info ms-2" @click="replyMessage(message.id)">
|
<button
|
||||||
|
class="btn btn-sm btn-outline-info ms-2"
|
||||||
|
@click="replyMessage(message.id)"
|
||||||
|
>
|
||||||
<i class="fas fa-reply"></i> Répondre
|
<i class="fas fa-reply"></i> Répondre
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-danger ms-2" @click="deleteMessage(message.id)">
|
<button
|
||||||
|
class="btn btn-sm btn-outline-danger ms-2"
|
||||||
|
@click="deleteMessage(message.id)"
|
||||||
|
>
|
||||||
<i class="fas fa-trash"></i> Supprimer
|
<i class="fas fa-trash"></i> Supprimer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,22 +3,34 @@
|
|||||||
<table class="table align-items-center mb-0">
|
<table class="table align-items-center mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">
|
<th
|
||||||
|
class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7"
|
||||||
|
>
|
||||||
Produit
|
Produit
|
||||||
</th>
|
</th>
|
||||||
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 ps-2">
|
<th
|
||||||
|
class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 ps-2"
|
||||||
|
>
|
||||||
Description
|
Description
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">
|
<th
|
||||||
|
class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7"
|
||||||
|
>
|
||||||
Quantité
|
Quantité
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">
|
<th
|
||||||
|
class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7"
|
||||||
|
>
|
||||||
Prix Unit.
|
Prix Unit.
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">
|
<th
|
||||||
|
class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7"
|
||||||
|
>
|
||||||
Remise
|
Remise
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">
|
<th
|
||||||
|
class="text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7"
|
||||||
|
>
|
||||||
Total HT
|
Total HT
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -26,22 +38,34 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(line, index) in lines" :key="index">
|
<tr v-for="(line, index) in lines" :key="index">
|
||||||
<td>
|
<td>
|
||||||
<span class="text-xs font-weight-bold">{{ line.product_name || 'Produit' }}</span>
|
<span class="text-xs font-weight-bold">{{
|
||||||
|
line.product_name || "Produit"
|
||||||
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="text-xs text-secondary">{{ line.description || '-' }}</span>
|
<span class="text-xs text-secondary">{{
|
||||||
|
line.description || "-"
|
||||||
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<span class="text-xs font-weight-bold">{{ line.units_qty || line.qty_base || 1 }}</span>
|
<span class="text-xs font-weight-bold">{{
|
||||||
|
line.units_qty || line.qty_base || 1
|
||||||
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<span class="text-xs font-weight-bold">{{ formatCurrency(line.unit_price) }}</span>
|
<span class="text-xs font-weight-bold">{{
|
||||||
|
formatCurrency(line.unit_price)
|
||||||
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<span class="text-xs font-weight-bold">{{ line.discount_pct || 0 }}%</span>
|
<span class="text-xs font-weight-bold"
|
||||||
|
>{{ line.discount_pct || 0 }}%</span
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<span class="text-xs font-weight-bold">{{ formatCurrency(line.total_ht) }}</span>
|
<span class="text-xs font-weight-bold">{{
|
||||||
|
formatCurrency(line.total_ht)
|
||||||
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="!lines || lines.length === 0">
|
<tr v-if="!lines || lines.length === 0">
|
||||||
|
|||||||
@ -12,7 +12,9 @@
|
|||||||
<hr class="horizontal dark my-2" />
|
<hr class="horizontal dark my-2" />
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<span class="text-sm font-weight-bold">Total TTC:</span>
|
<span class="text-sm font-weight-bold">Total TTC:</span>
|
||||||
<span class="text-sm font-weight-bold text-success">{{ formatCurrency(ttc) }}</span>
|
<span class="text-sm font-weight-bold text-success">{{
|
||||||
|
formatCurrency(ttc)
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -92,8 +92,8 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formatCurrency = (value) => {
|
const formatCurrency = (value) => {
|
||||||
const numberValue = typeof value === 'string' ? parseFloat(value) : value;
|
const numberValue = typeof value === "string" ? parseFloat(value) : value;
|
||||||
if (isNaN(numberValue)) return '0,00 €';
|
if (isNaN(numberValue)) return "0,00 €";
|
||||||
|
|
||||||
return new Intl.NumberFormat("fr-FR", {
|
return new Intl.NumberFormat("fr-FR", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
|
|||||||
@ -32,9 +32,9 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formatCurrency = (value) => {
|
const formatCurrency = (value) => {
|
||||||
const numberValue = typeof value === 'string' ? parseFloat(value) : value;
|
const numberValue = typeof value === "string" ? parseFloat(value) : value;
|
||||||
if (isNaN(numberValue)) return '0,00 €';
|
if (isNaN(numberValue)) return "0,00 €";
|
||||||
|
|
||||||
return new Intl.NumberFormat("fr-FR", {
|
return new Intl.NumberFormat("fr-FR", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "EUR",
|
currency: "EUR",
|
||||||
|
|||||||
@ -33,7 +33,12 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="satisfaction-stars">
|
<div class="satisfaction-stars">
|
||||||
<span v-for="i in 5" :key="i" class="star" :class="getStarClass(i, practitioner.satisfaction)">
|
<span
|
||||||
|
v-for="i in 5"
|
||||||
|
:key="i"
|
||||||
|
class="star"
|
||||||
|
:class="getStarClass(i, practitioner.satisfaction)"
|
||||||
|
>
|
||||||
<i class="fas fa-star"></i>
|
<i class="fas fa-star"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
<!-- Due Date -->
|
<!-- Due Date -->
|
||||||
<td class="font-weight-bold">
|
<td class="font-weight-bold">
|
||||||
<span class="my-2 text-xs" :class="getDueDateClass(invoice)">{{
|
<span class="my-2 text-xs" :class="getDueDateClass(invoice)">{{
|
||||||
formatDate(invoice.due_date) || '-'
|
formatDate(invoice.due_date) || "-"
|
||||||
}}</span>
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|||||||
@ -21,23 +21,24 @@
|
|||||||
|
|
||||||
<div class="form-section mb-4">
|
<div class="form-section mb-4">
|
||||||
<h5 class="mb-3">Contenu du message</h5>
|
<h5 class="mb-3">Contenu du message</h5>
|
||||||
<webmailing-body-input
|
<webmailing-body-input v-model="formData.body" @blur="validateBody" />
|
||||||
v-model="formData.body"
|
|
||||||
@blur="validateBody"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-section mb-4">
|
<div class="form-section mb-4">
|
||||||
<h5 class="mb-3">Pièces jointes</h5>
|
<h5 class="mb-3">Pièces jointes</h5>
|
||||||
<webmailing-attachment
|
<webmailing-attachment @files-selected="handleFilesSelected" />
|
||||||
@files-selected="handleFilesSelected"
|
|
||||||
/>
|
|
||||||
<div v-if="formData.attachments.length > 0" class="mt-3">
|
<div v-if="formData.attachments.length > 0" class="mt-3">
|
||||||
<h6>Fichiers sélectionnés:</h6>
|
<h6>Fichiers sélectionnés:</h6>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li v-for="file in formData.attachments" :key="file.name" class="mb-2">
|
<li
|
||||||
|
v-for="file in formData.attachments"
|
||||||
|
:key="file.name"
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
<span class="badge bg-info">{{ file.name }}</span>
|
<span class="badge bg-info">{{ file.name }}</span>
|
||||||
<small class="ms-2 text-muted">({{ formatFileSize(file.size) }})</small>
|
<small class="ms-2 text-muted"
|
||||||
|
>({{ formatFileSize(file.size) }})</small
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -129,7 +130,7 @@ const formatFileSize = (bytes) => {
|
|||||||
const k = 1024;
|
const k = 1024;
|
||||||
const sizes = ["Bytes", "KB", "MB", "GB"];
|
const sizes = ["Bytes", "KB", "MB", "GB"];
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,10 @@
|
|||||||
<button class="btn btn-sm btn-info" @click="viewEmail(email.id)">
|
<button class="btn btn-sm btn-info" @click="viewEmail(email.id)">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-danger ms-2" @click="deleteEmail(email.id)">
|
<button
|
||||||
|
class="btn btn-sm btn-danger ms-2"
|
||||||
|
@click="deleteEmail(email.id)"
|
||||||
|
>
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -215,7 +215,7 @@ watch(
|
|||||||
(newErrors) => {
|
(newErrors) => {
|
||||||
fieldErrors.value = { ...newErrors };
|
fieldErrors.value = { ...newErrors };
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Watch for success from parent
|
// Watch for success from parent
|
||||||
@ -225,7 +225,7 @@ watch(
|
|||||||
if (newSuccess) {
|
if (newSuccess) {
|
||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|||||||
@ -240,8 +240,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input
|
<input
|
||||||
id="isDefaultCheckbox"
|
id="isDefaultCheckbox"
|
||||||
|
|||||||
@ -61,7 +61,9 @@ export const TvaRateService = {
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteTvaRate(id: number): Promise<{ success: boolean; message: string }> {
|
async deleteTvaRate(
|
||||||
|
id: number
|
||||||
|
): Promise<{ success: boolean; message: string }> {
|
||||||
const response = await request<{ success: boolean; message: string }>({
|
const response = await request<{ success: boolean; message: string }>({
|
||||||
url: `/api/tva-rates/${id}`,
|
url: `/api/tva-rates/${id}`,
|
||||||
method: "delete",
|
method: "delete",
|
||||||
|
|||||||
@ -81,7 +81,9 @@ export const useInvoiceStore = defineStore("invoice", () => {
|
|||||||
return response;
|
return response;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err.response?.data?.message || err.message || "Failed to fetch invoices";
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Failed to fetch invoices";
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -124,7 +126,9 @@ export const useInvoiceStore = defineStore("invoice", () => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err.response?.data?.message || err.message || "Failed to create invoice";
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Failed to create invoice";
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -146,7 +150,9 @@ export const useInvoiceStore = defineStore("invoice", () => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err.response?.data?.message || err.message || "Failed to create invoice from quote";
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Failed to create invoice from quote";
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -174,14 +180,19 @@ export const useInvoiceStore = defineStore("invoice", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update current invoice if it's the one being edited
|
// Update current invoice if it's the one being edited
|
||||||
if (currentInvoice.value && currentInvoice.value.id === updatedInvoice.id) {
|
if (
|
||||||
|
currentInvoice.value &&
|
||||||
|
currentInvoice.value.id === updatedInvoice.id
|
||||||
|
) {
|
||||||
setCurrentInvoice(updatedInvoice);
|
setCurrentInvoice(updatedInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedInvoice;
|
return updatedInvoice;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err.response?.data?.message || err.message || "Failed to update invoice";
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Failed to update invoice";
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -210,7 +221,9 @@ export const useInvoiceStore = defineStore("invoice", () => {
|
|||||||
return response;
|
return response;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
err.response?.data?.message || err.message || "Failed to delete invoice";
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Failed to delete invoice";
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -6,7 +6,12 @@
|
|||||||
<div class="card-header pb-0 p-3">
|
<div class="card-header pb-0 p-3">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h6 class="mb-0">Nouvelle Facture</h6>
|
<h6 class="mb-0">Nouvelle Facture</h6>
|
||||||
<soft-button color="secondary" variant="outline" size="sm" @click="goBack">
|
<soft-button
|
||||||
|
color="secondary"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
@click="goBack"
|
||||||
|
>
|
||||||
<i class="fas fa-arrow-left me-2"></i>Retour
|
<i class="fas fa-arrow-left me-2"></i>Retour
|
||||||
</soft-button>
|
</soft-button>
|
||||||
</div>
|
</div>
|
||||||
@ -14,11 +19,16 @@
|
|||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
La création de facture directe sera disponible prochainement.
|
La création de facture directe sera disponible prochainement. Pour
|
||||||
Pour l'instant, vous pouvez créer une facture à partir d'un devis accepté.
|
l'instant, vous pouvez créer une facture à partir d'un devis
|
||||||
|
accepté.
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-4">
|
||||||
<soft-button color="success" variant="gradient" @click="goToQuotes">
|
<soft-button
|
||||||
|
color="success"
|
||||||
|
variant="gradient"
|
||||||
|
@click="goToQuotes"
|
||||||
|
>
|
||||||
<i class="fas fa-file-invoice me-2"></i>
|
<i class="fas fa-file-invoice me-2"></i>
|
||||||
Voir les Devis
|
Voir les Devis
|
||||||
</soft-button>
|
</soft-button>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user