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>
|
||||
<add-intervention-template>
|
||||
<template #intervention-form>
|
||||
<client-detail-template>
|
||||
<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
|
||||
:loading="loading"
|
||||
:validation-errors="validationErrors"
|
||||
@ -16,12 +107,14 @@
|
||||
@create-intervention="handleCreateIntervention"
|
||||
/>
|
||||
</template>
|
||||
</add-intervention-template>
|
||||
</client-detail-template>
|
||||
</template>
|
||||
<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 SoftBadge from "@/components/SoftBadge.vue";
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
|
||||
defineProps({
|
||||
loading: {
|
||||
@ -76,3 +169,57 @@ const handleCreateIntervention = (data) => {
|
||||
emit("createIntervention", data);
|
||||
};
|
||||
</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;
|
||||
|
||||
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,12 +1,29 @@
|
||||
<template>
|
||||
<div class="card p-3 border-radius-xl bg-white" data-animation="FadeIn">
|
||||
<h5 class="font-weight-bolder mb-0">Informations de l'intervention</h5>
|
||||
<p class="mb-0 text-sm">Créez une nouvelle intervention funéraire</p>
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="card intervention-panel-card h-100" data-animation="FadeIn">
|
||||
<div class="card-header pb-0 p-4">
|
||||
<div
|
||||
class="d-flex align-items-center justify-content-between flex-wrap gap-2"
|
||||
>
|
||||
<div>
|
||||
<h5 class="font-weight-bolder mb-1">
|
||||
Informations principales
|
||||
</h5>
|
||||
<p class="mb-0 text-sm text-muted">
|
||||
Identifiez le client, le défunt concerné et le type
|
||||
d'intervention.
|
||||
</p>
|
||||
</div>
|
||||
<soft-badge color="info" variant="gradient" size="sm">
|
||||
Champs essentiels
|
||||
</soft-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="multisteps-form__content">
|
||||
<!-- Client selection -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="card-body pt-3 p-4">
|
||||
<div class="mb-4">
|
||||
<label class="form-label"
|
||||
>Client <span class="text-danger">*</span></label
|
||||
>
|
||||
@ -19,10 +36,14 @@
|
||||
@search="handleSearch"
|
||||
@select="handleSelect"
|
||||
/>
|
||||
<div v-if="selectedItem" class="selected-item">
|
||||
Sélectionné: {{ selectedItem.name }} ({{
|
||||
<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"
|
||||
@ -31,11 +52,8 @@
|
||||
{{ fieldErrors.client_id }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deceased selection -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Personne décédée</label>
|
||||
<search-input
|
||||
v-model="selectedDeceased"
|
||||
@ -46,9 +64,15 @@
|
||||
@search="handleSearchDeceased"
|
||||
@select="handleSelectDeceased"
|
||||
/>
|
||||
<div v-if="selectedDeceased" class="selected-item">
|
||||
Sélectionné: {{ selectedDeceased.last_name }}
|
||||
<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"
|
||||
@ -57,17 +81,15 @@
|
||||
{{ fieldErrors.deceased_id }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Intervention type -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<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 multisteps-form__select"
|
||||
class="form-select soft-select"
|
||||
:class="{ 'is-invalid': fieldErrors.type }"
|
||||
>
|
||||
<option value="">Sélectionnez un type d'intervention</option>
|
||||
@ -78,92 +100,18 @@
|
||||
<option value="retrait_bijoux">Retrait bijoux</option>
|
||||
<option value="autre">Autre</option>
|
||||
</select>
|
||||
<div v-if="fieldErrors.type" class="invalid-feedback small-error">
|
||||
<div
|
||||
v-if="fieldErrors.type"
|
||||
class="invalid-feedback small-error"
|
||||
>
|
||||
{{ fieldErrors.type }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date et heure de l'intervention -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 col-sm-6">
|
||||
<label class="form-label">Date de l'intervention</label>
|
||||
<soft-input
|
||||
v-model="form.scheduled_date"
|
||||
class="multisteps-form__input"
|
||||
:class="{ 'is-invalid': fieldErrors.scheduled_at }"
|
||||
type="date"
|
||||
/>
|
||||
<div
|
||||
v-if="fieldErrors.scheduled_at"
|
||||
class="invalid-feedback small-error"
|
||||
>
|
||||
{{ fieldErrors.scheduled_at }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 mt-3 mt-sm-0">
|
||||
<label class="form-label">Heure de l'intervention</label>
|
||||
<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="row mt-3">
|
||||
<div class="col-12 col-sm-6">
|
||||
<label class="form-label">Durée (minutes)</label>
|
||||
<soft-input
|
||||
v-model="form.duration_min"
|
||||
class="multisteps-form__input"
|
||||
: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 mt-3 mt-sm-0">
|
||||
<label class="form-label">Statut</label>
|
||||
<select
|
||||
v-model="form.status"
|
||||
class="form-select multisteps-form__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>
|
||||
|
||||
<!-- Order giver and notes -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<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="multisteps-form__input"
|
||||
:class="{ 'is-invalid': fieldErrors.order_giver }"
|
||||
type="text"
|
||||
placeholder="Nom du donneur d'ordre"
|
||||
@ -176,60 +124,205 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-5">
|
||||
<div class="card intervention-panel-card h-100">
|
||||
<div class="card-header pb-0 p-4">
|
||||
<div
|
||||
class="d-flex align-items-center justify-content-between flex-wrap gap-2"
|
||||
>
|
||||
<div>
|
||||
<h5 class="font-weight-bolder mb-1">Planification</h5>
|
||||
<p class="mb-0 text-sm text-muted">
|
||||
Définissez la date, l'heure, la durée et l'état de suivi.
|
||||
</p>
|
||||
</div>
|
||||
<soft-badge color="success" variant="gradient" size="sm">
|
||||
Organisation
|
||||
</soft-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-3 p-4">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-sm-6">
|
||||
<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>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="card intervention-panel-card">
|
||||
<div class="card-header pb-0 p-4">
|
||||
<div
|
||||
class="d-flex align-items-center justify-content-between flex-wrap gap-2"
|
||||
>
|
||||
<div>
|
||||
<h5 class="font-weight-bolder mb-1">Observations</h5>
|
||||
<p class="mb-0 text-sm text-muted">
|
||||
Ajoutez le contexte utile pour l'équipe et la coordination
|
||||
terrain.
|
||||
</p>
|
||||
</div>
|
||||
<soft-badge color="dark" variant="gradient" size="sm">
|
||||
Notes métier
|
||||
</soft-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-3 p-4">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-12 col-xl-8">
|
||||
<label class="form-label">Notes et observations</label>
|
||||
<textarea
|
||||
v-model="form.notes"
|
||||
class="form-control multisteps-form__input"
|
||||
class="form-control soft-textarea"
|
||||
:class="{ 'is-invalid': fieldErrors.notes }"
|
||||
rows="4"
|
||||
rows="5"
|
||||
placeholder="Informations complémentaires, instructions spéciales..."
|
||||
maxlength="2000"
|
||||
></textarea>
|
||||
<div v-if="fieldErrors.notes" class="invalid-feedback small-error">
|
||||
<div
|
||||
v-if="fieldErrors.notes"
|
||||
class="invalid-feedback small-error"
|
||||
>
|
||||
{{ fieldErrors.notes }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-4">
|
||||
<div class="summary-panel">
|
||||
<p class="summary-title mb-3">Résumé rapide</p>
|
||||
<div class="summary-line">
|
||||
<span>Client</span>
|
||||
<strong>{{
|
||||
selectedItem?.name || "Non sélectionné"
|
||||
}}</strong>
|
||||
</div>
|
||||
<div class="summary-line">
|
||||
<span>Défunt</span>
|
||||
<strong>{{
|
||||
selectedDeceased
|
||||
? getDeceasedFullName(selectedDeceased)
|
||||
: "Non sélectionné"
|
||||
}}</strong>
|
||||
</div>
|
||||
<div class="summary-line">
|
||||
<span>Type</span>
|
||||
<strong>{{ form.type || "Non défini" }}</strong>
|
||||
</div>
|
||||
<div class="summary-line">
|
||||
<span>Statut</span>
|
||||
<strong>{{ form.status || "Non défini" }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Boutons -->
|
||||
<div class="button-row d-flex mt-4">
|
||||
<div
|
||||
class="button-row d-flex justify-content-end flex-wrap gap-2 mt-4"
|
||||
>
|
||||
<soft-button
|
||||
type="button"
|
||||
color="secondary"
|
||||
variant="outline"
|
||||
class="me-2 mb-0"
|
||||
class="mb-0"
|
||||
@click="resetForm"
|
||||
>
|
||||
Réinitialiser
|
||||
</soft-button>
|
||||
<soft-button
|
||||
type="button"
|
||||
color="dark"
|
||||
type="submit"
|
||||
color="success"
|
||||
variant="gradient"
|
||||
class="ms-auto mb-0"
|
||||
class="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" }}
|
||||
{{
|
||||
props.loading ? "Enregistrement..." : "Créer l'intervention"
|
||||
}}
|
||||
</soft-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, defineEmits, watch } from "vue";
|
||||
import SoftInput from "@/components/SoftInput.vue";
|
||||
import SoftButton from "@/components/SoftButton.vue";
|
||||
import SoftBadge from "@/components/SoftBadge.vue";
|
||||
import SearchInput from "@/components/atoms/input/SearchInput.vue";
|
||||
|
||||
// Props
|
||||
@ -484,9 +577,15 @@ const clearErrors = () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.intervention-panel-card {
|
||||
border: 0;
|
||||
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, 0.15);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #344767;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
@ -502,6 +601,21 @@ const clearErrors = () => {
|
||||
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 {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
@ -521,19 +635,57 @@ const clearErrors = () => {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.multisteps-form__select {
|
||||
.soft-select,
|
||||
.soft-textarea {
|
||||
background-color: white;
|
||||
border: 1px solid #d2d6da;
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 0.75rem;
|
||||
color: #495057;
|
||||
padding: 0.5rem 0.75rem;
|
||||
padding: 0.7rem 0.9rem;
|
||||
font-size: 0.875rem;
|
||||
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;
|
||||
box-shadow: 0 0 0 0.2rem rgba(94, 114, 228, 0.25);
|
||||
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>
|
||||
|
||||
@ -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>
|
||||
<div class="container-fluid py-4">
|
||||
<slot name="webmailing-header"></slot>
|
||||
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-body">
|
||||
<slot name="webmailing-tabs"></slot>
|
||||
<slot name="webmailing-content"></slot>
|
||||
</div>
|
||||
<section class="container-fluid py-4">
|
||||
<div class="card webmail-layout-card">
|
||||
<div class="webmail-shell__frame">
|
||||
<slot name="webmailing-sidebar"></slot>
|
||||
<slot name="webmailing-list"></slot>
|
||||
<slot name="webmailing-detail"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script></script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
.webmail-layout-card {
|
||||
border: 0;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 20px 30px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 2rem;
|
||||
.webmail-shell__frame {
|
||||
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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user