429 lines
11 KiB
Vue
429 lines
11 KiB
Vue
<template>
|
|
<div class="modal-intervention-form">
|
|
<!-- Client Selection -->
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
Client <span class="text-danger">*</span>
|
|
</label>
|
|
<modal-search
|
|
:search-action="searchClients"
|
|
:selected-item="selectedClient"
|
|
title="Rechercher un client"
|
|
search-label="Nom du client"
|
|
search-placeholder="Entrez le nom du client..."
|
|
item-key="id"
|
|
item-label="name"
|
|
:item-description="getClientDescription"
|
|
:item-meta="getClientMeta"
|
|
@select="handleClientSelect"
|
|
@confirm="handleClientConfirm"
|
|
/>
|
|
<div v-if="fieldErrors.client_id" class="invalid-feedback">
|
|
{{ fieldErrors.client_id }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Selected Client Display -->
|
|
<div v-if="selectedClient" class="selected-display mb-3">
|
|
<div class="alert alert-success">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>{{ selectedClient.name }}</strong>
|
|
<div class="text-sm text-muted">
|
|
{{ getClientDescription(selectedClient) }}
|
|
</div>
|
|
</div>
|
|
<span class="badge bg-success">Sélectionné</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Deceased Selection -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Personne décédée</label>
|
|
|
|
<!-- Selected Deceased Display -->
|
|
<div v-if="selectedDeceased" class="selected-display mb-2">
|
|
<div class="alert alert-info">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>{{ getDeceasedFullName(selectedDeceased) }}</strong>
|
|
<div class="text-sm text-muted">
|
|
{{
|
|
selectedDeceased.birth_date
|
|
? `Né(e) le ${formatDate(selectedDeceased.birth_date)}`
|
|
: "Date de naissance inconnue"
|
|
}}
|
|
</div>
|
|
</div>
|
|
<span class="badge bg-info">Pré-sélectionné</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search for deceased if needed -->
|
|
<div v-if="!selectedDeceased">
|
|
<modal-search
|
|
:search-action="searchDeceased"
|
|
:selected-item="selectedDeceased"
|
|
title="Rechercher une personne décédée"
|
|
search-label="Nom de la personne"
|
|
search-placeholder="Entrez le nom de la personne..."
|
|
item-key="id"
|
|
:item-label="getDeceasedFullName"
|
|
:item-description="getDeceasedDescription"
|
|
:item-meta="getDeceasedMeta"
|
|
@select="handleDeceasedSelect"
|
|
@confirm="handleDeceasedConfirm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Intervention Type -->
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
Type d'intervention <span class="text-danger">*</span>
|
|
</label>
|
|
<select
|
|
v-model="form.type"
|
|
class="form-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">
|
|
{{ fieldErrors.type }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Date and Time -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Date de l'intervention</label>
|
|
<input
|
|
v-model="form.scheduled_date"
|
|
type="date"
|
|
class="form-control"
|
|
:class="{ 'is-invalid': fieldErrors.scheduled_at }"
|
|
/>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Heure de l'intervention</label>
|
|
<input
|
|
v-model="form.scheduled_time"
|
|
type="time"
|
|
class="form-control"
|
|
:class="{ 'is-invalid': fieldErrors.scheduled_at }"
|
|
/>
|
|
</div>
|
|
<div v-if="fieldErrors.scheduled_at" class="invalid-feedback">
|
|
{{ fieldErrors.scheduled_at }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Duration and Status -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Durée (minutes)</label>
|
|
<input
|
|
v-model="form.duration_min"
|
|
type="number"
|
|
class="form-control"
|
|
placeholder="ex. 90"
|
|
min="1"
|
|
/>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Statut</label>
|
|
<select v-model="form.status" class="form-select">
|
|
<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>
|
|
</div>
|
|
|
|
<!-- Order Giver -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Donneur d'ordre</label>
|
|
<input
|
|
v-model="form.order_giver"
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="Nom du donneur d'ordre"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Notes -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Notes et observations</label>
|
|
<textarea
|
|
v-model="form.notes"
|
|
class="form-control"
|
|
rows="3"
|
|
placeholder="Informations complémentaires, instructions spéciales..."
|
|
maxlength="2000"
|
|
></textarea>
|
|
</div>
|
|
|
|
<!-- Form Validation Errors -->
|
|
<div v-if="formValidationError" class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
{{ formValidationError }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch } from "vue";
|
|
import { defineProps, defineEmits, defineExpose } from "vue";
|
|
import ModalSearch from "@/components/atoms/input/ModalSearch.vue";
|
|
|
|
const props = defineProps({
|
|
searchClients: {
|
|
type: Function,
|
|
required: true,
|
|
},
|
|
searchDeceased: {
|
|
type: Function,
|
|
required: true,
|
|
},
|
|
onClientSelect: {
|
|
type: Function,
|
|
default: null,
|
|
},
|
|
onDeceasedSelect: {
|
|
type: Function,
|
|
default: null,
|
|
},
|
|
selectedDeceased: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
loading: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(["create-intervention"]);
|
|
|
|
// Form data
|
|
const form = ref({
|
|
client_id: "",
|
|
deceased_id: "",
|
|
type: "",
|
|
scheduled_date: "",
|
|
scheduled_time: "",
|
|
duration_min: "",
|
|
status: "",
|
|
order_giver: "",
|
|
notes: "",
|
|
});
|
|
|
|
// Field errors
|
|
const fieldErrors = ref({});
|
|
|
|
// Selected items
|
|
const selectedClient = ref(null);
|
|
const selectedDeceased = ref(props.selectedDeceased);
|
|
|
|
// Store functions
|
|
const searchClients = async (params) => {
|
|
return await props.searchClients(params.query);
|
|
};
|
|
|
|
const searchDeceased = async (params) => {
|
|
return await props.searchDeceased(params.query);
|
|
};
|
|
|
|
// Client methods
|
|
const handleClientSelect = (client) => {
|
|
selectedClient.value = client;
|
|
};
|
|
|
|
const handleClientConfirm = (client) => {
|
|
selectedClient.value = client;
|
|
form.value.client_id = client.id.toString();
|
|
if (props.onClientSelect) {
|
|
props.onClientSelect(client);
|
|
}
|
|
};
|
|
|
|
const getClientDescription = (client) => {
|
|
return client.email || "Pas d'email";
|
|
};
|
|
|
|
const getClientMeta = (client) => {
|
|
return client.phone ? `Tél: ${client.phone}` : "Pas de téléphone";
|
|
};
|
|
|
|
// Deceased methods
|
|
const handleDeceasedSelect = (deceased) => {
|
|
selectedDeceased.value = deceased;
|
|
};
|
|
|
|
const handleDeceasedConfirm = (deceased) => {
|
|
selectedDeceased.value = deceased;
|
|
form.value.deceased_id = deceased.id.toString();
|
|
if (props.onDeceasedSelect) {
|
|
props.onDeceasedSelect(deceased);
|
|
}
|
|
};
|
|
|
|
const getDeceasedFullName = (deceased) => {
|
|
return `${deceased.last_name} ${deceased.first_name || ""}`.trim();
|
|
};
|
|
|
|
const getDeceasedDescription = (deceased) => {
|
|
return `Né(e) le ${formatDate(deceased.birth_date)}`;
|
|
};
|
|
|
|
const getDeceasedMeta = (deceased) => {
|
|
return `Décédé(e) le ${formatDate(deceased.death_date)}`;
|
|
};
|
|
|
|
const formatDate = (dateString) => {
|
|
if (!dateString) return "Date inconnue";
|
|
return new Date(dateString).toLocaleDateString("fr-FR");
|
|
};
|
|
|
|
// Form validation
|
|
const formValidationError = computed(() => {
|
|
if (!form.value.client_id) {
|
|
return "Le client est obligatoire";
|
|
}
|
|
if (!form.value.type) {
|
|
return "Le type d'intervention est obligatoire";
|
|
}
|
|
return null;
|
|
});
|
|
|
|
// Form submission
|
|
const submitForm = () => {
|
|
// Clear field errors
|
|
fieldErrors.value = {};
|
|
|
|
// Check for validation errors
|
|
if (formValidationError.value) {
|
|
return;
|
|
}
|
|
|
|
// Validate required fields and set field errors
|
|
if (!selectedClient.value) {
|
|
fieldErrors.value.client_id = "Le client est obligatoire";
|
|
return;
|
|
}
|
|
|
|
if (!form.value.type) {
|
|
fieldErrors.value.type = "Le type d'intervention est obligatoire";
|
|
return;
|
|
}
|
|
|
|
// Combine date and time into scheduled_at
|
|
const cleanedForm = { ...form.value };
|
|
if (cleanedForm.scheduled_date && cleanedForm.scheduled_time) {
|
|
cleanedForm.scheduled_at = `${cleanedForm.scheduled_date} ${cleanedForm.scheduled_time}:00`;
|
|
delete cleanedForm.scheduled_date;
|
|
delete cleanedForm.scheduled_time;
|
|
}
|
|
|
|
// Set IDs
|
|
cleanedForm.client_id = selectedClient.value.id;
|
|
if (selectedDeceased.value) {
|
|
cleanedForm.deceased_id = selectedDeceased.value.id;
|
|
}
|
|
|
|
// Convert string numbers to integers
|
|
if (cleanedForm.client_id) {
|
|
cleanedForm.client_id = parseInt(cleanedForm.client_id);
|
|
}
|
|
if (cleanedForm.deceased_id) {
|
|
cleanedForm.deceased_id = parseInt(cleanedForm.deceased_id);
|
|
}
|
|
if (cleanedForm.duration_min) {
|
|
cleanedForm.duration_min = parseInt(cleanedForm.duration_min);
|
|
}
|
|
|
|
// Emit the create-intervention event
|
|
emit("create-intervention", cleanedForm);
|
|
};
|
|
|
|
// Watch for pre-selected deceased from parent
|
|
watch(
|
|
() => props.selectedDeceased,
|
|
(newValue) => {
|
|
if (newValue) {
|
|
selectedDeceased.value = newValue;
|
|
form.value.deceased_id = newValue.id.toString();
|
|
if (props.onDeceasedSelect) {
|
|
props.onDeceasedSelect(newValue);
|
|
}
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
// Expose submitForm function to parent component
|
|
defineExpose({
|
|
submitForm,
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.modal-intervention-form {
|
|
height: 100%;
|
|
overflow-y: auto;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.selected-display .alert {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.form-label {
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.text-danger {
|
|
color: #f5365c;
|
|
}
|
|
|
|
.invalid-feedback {
|
|
display: block;
|
|
font-size: 0.875rem;
|
|
color: #dc3545;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.form-select.is-invalid,
|
|
.form-control.is-invalid {
|
|
border-color: #dc3545;
|
|
padding-right: calc(1.5em + 0.75rem);
|
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath d='M5.8 4.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
|
background-repeat: no-repeat;
|
|
background-position: right calc(0.375em + 0.1875rem) center;
|
|
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
|
}
|
|
|
|
.alert-warning {
|
|
background-color: #fff3cd;
|
|
border: 1px solid #ffeaa7;
|
|
color: #856404;
|
|
}
|
|
|
|
.alert i {
|
|
font-size: 0.8rem;
|
|
}
|
|
</style>
|