New-Thanasoft/thanasoft-front/src/components/Organism/Agenda/InterventionMultiStepModal.vue
2026-05-04 11:32:50 +03:00

1194 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
id="interventionModal"
ref="modalRef"
class="modal fade"
tabindex="-1"
aria-labelledby="interventionModalLabel"
aria-hidden="true"
>
<div
class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-xl"
>
<div class="modal-content border-0 shadow-lg">
<div class="modal-header border-0 pb-0">
<div>
<h5 id="interventionModalLabel" class="modal-title mb-1">
{{
isEditing ? "Modifier l'intervention" : "Nouvelle Intervention"
}}
</h5>
<p class="text-sm text-secondary mb-0">
{{
isEditing
? "Mettez à jour les informations ci-dessous"
: "Renseignez les informations de la prise en charge"
}}
</p>
</div>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body pt-3">
<div v-if="globalErrors.length" class="alert alert-danger py-2">
<div v-for="(err, idx) in globalErrors" :key="idx">{{ err }}</div>
</div>
<form @submit.prevent="handleSubmit">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header pb-0">
<p
class="text-xs text-uppercase text-secondary font-weight-bold mb-0"
>
1. Personnes concernées
</p>
</div>
<div class="card-body pt-3">
<div class="row g-4">
<div class="col-12 col-lg-6">
<div
class="d-flex justify-content-between align-items-center mb-2 gap-2 flex-wrap"
>
<label class="form-label mb-0">Défunt *</label>
<button
type="button"
class="btn btn-outline-secondary btn-sm mb-0"
@click="toggleDeceasedMode"
>
{{ deceasedForm.is_existing ? "Créer" : "Rechercher" }}
</button>
</div>
<div
v-if="deceasedForm.is_existing"
class="position-relative"
>
<div class="input-group">
<input
v-model="deceasedSearchQuery"
type="text"
class="form-control"
:class="{ 'is-invalid': hasError('deceased_id') }"
placeholder="Nom, prénom..."
@input="handleDeceasedSearch"
@focus="showDeceasedResults = true"
/>
<button
v-if="deceasedForm.id"
class="btn btn-outline-secondary mb-0"
type="button"
@click="clearDeceasedSelection"
>
Effacer
</button>
</div>
<small
v-if="getFieldError('deceased_id')"
class="text-danger d-block mt-1"
>
{{ getFieldError("deceased_id") }}
</small>
<div
v-if="
showDeceasedResults && deceasedSearchResults.length
"
class="position-absolute start-0 end-0 mt-2 card border-0 shadow-sm"
style="
z-index: 1060;
max-height: 16rem;
overflow-y: auto;
"
>
<div class="list-group list-group-flush">
<button
v-for="d in deceasedSearchResults"
:key="d.id"
type="button"
class="list-group-item list-group-item-action text-start"
@click="selectDeceased(d)"
>
<div class="fw-semibold text-sm">
{{ d.first_name }} {{ d.last_name }}
</div>
<small class="text-secondary">
{{ d.birth_date }} - {{ d.death_date }}
</small>
</button>
</div>
</div>
</div>
<div v-else class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Nom *</label>
<input
v-model="deceasedForm.last_name"
class="form-control"
:class="{
'is-invalid': hasError('deceased.last_name'),
}"
placeholder="Dupont"
/>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Prénom</label>
<input
v-model="deceasedForm.first_name"
class="form-control"
placeholder="Jean"
/>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Naissance</label>
<input
v-model="deceasedForm.birth_date"
type="date"
class="form-control"
/>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Décès</label>
<input
v-model="deceasedForm.death_date"
type="date"
class="form-control"
/>
</div>
</div>
<small
v-if="getFieldError('deceased.last_name')"
class="text-danger d-block mt-1"
>
{{ getFieldError("deceased.last_name") }}
</small>
</div>
<div class="col-12 col-lg-6">
<label class="form-label">Client (Donneur d'ordre) *</label>
<div class="position-relative">
<div class="input-group">
<input
v-model="clientSearchQuery"
type="text"
class="form-control"
:class="{ 'is-invalid': hasError('client') }"
placeholder="Nom, email..."
@input="handleClientSearch"
@focus="handleClientFocus"
/>
<button
v-if="selectedClient"
class="btn btn-outline-secondary mb-0"
type="button"
@click="clearClientSelection"
>
Effacer
</button>
</div>
<small
v-if="getFieldError('client')"
class="text-danger d-block mt-1"
>
{{ getFieldError("client") }}
</small>
<div
v-if="showClientResults"
class="position-absolute start-0 end-0 mt-2 card border-0 shadow-sm"
style="
z-index: 1060;
max-height: 16rem;
overflow-y: auto;
"
>
<div class="list-group list-group-flush">
<button
v-for="c in clientSearchResults"
:key="c.id"
type="button"
class="list-group-item list-group-item-action text-start"
@click="selectClient(c)"
>
<div class="fw-semibold text-sm">{{ c.name }}</div>
<small class="text-secondary">{{ c.email }}</small>
</button>
<div
v-if="!clientSearchResults.length"
class="list-group-item text-secondary text-sm"
>
Aucun client trouvé
</div>
<div class="list-group-item text-secondary text-xs">
Écrire le nom du client pour rechercher
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header pb-0">
<p
class="text-xs text-uppercase text-secondary font-weight-bold mb-0"
>
2. Planification
</p>
</div>
<div class="card-body pt-3">
<div class="row g-4">
<div class="col-12 col-lg-4">
<label class="form-label">Date et heure *</label>
<input
v-model="interventionForm.scheduled_at"
type="datetime-local"
class="form-control"
:class="{ 'is-invalid': hasError('scheduled_at') }"
required
/>
<small
v-if="getFieldError('scheduled_at')"
class="text-danger d-block mt-1"
>
{{ getFieldError("scheduled_at") }}
</small>
</div>
<div class="col-12 col-lg-4">
<label class="form-label">Type de soin *</label>
<div class="position-relative">
<div class="input-group">
<input
v-model="productSearchQuery"
type="text"
class="form-control"
:class="{ 'is-invalid': hasError('product_id') }"
placeholder="Rechercher un soin..."
@input="handleProductSearch"
@focus="handleProductFocus"
/>
<button
v-if="productForm.product_id"
class="btn btn-outline-secondary mb-0"
type="button"
@click="clearProductSelection"
>
Effacer
</button>
</div>
<small
v-if="getFieldError('product_id')"
class="text-danger d-block mt-1"
>
{{ getFieldError("product_id") }}
</small>
<div
v-if="showProductResults && productSearchResults.length"
class="position-absolute start-0 end-0 mt-2 card border-0 shadow-sm"
style="
z-index: 1060;
max-height: 16rem;
overflow-y: auto;
"
>
<div class="list-group list-group-flush">
<button
v-for="p in productSearchResults"
:key="p.id"
type="button"
class="list-group-item list-group-item-action text-start"
@click="selectProduct(p)"
>
<div class="fw-semibold text-sm">{{ p.nom }}</div>
<small v-if="p.price" class="text-secondary"
>{{ p.price }}€</small
>
</button>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-4">
<label class="form-label">Statut</label>
<select
v-model="interventionForm.status"
class="form-select"
>
<option value="demande">Demande</option>
<option value="planifie">Planifiée</option>
<option value="en_cours">En cours</option>
<option value="terminee">Terminée</option>
<option value="facturee">Facturée</option>
<option value="annule">Annulée</option>
</select>
</div>
<div class="col-12 col-lg-6">
<label class="form-label">Thanatopracteur salarié *</label>
<div class="position-relative">
<div class="input-group">
<input
v-model="practitionerSearchQuery"
type="text"
class="form-control"
:class="{
'is-invalid': hasError('assigned_practitioner_id'),
}"
placeholder="Rechercher par nom d'employé..."
@input="handlePractitionerSearch"
@focus="handlePractitionerFocus"
/>
<button
v-if="interventionForm.assigned_practitioner_id"
class="btn btn-outline-secondary mb-0"
type="button"
@click="clearPractitionerSelection"
>
Effacer
</button>
</div>
<small
v-if="getFieldError('assigned_practitioner_id')"
class="text-danger d-block mt-1"
>
{{ getFieldError("assigned_practitioner_id") }}
</small>
<div
v-if="showPractitionerResults"
class="position-absolute start-0 end-0 mt-2 card border-0 shadow-sm"
style="
z-index: 1060;
max-height: 16rem;
overflow-y: auto;
"
>
<div class="list-group list-group-flush">
<button
v-for="practitioner in practitionerSearchResults"
:key="practitioner.id"
type="button"
class="list-group-item list-group-item-action text-start"
@click="selectPractitioner(practitioner)"
>
<div class="fw-semibold text-sm">
{{ getPractitionerName(practitioner) }}
</div>
<small class="text-secondary">
{{ practitioner.employee?.email || "Employé" }}
</small>
</button>
<div
v-if="
practitionerSearchQuery.trim().length >= 2 &&
!practitionerSearchResults.length
"
class="list-group-item text-secondary text-sm"
>
Aucun employé trouvé
</div>
<div
v-else-if="
practitionerSearchQuery.trim().length < 2
"
class="list-group-item text-secondary text-sm"
>
Saisir au moins 2 caractères
</div>
</div>
</div>
</div>
<small
v-if="getFieldError('assigned_practitioner_id')"
class="text-danger d-block mt-1"
>
{{ getFieldError("assigned_practitioner_id") }}
</small>
</div>
<div class="col-12 col-lg-6">
<label class="form-label">Sous-traitant</label>
<select class="form-select" disabled>
<option>Bientôt disponible</option>
</select>
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header pb-0">
<p
class="text-xs text-uppercase text-secondary font-weight-bold mb-0"
>
3. Lieu & Documents
</p>
</div>
<div class="card-body pt-3">
<div class="row g-4">
<div class="col-12 col-lg-6">
<div
class="d-flex justify-content-between align-items-center mb-2 gap-2 flex-wrap"
>
<label class="form-label mb-0">Lieu d'intervention</label>
<button
type="button"
class="btn btn-outline-secondary btn-sm mb-0"
@click="toggleLocationMode"
>
{{
locationForm.is_existing ? "Nouveau" : "Rechercher"
}}
</button>
</div>
<div
v-if="locationForm.is_existing"
class="position-relative"
>
<div class="input-group">
<input
v-model="locationSearchQuery"
type="text"
class="form-control"
:class="{
'is-invalid':
hasError('location.name') && !locationForm.id,
}"
placeholder="Domicile, clinique..."
@input="handleLocationSearch"
@focus="showLocationResults = true"
/>
<button
v-if="locationForm.id"
class="btn btn-outline-secondary mb-0"
type="button"
@click="clearLocationSelection"
>
Effacer
</button>
</div>
<small
v-if="
getFieldError('location.name') && !locationForm.id
"
class="text-danger d-block mt-1"
>
{{ getFieldError("location.name") }}
</small>
<div
v-if="
showLocationResults && locationSearchResults.length
"
class="position-absolute start-0 end-0 mt-2 card border-0 shadow-sm"
style="
z-index: 1060;
max-height: 16rem;
overflow-y: auto;
"
>
<div class="list-group list-group-flush">
<button
v-for="loc in locationSearchResults"
:key="loc.id"
type="button"
class="list-group-item list-group-item-action text-start"
@click="selectLocation(loc)"
>
<div class="fw-semibold text-sm">
{{ loc.name }}
</div>
<small class="text-secondary">{{ loc.city }}</small>
</button>
</div>
</div>
</div>
<div v-else class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Nom du lieu *</label>
<input
v-model="locationForm.name"
class="form-control"
placeholder="Ex: Domicile"
:class="{ 'is-invalid': hasError('location.name') }"
/>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Ville</label>
<input
v-model="locationForm.city"
class="form-control"
placeholder="Ex: Paris"
/>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<label class="form-label">N° Prescription médicale</label>
<div class="input-group">
<input
v-model="interventionForm.medical_prescription_number"
type="text"
class="form-control"
placeholder="Ex: RX-2024-001"
/>
<button
class="btn btn-outline-secondary mb-0"
type="button"
@click="toggleVoiceInput('medical_prescription_number')"
>
Vocal
</button>
</div>
<small class="text-secondary d-block mt-1">
Optionnel - si fourni par la famille
</small>
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header pb-0">
<p
class="text-xs text-uppercase text-secondary font-weight-bold mb-0"
>
4. Observations
</p>
</div>
<div class="card-body pt-3">
<label class="form-label">Observations techniques</label>
<div class="input-group">
<textarea
v-model="interventionForm.notes"
class="form-control"
rows="4"
placeholder="Notes et observations sur l'intervention..."
></textarea>
<button
class="btn btn-outline-secondary mb-0"
type="button"
@click="toggleVoiceInput('notes')"
>
Vocal
</button>
</div>
</div>
</div>
<div
class="d-flex justify-content-between align-items-center gap-3 flex-wrap pt-2"
>
<div>
<p
class="text-xs text-uppercase text-secondary font-weight-bold mb-1"
>
Action rapide
</p>
<p class="text-sm text-secondary mb-0">
Vérifiez le client, le soin et l'intervenant avant validation.
</p>
</div>
<div class="d-flex gap-2 flex-wrap">
<SoftButton
type="button"
color="secondary"
variant="outline"
data-bs-dismiss="modal"
:disabled="submitting"
class="mb-0"
>
Annuler
</SoftButton>
<SoftButton
type="submit"
color="success"
variant="gradient"
:disabled="submitting"
class="mb-0"
>
<span
v-if="submitting"
class="spinner-border spinner-border-sm me-2"
role="status"
aria-hidden="true"
></span>
{{ isEditing ? "Mettre à jour" : "Créer l'intervention" }}
</SoftButton>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
ref,
watch,
defineProps,
defineEmits,
defineExpose,
onMounted,
} from "vue";
import { Modal } from "bootstrap";
import DeceasedService from "@/services/deceased";
import ProductService from "@/services/product";
import ClientLocationService from "@/services/client_location";
import { ClientService } from "@/services/client";
import ThanatopractitionerService from "@/services/thanatopractitioner";
import SoftButton from "@/components/SoftButton.vue";
const props = defineProps({
isEditing: { type: Boolean, default: false },
practitioners: { type: Array, default: () => [] },
initialDate: { type: String, default: "" },
});
const emit = defineEmits(["submit", "close"]);
const modalRef = ref(null);
let modalInstance = null;
const submitting = ref(false);
const globalErrors = ref([]);
const errors = ref([]);
const deceasedForm = ref({
id: null,
is_existing: true,
first_name: "",
last_name: "",
birth_date: "",
death_date: "",
});
const deceasedSearchQuery = ref("");
const deceasedSearchResults = ref([]);
const showDeceasedResults = ref(false);
let deceasedSearchTimeout;
const selectedClient = ref(null);
const clientSearchQuery = ref("");
const clientSearchResults = ref([]);
const showClientResults = ref(false);
const recentClients = ref([]);
let clientSearchTimeout;
const productForm = ref({ product_id: null });
const productSearchQuery = ref("");
const productSearchResults = ref([]);
const allProducts = ref([]);
const showProductResults = ref(false);
let productSearchTimeout;
const practitionerSearchQuery = ref("");
const practitionerSearchResults = ref([]);
const showPractitionerResults = ref(false);
let practitionerSearchTimeout;
const locationForm = ref({ id: null, is_existing: true, name: "", city: "" });
const locationSearchQuery = ref("");
const locationSearchResults = ref([]);
const showLocationResults = ref(false);
let locationSearchTimeout;
const interventionForm = ref({
scheduled_at: "",
status: "planifie",
assigned_practitioner_id: "",
notes: "",
medical_prescription_number: "",
order_giver: "",
type: "thanatopraxie",
});
onMounted(async () => {
if (modalRef.value) {
modalInstance = new Modal(modalRef.value);
modalRef.value.addEventListener("hidden.bs.modal", () => emit("close"));
}
if (props.initialDate) {
interventionForm.value.scheduled_at = props.initialDate;
} else {
const now = new Date();
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
interventionForm.value.scheduled_at = now.toISOString().slice(0, 16);
}
try {
const pService = new ProductService();
const response = await pService.getAllProducts({
per_page: 100,
is_intervention: true,
});
const products = Array.isArray(response?.data)
? response.data
: Array.isArray(response?.data?.data)
? response.data.data
: [];
allProducts.value = products;
productSearchResults.value = products;
} catch (e) {
console.error("Failed to load products", e);
}
});
const show = () => modalInstance?.show();
const hide = () => modalInstance?.hide();
const toggleDeceasedMode = () => {
deceasedForm.value.is_existing = !deceasedForm.value.is_existing;
if (!deceasedForm.value.is_existing) clearDeceasedSelection();
else {
deceasedSearchQuery.value = "";
deceasedSearchResults.value = [];
}
};
const handleDeceasedSearch = () => {
if (deceasedSearchTimeout) clearTimeout(deceasedSearchTimeout);
if (deceasedSearchQuery.value.length < 2) {
deceasedSearchResults.value = [];
return;
}
deceasedSearchTimeout = setTimeout(async () => {
try {
deceasedSearchResults.value = await DeceasedService.searchDeceased(
deceasedSearchQuery.value
);
} catch (e) {
console.error(e);
}
}, 300);
};
const selectDeceased = (d) => {
deceasedForm.value.id = d.id;
deceasedSearchQuery.value = `${d.first_name || ""} ${d.last_name}`;
deceasedSearchResults.value = [];
showDeceasedResults.value = false;
errors.value = errors.value.filter((e) => e.field !== "deceased_id");
};
const clearDeceasedSelection = () => {
deceasedForm.value.id = null;
deceasedSearchQuery.value = "";
Object.assign(deceasedForm.value, {
first_name: "",
last_name: "",
birth_date: "",
death_date: "",
});
deceasedSearchResults.value = [];
errors.value = errors.value.filter(
(e) => e.field !== "deceased_id" && e.field !== "deceased.last_name"
);
};
const handleClientSearch = () => {
showClientResults.value = true;
if (clientSearchTimeout) clearTimeout(clientSearchTimeout);
if (clientSearchQuery.value.length < 2) {
clientSearchResults.value = recentClients.value;
return;
}
clientSearchTimeout = setTimeout(async () => {
try {
clientSearchResults.value = await ClientService.searchClients(
clientSearchQuery.value
);
} catch (e) {
console.error(e);
}
}, 300);
};
const loadRecentClients = async () => {
try {
const response = await ClientService.getAllClients({
page: 1,
per_page: 5,
});
const clients = Array.isArray(response?.data)
? response.data
: Array.isArray(response?.data?.data)
? response.data.data
: [];
recentClients.value = clients;
} catch (e) {
console.error("Failed to load recent clients", e);
recentClients.value = [];
}
};
const handleClientFocus = async () => {
if (!recentClients.value.length) {
await loadRecentClients();
}
if (!clientSearchQuery.value || clientSearchQuery.value.length < 2) {
clientSearchResults.value = recentClients.value;
}
showClientResults.value = true;
};
const selectClient = (c) => {
selectedClient.value = c;
clientSearchQuery.value = c.name + (c.email ? ` (${c.email})` : "");
clientSearchResults.value = [];
showClientResults.value = false;
errors.value = errors.value.filter((e) => e.field !== "client");
};
const clearClientSelection = () => {
selectedClient.value = null;
clientSearchQuery.value = "";
clientSearchResults.value = recentClients.value;
errors.value = errors.value.filter((e) => e.field !== "client");
};
const handleProductSearch = () => {
showProductResults.value = true;
if (productSearchTimeout) clearTimeout(productSearchTimeout);
productSearchTimeout = setTimeout(async () => {
try {
const pService = new ProductService();
const response = await pService.getAllProducts({
per_page: 100,
is_intervention: true,
search: productSearchQuery.value || undefined,
});
const products = Array.isArray(response?.data)
? response.data
: Array.isArray(response?.data?.data)
? response.data.data
: [];
productSearchResults.value = products;
} catch (e) {
console.error(e);
productSearchResults.value = [];
}
}, 250);
};
const handleProductFocus = () => {
showProductResults.value = true;
if (!productSearchQuery.value) productSearchResults.value = allProducts.value;
else handleProductSearch();
};
const selectProduct = (p) => {
productForm.value.product_id = p.id;
productSearchQuery.value = p.nom;
showProductResults.value = false;
errors.value = errors.value.filter((e) => e.field !== "product_id");
if (!interventionForm.value.type)
interventionForm.value.type = "thanatopraxie";
};
const clearProductSelection = () => {
productForm.value.product_id = null;
productSearchQuery.value = "";
productSearchResults.value = allProducts.value;
errors.value = errors.value.filter((e) => e.field !== "product_id");
};
const getPractitionerName = (practitioner) => {
const firstName = practitioner?.employee?.first_name || "";
const lastName = practitioner?.employee?.last_name || "";
return `${firstName} ${lastName}`.trim() || `#${practitioner?.id || ""}`;
};
const handlePractitionerSearch = () => {
if (practitionerSearchTimeout) clearTimeout(practitionerSearchTimeout);
if (interventionForm.value.assigned_practitioner_id) {
interventionForm.value.assigned_practitioner_id = "";
}
if (practitionerSearchQuery.value.trim().length < 2) {
practitionerSearchResults.value = [];
showPractitionerResults.value = true;
return;
}
showPractitionerResults.value = true;
practitionerSearchTimeout = setTimeout(async () => {
try {
practitionerSearchResults.value = await ThanatopractitionerService.searchThanatopractitioners(
practitionerSearchQuery.value.trim()
);
} catch (error) {
console.error("Failed to search practitioners", error);
practitionerSearchResults.value = [];
}
}, 300);
};
const handlePractitionerFocus = () => {
showPractitionerResults.value = !interventionForm.value
.assigned_practitioner_id;
};
const selectPractitioner = (practitioner) => {
interventionForm.value.assigned_practitioner_id = practitioner.id;
practitionerSearchQuery.value = getPractitionerName(practitioner);
showPractitionerResults.value = false;
practitionerSearchResults.value = [];
errors.value = errors.value.filter(
(e) => e.field !== "assigned_practitioner_id"
);
};
const clearPractitionerSelection = () => {
interventionForm.value.assigned_practitioner_id = "";
practitionerSearchQuery.value = "";
practitionerSearchResults.value = [];
showPractitionerResults.value = false;
errors.value = errors.value.filter(
(e) => e.field !== "assigned_practitioner_id"
);
};
const toggleLocationMode = () => {
locationForm.value.is_existing = !locationForm.value.is_existing;
if (!locationForm.value.is_existing) clearLocationSelection();
else {
locationForm.value.name = "";
locationForm.value.city = "";
locationSearchQuery.value = "";
locationSearchResults.value = [];
}
errors.value = errors.value.filter((e) => e.field === "location.name");
};
const handleLocationSearch = () => {
if (locationSearchTimeout) clearTimeout(locationSearchTimeout);
if (locationSearchQuery.value.length < 2) {
locationSearchResults.value = [];
return;
}
locationSearchTimeout = setTimeout(async () => {
try {
const response = await ClientLocationService.getAllClientLocations({
search: locationSearchQuery.value,
per_page: 10,
});
locationSearchResults.value = response.data;
} catch (e) {
console.error(e);
}
}, 300);
};
const selectLocation = (loc) => {
locationForm.value.id = loc.id;
locationForm.value.name = loc.name;
locationForm.value.city = loc.city || "";
locationSearchQuery.value = loc.name + (loc.city ? ` (${loc.city})` : "");
showLocationResults.value = false;
locationSearchResults.value = [];
errors.value = errors.value.filter((e) => e.field !== "location.name");
};
const clearLocationSelection = () => {
Object.assign(locationForm.value, { id: null, name: "", city: "" });
locationSearchQuery.value = "";
locationSearchResults.value = [];
showLocationResults.value = false;
errors.value = errors.value.filter((e) => e.field !== "location.name");
};
const toggleVoiceInput = (field) =>
alert(`Saisie vocale pour ${field} Fonctionnalité à implémenter`);
const hasError = (field) => errors.value.some((e) => e.field === field);
const getFieldError = (field) =>
errors.value.find((e) => e.field === field)?.message || "";
const validate = () => {
errors.value = [];
let isValid = true;
if (deceasedForm.value.is_existing) {
if (!deceasedForm.value.id) {
errors.value.push({
field: "deceased_id",
message: "Veuillez sélectionner un défunt.",
});
isValid = false;
}
} else {
if (!deceasedForm.value.last_name) {
errors.value.push({
field: "deceased.last_name",
message: "Le nom du défunt est requis.",
});
isValid = false;
}
}
if (!selectedClient.value) {
errors.value.push({
field: "client",
message: "Veuillez sélectionner un client.",
});
isValid = false;
}
if (!interventionForm.value.scheduled_at) {
errors.value.push({ field: "scheduled_at", message: "Date obligatoire." });
isValid = false;
}
if (!productForm.value.product_id) {
errors.value.push({ field: "product_id", message: "Type de soin requis." });
isValid = false;
}
if (!interventionForm.value.assigned_practitioner_id) {
errors.value.push({
field: "assigned_practitioner_id",
message: "Intervenant requis.",
});
isValid = false;
}
if (locationForm.value.is_existing) {
if (!locationForm.value.id) {
errors.value.push({
field: "location.name",
message: "Veuillez sélectionner un lieu.",
});
isValid = false;
}
} else {
if (!locationForm.value.name) {
errors.value.push({
field: "location.name",
message: "Le nom du lieu est requis.",
});
isValid = false;
}
}
return isValid;
};
const handleSubmit = async () => {
if (submitting.value || !validate()) return;
submitting.value = true;
globalErrors.value = [];
try {
const formData = new FormData();
if (deceasedForm.value.is_existing && deceasedForm.value.id) {
formData.append("deceased_id", deceasedForm.value.id);
} else {
formData.append(
"deceased[first_name]",
deceasedForm.value.first_name || ""
);
formData.append(
"deceased[last_name]",
deceasedForm.value.last_name || ""
);
if (deceasedForm.value.birth_date)
formData.append("deceased[birth_date]", deceasedForm.value.birth_date);
if (deceasedForm.value.death_date)
formData.append("deceased[death_date]", deceasedForm.value.death_date);
}
if (selectedClient.value) {
formData.append("client_id", selectedClient.value.id);
if (selectedClient.value.name)
formData.append("client[name]", selectedClient.value.name);
if (selectedClient.value.email)
formData.append("client[email]", selectedClient.value.email);
if (selectedClient.value.phone)
formData.append("client[phone]", selectedClient.value.phone);
if (selectedClient.value.billing_address) {
const ba = selectedClient.value.billing_address;
if (ba.line1)
formData.append("client[billing_address_line1]", ba.line1);
if (ba.line2)
formData.append("client[billing_address_line2]", ba.line2);
if (ba.postal_code)
formData.append("client[billing_postal_code]", ba.postal_code);
if (ba.city) formData.append("client[billing_city]", ba.city);
if (ba.country_code)
formData.append("client[billing_country_code]", ba.country_code);
}
if (selectedClient.value.vat_number)
formData.append("client[vat_number]", selectedClient.value.vat_number);
if (selectedClient.value.siret)
formData.append("client[siret]", selectedClient.value.siret);
}
if (locationForm.value.is_existing && locationForm.value.id) {
formData.append("location_id", locationForm.value.id);
} else {
formData.append("location[name]", locationForm.value.name || "");
if (locationForm.value.city)
formData.append("location[city]", locationForm.value.city);
}
if (productForm.value.product_id)
formData.append("product_id", productForm.value.product_id);
if (interventionForm.value.assigned_practitioner_id) {
formData.append(
"intervention[principal_practitioner_id]",
interventionForm.value.assigned_practitioner_id
);
formData.append(
"intervention[practitioners][0]",
interventionForm.value.assigned_practitioner_id
);
}
Object.keys(interventionForm.value).forEach((key) => {
if (interventionForm.value[key] != null) {
let value = interventionForm.value[key];
if (key === "scheduled_at" && value) {
value = value.replace("T", " ");
if (value.length === 16) value += ":00";
}
formData.append(`intervention[${key}]`, value);
}
});
emit("submit", formData);
} catch (e) {
console.error(e);
globalErrors.value.push("Erreur lors de la préparation du formulaire.");
} finally {
submitting.value = false;
}
};
watch(
() => locationForm.value.name,
() => {
if (getFieldError("location.name"))
errors.value = errors.value.filter((e) => e.field !== "location.name");
}
);
watch(
() => interventionForm.value.assigned_practitioner_id,
() => {
if (getFieldError("assigned_practitioner_id"))
errors.value = errors.value.filter(
(e) => e.field !== "assigned_practitioner_id"
);
const practitioner = props.practitioners.find(
(item) => item.id === interventionForm.value.assigned_practitioner_id
);
if (practitioner) {
practitionerSearchQuery.value = getPractitionerName(practitioner);
} else if (!interventionForm.value.assigned_practitioner_id) {
practitionerSearchQuery.value = "";
}
}
);
defineExpose({ show, hide });
</script>