Integration page webmail dans front end
This commit is contained in:
parent
9951ed0ee6
commit
c4977bad15
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,97 @@
|
|||||||
<template>
|
<template>
|
||||||
<add-intervention-template>
|
<client-detail-template>
|
||||||
<template #intervention-form>
|
<template #button-return>
|
||||||
|
<div class="col-12">
|
||||||
|
<router-link
|
||||||
|
to="/interventions"
|
||||||
|
class="btn btn-outline-secondary btn-sm mb-3"
|
||||||
|
>
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Retour aux interventions
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #client-detail-sidebar>
|
||||||
|
<div class="card position-sticky top-1 intervention-sidebar-card">
|
||||||
|
<div class="card-body text-center p-4">
|
||||||
|
<div class="avatar avatar-xxl position-relative mx-auto mb-3">
|
||||||
|
<div
|
||||||
|
class="intervention-avatar w-100 border-radius-xl shadow-sm d-flex align-items-center justify-content-center"
|
||||||
|
>
|
||||||
|
<i class="fas fa-notes-medical text-white text-xl"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<soft-badge color="success" variant="gradient" size="sm" class="mb-3">
|
||||||
|
Nouvelle intervention
|
||||||
|
</soft-badge>
|
||||||
|
|
||||||
|
<h5 class="mb-1">Planifier une intervention</h5>
|
||||||
|
<p class="text-sm text-muted mb-0">
|
||||||
|
Préparez une fiche claire, liée au client et au défunt, dans le
|
||||||
|
style du dashboard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card intervention-sidebar-card mt-4">
|
||||||
|
<div class="card-header pb-0">
|
||||||
|
<h6 class="mb-1">Repères</h6>
|
||||||
|
<p class="text-sm text-muted mb-0">
|
||||||
|
Les éléments ci-dessous suffisent pour démarrer correctement.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-3">
|
||||||
|
<div class="check-item">
|
||||||
|
<span class="check-dot bg-gradient-success"></span>
|
||||||
|
<span class="text-sm">Associer le bon client</span>
|
||||||
|
</div>
|
||||||
|
<div class="check-item">
|
||||||
|
<span class="check-dot bg-gradient-info"></span>
|
||||||
|
<span class="text-sm">Définir le type d'intervention</span>
|
||||||
|
</div>
|
||||||
|
<div class="check-item">
|
||||||
|
<span class="check-dot bg-gradient-dark"></span>
|
||||||
|
<span class="text-sm">Renseigner la planification si connue</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #client-detail-content>
|
||||||
|
<div class="card intervention-hero-card mb-4">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div
|
||||||
|
class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
class="text-sm text-uppercase text-success font-weight-bold mb-2"
|
||||||
|
>
|
||||||
|
Dashboard intervention
|
||||||
|
</p>
|
||||||
|
<h4 class="mb-1">Créer une nouvelle intervention</h4>
|
||||||
|
<p class="text-sm text-muted mb-0">
|
||||||
|
Une mise en page plus propre, cohérente avec le reste de
|
||||||
|
l'application, centrée sur les informations métier utiles.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-pill-group">
|
||||||
|
<span class="hero-pill">
|
||||||
|
<i class="fas fa-user me-2 text-success"></i>Client
|
||||||
|
</span>
|
||||||
|
<span class="hero-pill">
|
||||||
|
<i class="fas fa-cross me-2 text-info"></i>Défunt
|
||||||
|
</span>
|
||||||
|
<span class="hero-pill">
|
||||||
|
<i class="fas fa-calendar-alt me-2 text-dark"></i>Planning
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<intervention-form
|
<intervention-form
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:validation-errors="validationErrors"
|
:validation-errors="validationErrors"
|
||||||
@ -16,12 +107,14 @@
|
|||||||
@create-intervention="handleCreateIntervention"
|
@create-intervention="handleCreateIntervention"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</add-intervention-template>
|
</client-detail-template>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import AddInterventionTemplate from "@/components/templates/Interventions/AddInterventionTemplate.vue";
|
import ClientDetailTemplate from "@/components/templates/CRM/ClientDetailTemplate.vue";
|
||||||
import InterventionForm from "@/components/molecules/Interventions/InterventionForm.vue";
|
import InterventionForm from "@/components/molecules/Interventions/InterventionForm.vue";
|
||||||
|
import SoftBadge from "@/components/SoftBadge.vue";
|
||||||
import { defineProps, defineEmits } from "vue";
|
import { defineProps, defineEmits } from "vue";
|
||||||
|
import { RouterLink } from "vue-router";
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
loading: {
|
loading: {
|
||||||
@ -76,3 +169,57 @@ const handleCreateIntervention = (data) => {
|
|||||||
emit("createIntervention", data);
|
emit("createIntervention", data);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.position-sticky {
|
||||||
|
top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intervention-sidebar-card,
|
||||||
|
.intervention-hero-card {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intervention-avatar {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: linear-gradient(135deg, #17c1e8 0%, #3a416f 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-item + .check-item {
|
||||||
|
margin-top: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
display: inline-block;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-pill-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.7rem 1rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(248, 249, 250, 0.95);
|
||||||
|
color: #344767;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -53,7 +53,9 @@ export default {
|
|||||||
|
|
||||||
activeValue = active ? `active` : null;
|
activeValue = active ? `active` : null;
|
||||||
|
|
||||||
return `${colorValue} ${sizeValue} ${fullWidthValue} ${activeValue}`;
|
return [colorValue, sizeValue, fullWidthValue, activeValue]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="form-group">
|
|
||||||
<label :for="id" class="form-label">
|
|
||||||
<i class="fas fa-paperclip"></i> Pièces jointes
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
:id="id"
|
|
||||||
type="file"
|
|
||||||
class="form-control"
|
|
||||||
multiple
|
|
||||||
@change="handleFileChange"
|
|
||||||
/>
|
|
||||||
<small class="text-muted"
|
|
||||||
>Formats acceptés: PDF, DOC, DOCX, XLS, XLSX, JPG, PNG</small
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits } from "vue";
|
|
||||||
const props = defineProps({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: "webmailing-attachments",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["files-selected"]);
|
|
||||||
|
|
||||||
const handleFileChange = (event) => {
|
|
||||||
const files = event.target.files;
|
|
||||||
const fileList = Array.from(files).map((file) => ({
|
|
||||||
name: file.name,
|
|
||||||
size: file.size,
|
|
||||||
type: file.type,
|
|
||||||
}));
|
|
||||||
emit("files-selected", fileList);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="form-group">
|
|
||||||
<textarea
|
|
||||||
:id="id"
|
|
||||||
v-model="localValue"
|
|
||||||
class="form-control"
|
|
||||||
:class="getClasses(error, success)"
|
|
||||||
placeholder="Contenu du message"
|
|
||||||
rows="8"
|
|
||||||
@blur="handleBlur"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, watch, defineEmits, defineProps } from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: "webmailing-body",
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "blur"]);
|
|
||||||
|
|
||||||
const localValue = ref(props.modelValue);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(newValue) => {
|
|
||||||
localValue.value = newValue;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(localValue, (newValue) => {
|
|
||||||
emit("update:modelValue", newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
const getClasses = (error, success) => {
|
|
||||||
if (error) {
|
|
||||||
return "is-invalid";
|
|
||||||
} else if (success) {
|
|
||||||
return "is-valid";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBlur = () => {
|
|
||||||
emit("blur");
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<soft-input
|
|
||||||
:id="id"
|
|
||||||
v-model="localValue"
|
|
||||||
type="text"
|
|
||||||
placeholder="Sujet du message"
|
|
||||||
:error="error"
|
|
||||||
:success="success"
|
|
||||||
@blur="handleBlur"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineEmits, defineProps } from "vue";
|
|
||||||
import { ref, watch } from "vue";
|
|
||||||
import SoftInput from "@/components/SoftInput.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: "webmailing-subject",
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "blur"]);
|
|
||||||
|
|
||||||
const localValue = ref(props.modelValue);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(newValue) => {
|
|
||||||
localValue.value = newValue;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(localValue, (newValue) => {
|
|
||||||
emit("update:modelValue", newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleBlur = () => {
|
|
||||||
emit("blur");
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@ -1,235 +1,328 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card p-3 border-radius-xl bg-white" data-animation="FadeIn">
|
<form @submit.prevent="submitForm">
|
||||||
<h5 class="font-weight-bolder mb-0">Informations de l'intervention</h5>
|
<div class="row g-4">
|
||||||
<p class="mb-0 text-sm">Créez une nouvelle intervention funéraire</p>
|
<div class="col-12 col-xl-7">
|
||||||
|
<div class="card intervention-panel-card h-100" data-animation="FadeIn">
|
||||||
<div class="multisteps-form__content">
|
<div class="card-header pb-0 p-4">
|
||||||
<!-- Client selection -->
|
<div
|
||||||
<div class="row mt-3">
|
class="d-flex align-items-center justify-content-between flex-wrap gap-2"
|
||||||
<div class="col-12">
|
>
|
||||||
<label class="form-label"
|
<div>
|
||||||
>Client <span class="text-danger">*</span></label
|
<h5 class="font-weight-bolder mb-1">
|
||||||
>
|
Informations principales
|
||||||
<search-input
|
</h5>
|
||||||
v-model="selectedItem"
|
<p class="mb-0 text-sm text-muted">
|
||||||
:search-action="props.searchClients"
|
Identifiez le client, le défunt concerné et le type
|
||||||
:min-chars="0"
|
d'intervention.
|
||||||
item-key="id"
|
</p>
|
||||||
item-label="name"
|
</div>
|
||||||
@search="handleSearch"
|
<soft-badge color="info" variant="gradient" size="sm">
|
||||||
@select="handleSelect"
|
Champs essentiels
|
||||||
/>
|
</soft-badge>
|
||||||
<div v-if="selectedItem" class="selected-item">
|
</div>
|
||||||
Sélectionné: {{ selectedItem.name }} ({{
|
|
||||||
selectedItem.email || "Pas d'email"
|
|
||||||
}})
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="fieldErrors.client_id"
|
<div class="card-body pt-3 p-4">
|
||||||
class="invalid-feedback small-error"
|
<div class="mb-4">
|
||||||
>
|
<label class="form-label"
|
||||||
{{ fieldErrors.client_id }}
|
>Client <span class="text-danger">*</span></label
|
||||||
|
>
|
||||||
|
<search-input
|
||||||
|
v-model="selectedItem"
|
||||||
|
:search-action="props.searchClients"
|
||||||
|
:min-chars="0"
|
||||||
|
item-key="id"
|
||||||
|
item-label="name"
|
||||||
|
@search="handleSearch"
|
||||||
|
@select="handleSelect"
|
||||||
|
/>
|
||||||
|
<div v-if="selectedItem" class="selection-chip mt-2">
|
||||||
|
<i class="fas fa-user me-2 text-success"></i>
|
||||||
|
<span>
|
||||||
|
{{ selectedItem.name }}
|
||||||
|
<small class="text-muted ms-1">{{
|
||||||
|
selectedItem.email || "Pas d'email"
|
||||||
|
}}</small>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="fieldErrors.client_id"
|
||||||
|
class="invalid-feedback small-error"
|
||||||
|
>
|
||||||
|
{{ fieldErrors.client_id }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label">Personne décédée</label>
|
||||||
|
<search-input
|
||||||
|
v-model="selectedDeceased"
|
||||||
|
:search-action="props.searchDeceased"
|
||||||
|
:min-chars="0"
|
||||||
|
item-key="id"
|
||||||
|
:item-label="getDeceasedFullName"
|
||||||
|
@search="handleSearchDeceased"
|
||||||
|
@select="handleSelectDeceased"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="selectedDeceased"
|
||||||
|
class="selection-chip mt-2 selection-chip-muted"
|
||||||
|
>
|
||||||
|
<i class="fas fa-cross me-2 text-info"></i>
|
||||||
|
<span>
|
||||||
|
{{ selectedDeceased.last_name }}
|
||||||
|
{{ selectedDeceased.first_name || "" }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="fieldErrors.deceased_id"
|
||||||
|
class="invalid-feedback small-error"
|
||||||
|
>
|
||||||
|
{{ fieldErrors.deceased_id }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<label class="form-label"
|
||||||
|
>Type d'intervention <span class="text-danger">*</span></label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="form.type"
|
||||||
|
class="form-select soft-select"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.type }"
|
||||||
|
>
|
||||||
|
<option value="">Sélectionnez un type d'intervention</option>
|
||||||
|
<option value="thanatopraxie">Thanatopraxie</option>
|
||||||
|
<option value="toilette_mortuaire">Toilette mortuaire</option>
|
||||||
|
<option value="exhumation">Exhumation</option>
|
||||||
|
<option value="retrait_pacemaker">Retrait pacemaker</option>
|
||||||
|
<option value="retrait_bijoux">Retrait bijoux</option>
|
||||||
|
<option value="autre">Autre</option>
|
||||||
|
</select>
|
||||||
|
<div
|
||||||
|
v-if="fieldErrors.type"
|
||||||
|
class="invalid-feedback small-error"
|
||||||
|
>
|
||||||
|
{{ fieldErrors.type }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-lg-6 mt-4 mt-lg-0">
|
||||||
|
<label class="form-label">Donneur d'ordre</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.order_giver"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.order_giver }"
|
||||||
|
type="text"
|
||||||
|
placeholder="Nom du donneur d'ordre"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="fieldErrors.order_giver"
|
||||||
|
class="invalid-feedback small-error"
|
||||||
|
>
|
||||||
|
{{ fieldErrors.order_giver }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Deceased selection -->
|
<div class="col-12 col-xl-5">
|
||||||
<div class="row mt-3">
|
<div class="card intervention-panel-card h-100">
|
||||||
<div class="col-12">
|
<div class="card-header pb-0 p-4">
|
||||||
<label class="form-label">Personne décédée</label>
|
<div
|
||||||
<search-input
|
class="d-flex align-items-center justify-content-between flex-wrap gap-2"
|
||||||
v-model="selectedDeceased"
|
>
|
||||||
:search-action="props.searchDeceased"
|
<div>
|
||||||
:min-chars="0"
|
<h5 class="font-weight-bolder mb-1">Planification</h5>
|
||||||
item-key="id"
|
<p class="mb-0 text-sm text-muted">
|
||||||
:item-label="getDeceasedFullName"
|
Définissez la date, l'heure, la durée et l'état de suivi.
|
||||||
@search="handleSearchDeceased"
|
</p>
|
||||||
@select="handleSelectDeceased"
|
</div>
|
||||||
/>
|
<soft-badge color="success" variant="gradient" size="sm">
|
||||||
<div v-if="selectedDeceased" class="selected-item">
|
Organisation
|
||||||
Sélectionné: {{ selectedDeceased.last_name }}
|
</soft-badge>
|
||||||
{{ selectedDeceased.first_name || "" }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="fieldErrors.deceased_id"
|
<div class="card-body pt-3 p-4">
|
||||||
class="invalid-feedback small-error"
|
<div class="row g-3">
|
||||||
>
|
<div class="col-12 col-sm-6">
|
||||||
{{ fieldErrors.deceased_id }}
|
<label class="form-label">Date de l'intervention</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.scheduled_date"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.scheduled_at }"
|
||||||
|
type="date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<label class="form-label">Heure de l'intervention</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.scheduled_time"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.scheduled_at }"
|
||||||
|
type="time"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="fieldErrors.scheduled_at"
|
||||||
|
class="col-12 invalid-feedback small-error"
|
||||||
|
>
|
||||||
|
{{ fieldErrors.scheduled_at }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<label class="form-label">Durée (minutes)</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.duration_min"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.duration_min }"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
placeholder="ex. 90"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="fieldErrors.duration_min"
|
||||||
|
class="invalid-feedback small-error"
|
||||||
|
>
|
||||||
|
{{ fieldErrors.duration_min }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<label class="form-label">Statut</label>
|
||||||
|
<select
|
||||||
|
v-model="form.status"
|
||||||
|
class="form-select soft-select"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.status }"
|
||||||
|
>
|
||||||
|
<option value="">Sélectionnez un statut</option>
|
||||||
|
<option value="demande">Demande</option>
|
||||||
|
<option value="planifie">Planifié</option>
|
||||||
|
<option value="en_cours">En cours</option>
|
||||||
|
<option value="termine">Terminé</option>
|
||||||
|
<option value="annule">Annulé</option>
|
||||||
|
</select>
|
||||||
|
<div
|
||||||
|
v-if="fieldErrors.status"
|
||||||
|
class="invalid-feedback small-error"
|
||||||
|
>
|
||||||
|
{{ fieldErrors.status }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Intervention type -->
|
<div class="col-12">
|
||||||
<div class="row mt-3">
|
<div class="card intervention-panel-card">
|
||||||
<div class="col-12">
|
<div class="card-header pb-0 p-4">
|
||||||
<label class="form-label"
|
<div
|
||||||
>Type d'intervention <span class="text-danger">*</span></label
|
class="d-flex align-items-center justify-content-between flex-wrap gap-2"
|
||||||
>
|
>
|
||||||
<select
|
<div>
|
||||||
v-model="form.type"
|
<h5 class="font-weight-bolder mb-1">Observations</h5>
|
||||||
class="form-select multisteps-form__select"
|
<p class="mb-0 text-sm text-muted">
|
||||||
:class="{ 'is-invalid': fieldErrors.type }"
|
Ajoutez le contexte utile pour l'équipe et la coordination
|
||||||
>
|
terrain.
|
||||||
<option value="">Sélectionnez un type d'intervention</option>
|
</p>
|
||||||
<option value="thanatopraxie">Thanatopraxie</option>
|
</div>
|
||||||
<option value="toilette_mortuaire">Toilette mortuaire</option>
|
<soft-badge color="dark" variant="gradient" size="sm">
|
||||||
<option value="exhumation">Exhumation</option>
|
Notes métier
|
||||||
<option value="retrait_pacemaker">Retrait pacemaker</option>
|
</soft-badge>
|
||||||
<option value="retrait_bijoux">Retrait bijoux</option>
|
</div>
|
||||||
<option value="autre">Autre</option>
|
|
||||||
</select>
|
|
||||||
<div v-if="fieldErrors.type" class="invalid-feedback small-error">
|
|
||||||
{{ fieldErrors.type }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Date et heure de l'intervention -->
|
<div class="card-body pt-3 p-4">
|
||||||
<div class="row mt-3">
|
<div class="row g-4 align-items-start">
|
||||||
<div class="col-12 col-sm-6">
|
<div class="col-12 col-xl-8">
|
||||||
<label class="form-label">Date de l'intervention</label>
|
<label class="form-label">Notes et observations</label>
|
||||||
<soft-input
|
<textarea
|
||||||
v-model="form.scheduled_date"
|
v-model="form.notes"
|
||||||
class="multisteps-form__input"
|
class="form-control soft-textarea"
|
||||||
:class="{ 'is-invalid': fieldErrors.scheduled_at }"
|
:class="{ 'is-invalid': fieldErrors.notes }"
|
||||||
type="date"
|
rows="5"
|
||||||
/>
|
placeholder="Informations complémentaires, instructions spéciales..."
|
||||||
<div
|
maxlength="2000"
|
||||||
v-if="fieldErrors.scheduled_at"
|
></textarea>
|
||||||
class="invalid-feedback small-error"
|
<div
|
||||||
>
|
v-if="fieldErrors.notes"
|
||||||
{{ fieldErrors.scheduled_at }}
|
class="invalid-feedback small-error"
|
||||||
</div>
|
>
|
||||||
</div>
|
{{ fieldErrors.notes }}
|
||||||
<div class="col-12 col-sm-6 mt-3 mt-sm-0">
|
</div>
|
||||||
<label class="form-label">Heure de l'intervention</label>
|
</div>
|
||||||
<soft-input
|
|
||||||
v-model="form.scheduled_time"
|
|
||||||
class="multisteps-form__input"
|
|
||||||
:class="{ 'is-invalid': fieldErrors.scheduled_at }"
|
|
||||||
type="time"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="fieldErrors.scheduled_at"
|
|
||||||
class="invalid-feedback small-error"
|
|
||||||
>
|
|
||||||
{{ fieldErrors.scheduled_at }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Duration and status -->
|
<div class="col-12 col-xl-4">
|
||||||
<div class="row mt-3">
|
<div class="summary-panel">
|
||||||
<div class="col-12 col-sm-6">
|
<p class="summary-title mb-3">Résumé rapide</p>
|
||||||
<label class="form-label">Durée (minutes)</label>
|
<div class="summary-line">
|
||||||
<soft-input
|
<span>Client</span>
|
||||||
v-model="form.duration_min"
|
<strong>{{
|
||||||
class="multisteps-form__input"
|
selectedItem?.name || "Non sélectionné"
|
||||||
:class="{ 'is-invalid': fieldErrors.duration_min }"
|
}}</strong>
|
||||||
type="number"
|
</div>
|
||||||
min="1"
|
<div class="summary-line">
|
||||||
placeholder="ex. 90"
|
<span>Défunt</span>
|
||||||
/>
|
<strong>{{
|
||||||
<div
|
selectedDeceased
|
||||||
v-if="fieldErrors.duration_min"
|
? getDeceasedFullName(selectedDeceased)
|
||||||
class="invalid-feedback small-error"
|
: "Non sélectionné"
|
||||||
>
|
}}</strong>
|
||||||
{{ fieldErrors.duration_min }}
|
</div>
|
||||||
</div>
|
<div class="summary-line">
|
||||||
</div>
|
<span>Type</span>
|
||||||
<div class="col-12 col-sm-6 mt-3 mt-sm-0">
|
<strong>{{ form.type || "Non défini" }}</strong>
|
||||||
<label class="form-label">Statut</label>
|
</div>
|
||||||
<select
|
<div class="summary-line">
|
||||||
v-model="form.status"
|
<span>Statut</span>
|
||||||
class="form-select multisteps-form__select"
|
<strong>{{ form.status || "Non défini" }}</strong>
|
||||||
:class="{ 'is-invalid': fieldErrors.status }"
|
</div>
|
||||||
>
|
</div>
|
||||||
<option value="">Sélectionnez un statut</option>
|
</div>
|
||||||
<option value="demande">Demande</option>
|
</div>
|
||||||
<option value="planifie">Planifié</option>
|
|
||||||
<option value="en_cours">En cours</option>
|
|
||||||
<option value="termine">Terminé</option>
|
|
||||||
<option value="annule">Annulé</option>
|
|
||||||
</select>
|
|
||||||
<div v-if="fieldErrors.status" class="invalid-feedback small-error">
|
|
||||||
{{ fieldErrors.status }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Order giver and notes -->
|
<div
|
||||||
<div class="row mt-3">
|
class="button-row d-flex justify-content-end flex-wrap gap-2 mt-4"
|
||||||
<div class="col-12">
|
>
|
||||||
<label class="form-label">Donneur d'ordre</label>
|
<soft-button
|
||||||
<soft-input
|
type="button"
|
||||||
v-model="form.order_giver"
|
color="secondary"
|
||||||
class="multisteps-form__input"
|
variant="outline"
|
||||||
:class="{ 'is-invalid': fieldErrors.order_giver }"
|
class="mb-0"
|
||||||
type="text"
|
@click="resetForm"
|
||||||
placeholder="Nom du donneur d'ordre"
|
>
|
||||||
/>
|
Réinitialiser
|
||||||
<div
|
</soft-button>
|
||||||
v-if="fieldErrors.order_giver"
|
<soft-button
|
||||||
class="invalid-feedback small-error"
|
type="submit"
|
||||||
>
|
color="success"
|
||||||
{{ fieldErrors.order_giver }}
|
variant="gradient"
|
||||||
|
class="mb-0"
|
||||||
|
:disabled="props.loading"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="props.loading"
|
||||||
|
class="spinner-border spinner-border-sm me-2"
|
||||||
|
role="status"
|
||||||
|
></span>
|
||||||
|
{{
|
||||||
|
props.loading ? "Enregistrement..." : "Créer l'intervention"
|
||||||
|
}}
|
||||||
|
</soft-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notes -->
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col-12">
|
|
||||||
<label class="form-label">Notes et observations</label>
|
|
||||||
<textarea
|
|
||||||
v-model="form.notes"
|
|
||||||
class="form-control multisteps-form__input"
|
|
||||||
:class="{ 'is-invalid': fieldErrors.notes }"
|
|
||||||
rows="4"
|
|
||||||
placeholder="Informations complémentaires, instructions spéciales..."
|
|
||||||
maxlength="2000"
|
|
||||||
></textarea>
|
|
||||||
<div v-if="fieldErrors.notes" class="invalid-feedback small-error">
|
|
||||||
{{ fieldErrors.notes }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Boutons -->
|
|
||||||
<div class="button-row d-flex mt-4">
|
|
||||||
<soft-button
|
|
||||||
type="button"
|
|
||||||
color="secondary"
|
|
||||||
variant="outline"
|
|
||||||
class="me-2 mb-0"
|
|
||||||
@click="resetForm"
|
|
||||||
>
|
|
||||||
Réinitialiser
|
|
||||||
</soft-button>
|
|
||||||
<soft-button
|
|
||||||
type="button"
|
|
||||||
color="dark"
|
|
||||||
variant="gradient"
|
|
||||||
class="ms-auto mb-0"
|
|
||||||
:disabled="props.loading"
|
|
||||||
@click="submitForm"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="props.loading"
|
|
||||||
class="spinner-border spinner-border-sm me-2"
|
|
||||||
role="status"
|
|
||||||
></span>
|
|
||||||
{{ props.loading ? "Enregistrement..." : "Enregistrer" }}
|
|
||||||
</soft-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineProps, defineEmits, watch } from "vue";
|
import { ref, defineProps, defineEmits, watch } from "vue";
|
||||||
import SoftInput from "@/components/SoftInput.vue";
|
import SoftInput from "@/components/SoftInput.vue";
|
||||||
import SoftButton from "@/components/SoftButton.vue";
|
import SoftButton from "@/components/SoftButton.vue";
|
||||||
|
import SoftBadge from "@/components/SoftBadge.vue";
|
||||||
import SearchInput from "@/components/atoms/input/SearchInput.vue";
|
import SearchInput from "@/components/atoms/input/SearchInput.vue";
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
@ -484,9 +577,15 @@ const clearErrors = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.intervention-panel-card {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
color: #344767;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-danger {
|
.text-danger {
|
||||||
@ -502,6 +601,21 @@ const clearErrors = () => {
|
|||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selection-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.55rem 0.85rem;
|
||||||
|
border-radius: 0.85rem;
|
||||||
|
background: rgba(45, 206, 137, 0.08);
|
||||||
|
color: #344767;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-chip-muted {
|
||||||
|
background: rgba(23, 193, 232, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.spinner-border-sm {
|
.spinner-border-sm {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
@ -521,19 +635,57 @@ const clearErrors = () => {
|
|||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.multisteps-form__select {
|
.soft-select,
|
||||||
|
.soft-textarea {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #d2d6da;
|
border: 1px solid #d2d6da;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.75rem;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.7rem 0.9rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
transition: all 0.15s ease-in-out;
|
transition: all 0.15s ease-in-out;
|
||||||
|
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.multisteps-form__select:focus {
|
.soft-select:focus,
|
||||||
|
.soft-textarea:focus {
|
||||||
border-color: #5e72e4;
|
border-color: #5e72e4;
|
||||||
box-shadow: 0 0 0 0.2rem rgba(94, 114, 228, 0.25);
|
box-shadow: 0 0 0 0.2rem rgba(94, 114, 228, 0.25);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summary-panel {
|
||||||
|
border-radius: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);
|
||||||
|
border: 1px solid rgba(52, 71, 103, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-title {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: #8392ab;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #67748e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-line + .summary-line {
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
padding-top: 0.85rem;
|
||||||
|
border-top: 1px solid rgba(52, 71, 103, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-line strong {
|
||||||
|
color: #344767;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,150 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="webmailing-form">
|
|
||||||
<div class="form-section mb-4">
|
|
||||||
<h5 class="mb-3">Destinataires</h5>
|
|
||||||
<soft-input
|
|
||||||
v-model="formData.recipients"
|
|
||||||
type="email"
|
|
||||||
placeholder="Entrez les adresses email (séparées par des virgules)"
|
|
||||||
icon="fas fa-envelope"
|
|
||||||
icon-dir="left"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section mb-4">
|
|
||||||
<h5 class="mb-3">Sujet</h5>
|
|
||||||
<webmailing-subject-input
|
|
||||||
v-model="formData.subject"
|
|
||||||
@blur="validateSubject"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section mb-4">
|
|
||||||
<h5 class="mb-3">Contenu du message</h5>
|
|
||||||
<webmailing-body-input v-model="formData.body" @blur="validateBody" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section mb-4">
|
|
||||||
<h5 class="mb-3">Pièces jointes</h5>
|
|
||||||
<webmailing-attachment @files-selected="handleFilesSelected" />
|
|
||||||
<div v-if="formData.attachments.length > 0" class="mt-3">
|
|
||||||
<h6>Fichiers sélectionnés:</h6>
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
<li
|
|
||||||
v-for="file in formData.attachments"
|
|
||||||
:key="file.name"
|
|
||||||
class="mb-2"
|
|
||||||
>
|
|
||||||
<span class="badge bg-info">{{ file.name }}</span>
|
|
||||||
<small class="ms-2 text-muted"
|
|
||||||
>({{ formatFileSize(file.size) }})</small
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section">
|
|
||||||
<h5 class="mb-3">Options</h5>
|
|
||||||
<div class="form-check mb-2">
|
|
||||||
<input
|
|
||||||
id="send-copy"
|
|
||||||
v-model="formData.sendCopy"
|
|
||||||
type="checkbox"
|
|
||||||
class="form-check-input"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="send-copy">
|
|
||||||
M'envoyer une copie
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
id="send-scheduled"
|
|
||||||
v-model="formData.scheduled"
|
|
||||||
type="checkbox"
|
|
||||||
class="form-check-input"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="send-scheduled">
|
|
||||||
Programmer l'envoi
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div v-if="formData.scheduled" class="mt-3">
|
|
||||||
<soft-input
|
|
||||||
v-model="formData.scheduledDate"
|
|
||||||
type="datetime-local"
|
|
||||||
placeholder="Date et heure d'envoi"
|
|
||||||
icon="fas fa-calendar"
|
|
||||||
icon-dir="left"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineEmits, defineProps } from "vue";
|
|
||||||
import { ref } from "vue";
|
|
||||||
import SoftInput from "@/components/SoftInput.vue";
|
|
||||||
import WebmailingSubjectInput from "@/components/atoms/Webmailing/WebmailingSubjectInput.vue";
|
|
||||||
import WebmailingBodyInput from "@/components/atoms/Webmailing/WebmailingBodyInput.vue";
|
|
||||||
import WebmailingAttachment from "@/components/atoms/Webmailing/WebmailingAttachment.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
initialData: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["form-data-change"]);
|
|
||||||
|
|
||||||
const formData = ref({
|
|
||||||
recipients: props.initialData.recipients || "",
|
|
||||||
subject: props.initialData.subject || "",
|
|
||||||
body: props.initialData.body || "",
|
|
||||||
attachments: props.initialData.attachments || [],
|
|
||||||
sendCopy: props.initialData.sendCopy || false,
|
|
||||||
scheduled: props.initialData.scheduled || false,
|
|
||||||
scheduledDate: props.initialData.scheduledDate || "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const validateSubject = () => {
|
|
||||||
// Add validation logic if needed
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateBody = () => {
|
|
||||||
// Add validation logic if needed
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFilesSelected = (files) => {
|
|
||||||
formData.value.attachments = files;
|
|
||||||
emitFormData();
|
|
||||||
};
|
|
||||||
|
|
||||||
const emitFormData = () => {
|
|
||||||
emit("form-data-change", formData.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatFileSize = (bytes) => {
|
|
||||||
if (bytes === 0) return "0 Bytes";
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.webmailing-form {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-section {
|
|
||||||
background-color: white;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border-left: 4px solid #17a2b8;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="webmailing-list">
|
|
||||||
<div v-if="emails.length === 0" class="alert alert-info">
|
|
||||||
<i class="fas fa-info-circle"></i> Aucun email envoyé
|
|
||||||
</div>
|
|
||||||
<div v-else class="table-responsive">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead class="table-light">
|
|
||||||
<tr>
|
|
||||||
<th>Destinataires</th>
|
|
||||||
<th>Sujet</th>
|
|
||||||
<th>Date d'envoi</th>
|
|
||||||
<th>Statut</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="email in emails" :key="email.id">
|
|
||||||
<td>
|
|
||||||
<small>{{ email.recipients }}</small>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>{{ email.subject }}</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<small>{{ formatDate(email.sentDate) }}</small>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span :class="getStatusClass(email.status)">
|
|
||||||
{{ getStatusLabel(email.status) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn btn-sm btn-info" @click="viewEmail(email.id)">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-danger ms-2"
|
|
||||||
@click="deleteEmail(email.id)"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits } from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
emails: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["view-email", "delete-email"]);
|
|
||||||
|
|
||||||
const formatDate = (date) => {
|
|
||||||
if (!date) return "-";
|
|
||||||
return new Date(date).toLocaleDateString("fr-FR", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusClass = (status) => {
|
|
||||||
const statusClasses = {
|
|
||||||
sent: "badge bg-success",
|
|
||||||
pending: "badge bg-warning",
|
|
||||||
failed: "badge bg-danger",
|
|
||||||
scheduled: "badge bg-info",
|
|
||||||
};
|
|
||||||
return statusClasses[status] || "badge bg-secondary";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusLabel = (status) => {
|
|
||||||
const statusLabels = {
|
|
||||||
sent: "Envoyé",
|
|
||||||
pending: "En attente",
|
|
||||||
failed: "Échoué",
|
|
||||||
scheduled: "Programmé",
|
|
||||||
};
|
|
||||||
return statusLabels[status] || "Inconnu";
|
|
||||||
};
|
|
||||||
|
|
||||||
const viewEmail = (id) => {
|
|
||||||
emit("view-email", id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteEmail = (id) => {
|
|
||||||
emit("delete-email", id);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.webmailing-list {
|
|
||||||
background-color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,25 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-fluid py-4">
|
<section class="container-fluid py-4">
|
||||||
<slot name="webmailing-header"></slot>
|
<div class="card webmail-layout-card">
|
||||||
|
<div class="webmail-shell__frame">
|
||||||
<div class="card shadow-lg">
|
<slot name="webmailing-sidebar"></slot>
|
||||||
<div class="card-body">
|
<slot name="webmailing-list"></slot>
|
||||||
<slot name="webmailing-tabs"></slot>
|
<slot name="webmailing-detail"></slot>
|
||||||
<slot name="webmailing-content"></slot>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script></script>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card {
|
.webmail-layout-card {
|
||||||
border: none;
|
border: 0;
|
||||||
border-radius: 12px;
|
border-radius: 1rem;
|
||||||
|
box-shadow: 0 20px 30px rgba(15, 23, 42, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.webmail-shell__frame {
|
||||||
padding: 2rem;
|
display: grid;
|
||||||
|
grid-template-columns: 220px 320px minmax(0, 1fr);
|
||||||
|
min-height: calc(100vh - 185px);
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.webmail-shell__frame {
|
||||||
|
grid-template-columns: 210px 300px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.webmail-shell__frame {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user