intervention
This commit is contained in:
parent
0c4ff92fd5
commit
7570f46658
146
thanasoft-back/app/Http/Controllers/Api/DeceasedController.php
Normal file
146
thanasoft-back/app/Http/Controllers/Api/DeceasedController.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\StoreDeceasedRequest;
|
||||||
|
use App\Http\Requests\UpdateDeceasedRequest;
|
||||||
|
use App\Http\Resources\Deceased\DeceasedResource;
|
||||||
|
use App\Http\Resources\Deceased\DeceasedCollection;
|
||||||
|
use App\Models\Deceased;
|
||||||
|
use App\Repositories\DeceasedRepositoryInterface;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class DeceasedController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var DeceasedRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $deceasedRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeceasedController constructor.
|
||||||
|
*
|
||||||
|
* @param DeceasedRepositoryInterface $deceasedRepository
|
||||||
|
*/
|
||||||
|
public function __construct(DeceasedRepositoryInterface $deceasedRepository)
|
||||||
|
{
|
||||||
|
$this->deceasedRepository = $deceasedRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$filters = $request->only([
|
||||||
|
'search',
|
||||||
|
'start_date',
|
||||||
|
'end_date',
|
||||||
|
'sort_by',
|
||||||
|
'sort_order'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$perPage = $request->input('per_page', 15);
|
||||||
|
|
||||||
|
$deceased = $this->deceasedRepository->getAllPaginated($filters, $perPage);
|
||||||
|
|
||||||
|
return response()->json(new DeceasedCollection($deceased));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error fetching deceased list: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la récupération des défunts.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(StoreDeceasedRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validated();
|
||||||
|
|
||||||
|
$deceased = $this->deceasedRepository->create($validated);
|
||||||
|
|
||||||
|
return response()->json(new DeceasedResource($deceased), Response::HTTP_CREATED);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error creating deceased: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la création du défunt.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$deceased = $this->deceasedRepository->findById($id);
|
||||||
|
|
||||||
|
return response()->json(new DeceasedResource($deceased));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error fetching deceased details: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Défunt non trouvé ou une erreur est survenue.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(UpdateDeceasedRequest $request, int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$deceased = $this->deceasedRepository->findById($id);
|
||||||
|
|
||||||
|
$validated = $request->validated();
|
||||||
|
|
||||||
|
$updatedDeceased = $this->deceasedRepository->update($deceased, $validated);
|
||||||
|
|
||||||
|
return response()->json(new DeceasedResource($updatedDeceased));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error updating deceased: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la mise à jour du défunt.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$deceased = $this->deceasedRepository->findById($id);
|
||||||
|
|
||||||
|
$this->deceasedRepository->delete($deceased);
|
||||||
|
|
||||||
|
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error deleting deceased: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la suppression du défunt.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\StoreInterventionRequest;
|
||||||
|
use App\Http\Requests\UpdateInterventionRequest;
|
||||||
|
use App\Http\Resources\Intervention\InterventionResource;
|
||||||
|
use App\Http\Resources\Intervention\InterventionCollection;
|
||||||
|
use App\Repositories\InterventionRepositoryInterface;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class InterventionController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var InterventionRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $interventionRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InterventionController constructor.
|
||||||
|
*
|
||||||
|
* @param InterventionRepositoryInterface $interventionRepository
|
||||||
|
*/
|
||||||
|
public function __construct(InterventionRepositoryInterface $interventionRepository)
|
||||||
|
{
|
||||||
|
$this->interventionRepository = $interventionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$filters = $request->only([
|
||||||
|
'client_id',
|
||||||
|
'deceased_id',
|
||||||
|
'status',
|
||||||
|
'type',
|
||||||
|
'start_date',
|
||||||
|
'end_date',
|
||||||
|
'sort_by',
|
||||||
|
'sort_order'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$perPage = $request->input('per_page', 15);
|
||||||
|
|
||||||
|
$interventions = $this->interventionRepository->getAllPaginated($filters, $perPage);
|
||||||
|
|
||||||
|
return response()->json(new InterventionCollection($interventions));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error fetching interventions list: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la récupération des interventions.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(StoreInterventionRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validated();
|
||||||
|
|
||||||
|
$intervention = $this->interventionRepository->create($validated);
|
||||||
|
|
||||||
|
return response()->json(new InterventionResource($intervention), Response::HTTP_CREATED);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error creating intervention: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la création de l\'intervention.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$intervention = $this->interventionRepository->findById($id);
|
||||||
|
|
||||||
|
return response()->json(new InterventionResource($intervention));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error fetching intervention details: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Intervention non trouvée ou une erreur est survenue.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(UpdateInterventionRequest $request, int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$intervention = $this->interventionRepository->findById($id);
|
||||||
|
|
||||||
|
$validated = $request->validated();
|
||||||
|
|
||||||
|
$updatedIntervention = $this->interventionRepository->update($intervention, $validated);
|
||||||
|
|
||||||
|
return response()->json(new InterventionResource($updatedIntervention));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error updating intervention: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la mise à jour de l\'intervention.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$intervention = $this->interventionRepository->findById($id);
|
||||||
|
|
||||||
|
$this->interventionRepository->delete($intervention);
|
||||||
|
|
||||||
|
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error deleting intervention: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la suppression de l\'intervention.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the status of an intervention.
|
||||||
|
*/
|
||||||
|
public function changeStatus(Request $request, int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'status' => 'required|in:demande,planifie,en_cours,termine,annule'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$intervention = $this->interventionRepository->findById($id);
|
||||||
|
|
||||||
|
$updatedIntervention = $this->interventionRepository->changeStatus(
|
||||||
|
$intervention,
|
||||||
|
$validated['status']
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json(new InterventionResource($updatedIntervention));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error changing intervention status: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Une erreur est survenue lors de la modification du statut de l\'intervention.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
thanasoft-back/app/Http/Requests/StoreDeceasedRequest.php
Normal file
49
thanasoft-back/app/Http/Requests/StoreDeceasedRequest.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class StoreDeceasedRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
// Add authorization logic if needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'last_name' => ['required', 'string', 'max:191'],
|
||||||
|
'first_name' => ['nullable', 'string', 'max:191'],
|
||||||
|
'birth_date' => ['nullable', 'date'],
|
||||||
|
'death_date' => ['nullable', 'date', 'after_or_equal:birth_date'],
|
||||||
|
'place_of_death' => ['nullable', 'string', 'max:255'],
|
||||||
|
'notes' => ['nullable', 'string']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom error messages for validator errors.
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||||
|
'last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||||
|
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||||
|
'death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||||
|
'place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class StoreInterventionRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
// Add authorization logic if needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'client_id' => ['required', 'exists:clients,id'],
|
||||||
|
'deceased_id' => ['nullable', 'exists:deceased,id'],
|
||||||
|
'order_giver' => ['nullable', 'string', 'max:255'],
|
||||||
|
'location_id' => ['nullable', 'exists:client_locations,id'],
|
||||||
|
'type' => ['required', Rule::in([
|
||||||
|
'thanatopraxie',
|
||||||
|
'toilette_mortuaire',
|
||||||
|
'exhumation',
|
||||||
|
'retrait_pacemaker',
|
||||||
|
'retrait_bijoux',
|
||||||
|
'autre'
|
||||||
|
])],
|
||||||
|
'scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||||
|
'duration_min' => ['nullable', 'integer', 'min:0'],
|
||||||
|
'status' => ['sometimes', Rule::in([
|
||||||
|
'demande',
|
||||||
|
'planifie',
|
||||||
|
'en_cours',
|
||||||
|
'termine',
|
||||||
|
'annule'
|
||||||
|
])],
|
||||||
|
'assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||||
|
'notes' => ['nullable', 'string'],
|
||||||
|
'created_by' => ['nullable', 'exists:users,id']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom error messages for validator errors.
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'client_id.required' => 'Le client est obligatoire.',
|
||||||
|
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||||
|
'deceased_id.exists' => 'Le défunt sélectionné est invalide.',
|
||||||
|
'order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||||
|
'location_id.exists' => 'Le lieu sélectionné est invalide.',
|
||||||
|
'type.required' => 'Le type d\'intervention est obligatoire.',
|
||||||
|
'type.in' => 'Le type d\'intervention est invalide.',
|
||||||
|
'scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||||
|
'duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||||
|
'duration_min.min' => 'La durée ne peut pas être négative.',
|
||||||
|
'status.in' => 'Le statut de l\'intervention est invalide.',
|
||||||
|
'assigned_practitioner_id.exists' => 'Le praticien sélectionné est invalide.',
|
||||||
|
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
49
thanasoft-back/app/Http/Requests/UpdateDeceasedRequest.php
Normal file
49
thanasoft-back/app/Http/Requests/UpdateDeceasedRequest.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateDeceasedRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
// Add authorization logic if needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'last_name' => ['sometimes', 'required', 'string', 'max:191'],
|
||||||
|
'first_name' => ['nullable', 'string', 'max:191'],
|
||||||
|
'birth_date' => ['nullable', 'date'],
|
||||||
|
'death_date' => ['nullable', 'date', 'after_or_equal:birth_date'],
|
||||||
|
'place_of_death' => ['nullable', 'string', 'max:255'],
|
||||||
|
'notes' => ['nullable', 'string']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom error messages for validator errors.
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||||
|
'last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||||
|
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||||
|
'death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||||
|
'place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateInterventionRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
// Add authorization logic if needed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'client_id' => ['sometimes', 'required', 'exists:clients,id'],
|
||||||
|
'deceased_id' => ['nullable', 'exists:deceased,id'],
|
||||||
|
'order_giver' => ['nullable', 'string', 'max:255'],
|
||||||
|
'location_id' => ['nullable', 'exists:client_locations,id'],
|
||||||
|
'type' => ['sometimes', 'required', Rule::in([
|
||||||
|
'thanatopraxie',
|
||||||
|
'toilette_mortuaire',
|
||||||
|
'exhumation',
|
||||||
|
'retrait_pacemaker',
|
||||||
|
'retrait_bijoux',
|
||||||
|
'autre'
|
||||||
|
])],
|
||||||
|
'scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||||
|
'duration_min' => ['nullable', 'integer', 'min:0'],
|
||||||
|
'status' => ['sometimes', Rule::in([
|
||||||
|
'demande',
|
||||||
|
'planifie',
|
||||||
|
'en_cours',
|
||||||
|
'termine',
|
||||||
|
'annule'
|
||||||
|
])],
|
||||||
|
'assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||||
|
'notes' => ['nullable', 'string'],
|
||||||
|
'created_by' => ['nullable', 'exists:users,id']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom error messages for validator errors.
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'client_id.required' => 'Le client est obligatoire.',
|
||||||
|
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||||
|
'deceased_id.exists' => 'Le défunt sélectionné est invalide.',
|
||||||
|
'order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||||
|
'location_id.exists' => 'Le lieu sélectionné est invalide.',
|
||||||
|
'type.required' => 'Le type d\'intervention est obligatoire.',
|
||||||
|
'type.in' => 'Le type d\'intervention est invalide.',
|
||||||
|
'scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||||
|
'duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||||
|
'duration_min.min' => 'La durée ne peut pas être négative.',
|
||||||
|
'status.in' => 'Le statut de l\'intervention est invalide.',
|
||||||
|
'assigned_practitioner_id.exists' => 'Le praticien sélectionné est invalide.',
|
||||||
|
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,7 +24,7 @@ class UpdateThanatopractitionerRequest extends FormRequest
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'employee_id' => [
|
'employee_id' => [
|
||||||
'required',
|
'nullable',
|
||||||
'exists:employees,id',
|
'exists:employees,id',
|
||||||
Rule::unique('thanatopractitioners', 'employee_id')->ignore($this->route('thanatopractitioner'))
|
Rule::unique('thanatopractitioners', 'employee_id')->ignore($this->route('thanatopractitioner'))
|
||||||
],
|
],
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Deceased;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class DeceasedCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection,
|
||||||
|
'meta' => [
|
||||||
|
'total' => $this->total(),
|
||||||
|
'per_page' => $this->perPage(),
|
||||||
|
'current_page' => $this->currentPage(),
|
||||||
|
'last_page' => $this->lastPage(),
|
||||||
|
'from' => $this->firstItem(),
|
||||||
|
'to' => $this->lastItem()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Deceased;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class DeceasedResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'last_name' => $this->last_name,
|
||||||
|
'first_name' => $this->first_name,
|
||||||
|
'full_name' => trim($this->first_name . ' ' . $this->last_name),
|
||||||
|
'birth_date' => $this->birth_date ? $this->birth_date->format('Y-m-d') : null,
|
||||||
|
'death_date' => $this->death_date ? $this->death_date->format('Y-m-d') : null,
|
||||||
|
'place_of_death' => $this->place_of_death,
|
||||||
|
'notes' => $this->notes,
|
||||||
|
'documents_count' => $this->documents_count ?? $this->documents()->count(),
|
||||||
|
'interventions_count' => $this->interventions_count ?? $this->interventions()->count(),
|
||||||
|
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
||||||
|
'updated_at' => $this->updated_at->format('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Intervention;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class InterventionAttachmentResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'intervention_id' => $this->intervention_id,
|
||||||
|
'file' => $this->whenLoaded('file', function () {
|
||||||
|
return [
|
||||||
|
'id' => $this->file->id,
|
||||||
|
'name' => $this->file->name,
|
||||||
|
'path' => $this->file->path,
|
||||||
|
'mime_type' => $this->file->mime_type,
|
||||||
|
'size' => $this->file->size
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
'label' => $this->label,
|
||||||
|
'created_at' => $this->created_at->format('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Intervention;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class InterventionCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection,
|
||||||
|
'meta' => [
|
||||||
|
'total' => $this->total(),
|
||||||
|
'per_page' => $this->perPage(),
|
||||||
|
'current_page' => $this->currentPage(),
|
||||||
|
'last_page' => $this->lastPage(),
|
||||||
|
'from' => $this->firstItem(),
|
||||||
|
'to' => $this->lastItem(),
|
||||||
|
'status_summary' => $this->calculateStatusSummary()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate summary of intervention statuses.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function calculateStatusSummary(): array
|
||||||
|
{
|
||||||
|
$statusCounts = $this->collection->groupBy('status')
|
||||||
|
->map(function ($group) {
|
||||||
|
return $group->count();
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return $statusCounts;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Intervention;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class InterventionNotificationResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'intervention_id' => $this->intervention_id,
|
||||||
|
'channel' => $this->channel,
|
||||||
|
'destination' => $this->destination,
|
||||||
|
'payload' => $this->payload,
|
||||||
|
'status' => $this->status,
|
||||||
|
'sent_at' => $this->sent_at ? $this->sent_at->format('Y-m-d H:i:s') : null,
|
||||||
|
'created_at' => $this->created_at->format('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Intervention;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use App\Http\Resources\Deceased\DeceasedResource;
|
||||||
|
use App\Http\Resources\Client\ClientResource;
|
||||||
|
use App\Http\Resources\Employee\ThanatopractitionerResource;
|
||||||
|
|
||||||
|
class InterventionResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'client' => $this->whenLoaded('client', function () {
|
||||||
|
return new ClientResource($this->client);
|
||||||
|
}),
|
||||||
|
'deceased' => $this->whenLoaded('deceased', function () {
|
||||||
|
return new DeceasedResource($this->deceased);
|
||||||
|
}),
|
||||||
|
'order_giver' => $this->order_giver,
|
||||||
|
'location' => $this->whenLoaded('location', function () {
|
||||||
|
return [
|
||||||
|
'id' => $this->location->id,
|
||||||
|
'name' => $this->location->name
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
'type' => $this->type,
|
||||||
|
'scheduled_at' => $this->scheduled_at ? $this->scheduled_at->format('Y-m-d H:i:s') : null,
|
||||||
|
'duration_min' => $this->duration_min,
|
||||||
|
'status' => $this->status,
|
||||||
|
'assigned_practitioner' => $this->whenLoaded('assignedPractitioner', function () {
|
||||||
|
return new ThanatopractitionerResource($this->assignedPractitioner);
|
||||||
|
}),
|
||||||
|
'attachments_count' => $this->attachments_count,
|
||||||
|
'notes' => $this->notes,
|
||||||
|
'created_by' => $this->created_by,
|
||||||
|
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
||||||
|
'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
|
||||||
|
'attachments' => $this->whenLoaded('attachments', function () {
|
||||||
|
return InterventionAttachmentResource::collection($this->attachments);
|
||||||
|
}),
|
||||||
|
'notifications' => $this->whenLoaded('notifications', function () {
|
||||||
|
return InterventionNotificationResource::collection($this->notifications);
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
59
thanasoft-back/app/Models/Deceased.php
Normal file
59
thanasoft-back/app/Models/Deceased.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class Deceased extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table associated with the model.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $table = 'deceased';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'last_name',
|
||||||
|
'first_name',
|
||||||
|
'birth_date',
|
||||||
|
'death_date',
|
||||||
|
'place_of_death',
|
||||||
|
'notes'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'birth_date' => 'date',
|
||||||
|
'death_date' => 'date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the documents associated with the deceased.
|
||||||
|
*/
|
||||||
|
public function documents(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(DeceasedDocument::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the interventions associated with the deceased.
|
||||||
|
*/
|
||||||
|
public function interventions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Intervention::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
thanasoft-back/app/Models/DeceasedDocument.php
Normal file
49
thanasoft-back/app/Models/DeceasedDocument.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class DeceasedDocument extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'deceased_id',
|
||||||
|
'doc_type',
|
||||||
|
'file_id',
|
||||||
|
'generated_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'generated_at' => 'datetime'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the deceased associated with the document.
|
||||||
|
*/
|
||||||
|
public function deceased(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Deceased::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file associated with the document.
|
||||||
|
*/
|
||||||
|
public function file(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(File::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
99
thanasoft-back/app/Models/Intervention.php
Normal file
99
thanasoft-back/app/Models/Intervention.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class Intervention extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'client_id',
|
||||||
|
'deceased_id',
|
||||||
|
'order_giver',
|
||||||
|
'location_id',
|
||||||
|
'type',
|
||||||
|
'scheduled_at',
|
||||||
|
'duration_min',
|
||||||
|
'status',
|
||||||
|
'assigned_practitioner_id',
|
||||||
|
'attachments_count',
|
||||||
|
'notes',
|
||||||
|
'created_by'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'scheduled_at' => 'datetime',
|
||||||
|
'attachments_count' => 'integer'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the client associated with the intervention.
|
||||||
|
*/
|
||||||
|
public function client(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Client::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the deceased associated with the intervention.
|
||||||
|
*/
|
||||||
|
public function deceased(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Deceased::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the location associated with the intervention.
|
||||||
|
*/
|
||||||
|
public function location(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ClientLocation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the practitioner assigned to the intervention.
|
||||||
|
*/
|
||||||
|
public function assignedPractitioner(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Thanatopractitioner::class, 'assigned_practitioner_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user who created the intervention.
|
||||||
|
*/
|
||||||
|
public function creator(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'created_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the intervention.
|
||||||
|
*/
|
||||||
|
public function attachments(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(InterventionAttachment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notifications for the intervention.
|
||||||
|
*/
|
||||||
|
public function notifications(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(InterventionNotification::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
thanasoft-back/app/Models/InterventionAttachment.php
Normal file
39
thanasoft-back/app/Models/InterventionAttachment.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class InterventionAttachment extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'intervention_id',
|
||||||
|
'file_id',
|
||||||
|
'label'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the intervention associated with the attachment.
|
||||||
|
*/
|
||||||
|
public function intervention(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Intervention::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file associated with the attachment.
|
||||||
|
*/
|
||||||
|
public function file(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(File::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
thanasoft-back/app/Models/InterventionNotification.php
Normal file
44
thanasoft-back/app/Models/InterventionNotification.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class InterventionNotification extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'intervention_id',
|
||||||
|
'channel',
|
||||||
|
'destination',
|
||||||
|
'payload',
|
||||||
|
'status',
|
||||||
|
'sent_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'payload' => 'array',
|
||||||
|
'sent_at' => 'datetime'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the intervention associated with the notification.
|
||||||
|
*/
|
||||||
|
public function intervention(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Intervention::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -56,6 +56,10 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$this->app->bind(\App\Repositories\PractitionerDocumentRepositoryInterface::class, function ($app) {
|
$this->app->bind(\App\Repositories\PractitionerDocumentRepositoryInterface::class, function ($app) {
|
||||||
return new \App\Repositories\PractitionerDocumentRepository($app->make(\App\Models\PractitionerDocument::class));
|
return new \App\Repositories\PractitionerDocumentRepository($app->make(\App\Models\PractitionerDocument::class));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->app->bind(\App\Repositories\InterventionRepositoryInterface::class, \App\Repositories\InterventionRepository::class);
|
||||||
|
|
||||||
|
$this->app->bind(\App\Repositories\DeceasedRepositoryInterface::class, \App\Repositories\DeceasedRepository::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
29
thanasoft-back/app/Providers/RepositoryServiceProvider.php
Normal file
29
thanasoft-back/app/Providers/RepositoryServiceProvider.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Repositories\DeceasedRepositoryInterface;
|
||||||
|
use App\Repositories\DeceasedRepository;
|
||||||
|
use App\Repositories\InterventionRepositoryInterface;
|
||||||
|
use App\Repositories\InterventionRepository;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class RepositoryServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->bind(DeceasedRepositoryInterface::class, DeceasedRepository::class);
|
||||||
|
$this->app->bind(InterventionRepositoryInterface::class, InterventionRepository::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
102
thanasoft-back/app/Repositories/DeceasedRepository.php
Normal file
102
thanasoft-back/app/Repositories/DeceasedRepository.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Deceased;
|
||||||
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class DeceasedRepository implements DeceasedRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all deceased with optional filtering and pagination
|
||||||
|
*
|
||||||
|
* @param array $filters
|
||||||
|
* @param int $perPage
|
||||||
|
* @return LengthAwarePaginator
|
||||||
|
*/
|
||||||
|
public function getAllPaginated(array $filters = [], int $perPage = 15): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
$query = Deceased::query();
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if (!empty($filters['search'])) {
|
||||||
|
$query->where(function($q) use ($filters) {
|
||||||
|
$q->where('last_name', 'LIKE', "%{$filters['search']}%")
|
||||||
|
->orWhere('first_name', 'LIKE', "%{$filters['search']}%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply date range filters
|
||||||
|
if (!empty($filters['start_date'])) {
|
||||||
|
$query->where('death_date', '>=', $filters['start_date']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['end_date'])) {
|
||||||
|
$query->where('death_date', '<=', $filters['end_date']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
$sortBy = $filters['sort_by'] ?? 'created_at';
|
||||||
|
$sortOrder = $filters['sort_order'] ?? 'desc';
|
||||||
|
$query->orderBy($sortBy, $sortOrder);
|
||||||
|
|
||||||
|
// Eager load related counts
|
||||||
|
$query->withCount(['documents', 'interventions']);
|
||||||
|
|
||||||
|
return $query->paginate($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a deceased by ID
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return Deceased
|
||||||
|
*/
|
||||||
|
public function findById(int $id): Deceased
|
||||||
|
{
|
||||||
|
return Deceased::findOrFail($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new deceased record
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return Deceased
|
||||||
|
*/
|
||||||
|
public function create(array $data): Deceased
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($data) {
|
||||||
|
return Deceased::create($data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing deceased record
|
||||||
|
*
|
||||||
|
* @param Deceased $deceased
|
||||||
|
* @param array $data
|
||||||
|
* @return Deceased
|
||||||
|
*/
|
||||||
|
public function update(Deceased $deceased, array $data): Deceased
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($deceased, $data) {
|
||||||
|
$deceased->update($data);
|
||||||
|
return $deceased;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a deceased record
|
||||||
|
*
|
||||||
|
* @param Deceased $deceased
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete(Deceased $deceased): bool
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($deceased) {
|
||||||
|
return $deceased->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Deceased;
|
||||||
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
|
interface DeceasedRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all deceased with optional filtering and pagination
|
||||||
|
*
|
||||||
|
* @param array $filters
|
||||||
|
* @param int $perPage
|
||||||
|
* @return LengthAwarePaginator
|
||||||
|
*/
|
||||||
|
public function getAllPaginated(array $filters = [], int $perPage = 15): LengthAwarePaginator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a deceased by ID
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return Deceased
|
||||||
|
*/
|
||||||
|
public function findById(int $id): Deceased;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new deceased record
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return Deceased
|
||||||
|
*/
|
||||||
|
public function create(array $data): Deceased;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing deceased record
|
||||||
|
*
|
||||||
|
* @param Deceased $deceased
|
||||||
|
* @param array $data
|
||||||
|
* @return Deceased
|
||||||
|
*/
|
||||||
|
public function update(Deceased $deceased, array $data): Deceased;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a deceased record
|
||||||
|
*
|
||||||
|
* @param Deceased $deceased
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete(Deceased $deceased): bool;
|
||||||
|
}
|
||||||
137
thanasoft-back/app/Repositories/InterventionRepository.php
Normal file
137
thanasoft-back/app/Repositories/InterventionRepository.php
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Intervention;
|
||||||
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class InterventionRepository implements InterventionRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all interventions with optional filtering and pagination
|
||||||
|
*
|
||||||
|
* @param array $filters
|
||||||
|
* @param int $perPage
|
||||||
|
* @return LengthAwarePaginator
|
||||||
|
*/
|
||||||
|
public function getAllPaginated(array $filters = [], int $perPage = 15): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
$query = Intervention::query();
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if (!empty($filters['client_id'])) {
|
||||||
|
$query->where('client_id', $filters['client_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['deceased_id'])) {
|
||||||
|
$query->where('deceased_id', $filters['deceased_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['status'])) {
|
||||||
|
$query->where('status', $filters['status']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['type'])) {
|
||||||
|
$query->where('type', $filters['type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date range filters
|
||||||
|
if (!empty($filters['start_date'])) {
|
||||||
|
$query->where('scheduled_at', '>=', $filters['start_date']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['end_date'])) {
|
||||||
|
$query->where('scheduled_at', '<=', $filters['end_date']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
$sortBy = $filters['sort_by'] ?? 'created_at';
|
||||||
|
$sortOrder = $filters['sort_order'] ?? 'desc';
|
||||||
|
$query->orderBy($sortBy, $sortOrder);
|
||||||
|
|
||||||
|
// Eager load related models
|
||||||
|
$query->with([
|
||||||
|
'client',
|
||||||
|
'deceased',
|
||||||
|
'location',
|
||||||
|
'assignedPractitioner'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $query->paginate($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an intervention by ID
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function findById(int $id): Intervention
|
||||||
|
{
|
||||||
|
return Intervention::with([
|
||||||
|
'client',
|
||||||
|
'deceased',
|
||||||
|
'location',
|
||||||
|
'assignedPractitioner',
|
||||||
|
'attachments',
|
||||||
|
'notifications'
|
||||||
|
])->findOrFail($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new intervention record
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function create(array $data): Intervention
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($data) {
|
||||||
|
return Intervention::create($data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing intervention record
|
||||||
|
*
|
||||||
|
* @param Intervention $intervention
|
||||||
|
* @param array $data
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function update(Intervention $intervention, array $data): Intervention
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($intervention, $data) {
|
||||||
|
$intervention->update($data);
|
||||||
|
return $intervention;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an intervention record
|
||||||
|
*
|
||||||
|
* @param Intervention $intervention
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete(Intervention $intervention): bool
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($intervention) {
|
||||||
|
return $intervention->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the status of an intervention
|
||||||
|
*
|
||||||
|
* @param Intervention $intervention
|
||||||
|
* @param string $status
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function changeStatus(Intervention $intervention, string $status): Intervention
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($intervention, $status) {
|
||||||
|
$intervention->update(['status' => $status]);
|
||||||
|
return $intervention;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Models\Intervention;
|
||||||
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
|
||||||
|
interface InterventionRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all interventions with optional filtering and pagination
|
||||||
|
*
|
||||||
|
* @param array $filters
|
||||||
|
* @param int $perPage
|
||||||
|
* @return LengthAwarePaginator
|
||||||
|
*/
|
||||||
|
public function getAllPaginated(array $filters = [], int $perPage = 15): LengthAwarePaginator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an intervention by ID
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function findById(int $id): Intervention;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new intervention record
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function create(array $data): Intervention;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing intervention record
|
||||||
|
*
|
||||||
|
* @param Intervention $intervention
|
||||||
|
* @param array $data
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function update(Intervention $intervention, array $data): Intervention;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an intervention record
|
||||||
|
*
|
||||||
|
* @param Intervention $intervention
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete(Intervention $intervention): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the status of an intervention
|
||||||
|
*
|
||||||
|
* @param Intervention $intervention
|
||||||
|
* @param string $status
|
||||||
|
* @return Intervention
|
||||||
|
*/
|
||||||
|
public function changeStatus(Intervention $intervention, string $status): Intervention;
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('files', function (Blueprint $table) {
|
||||||
|
$table->id(); // This creates an auto-incrementing BIGINT primary key
|
||||||
|
$table->string('file_name', 255);
|
||||||
|
$table->string('mime_type', 191)->nullable();
|
||||||
|
$table->bigInteger('size_bytes')->nullable();
|
||||||
|
$table->text('storage_uri');
|
||||||
|
$table->char('sha256', 64)->nullable();
|
||||||
|
$table->foreignId('uploaded_by')->nullable()->constrained('users')->onDelete('set null');
|
||||||
|
$table->timestamp('uploaded_at')->useCurrent();
|
||||||
|
$table->timestamps(); // Optional: adds created_at and updated_at columns
|
||||||
|
|
||||||
|
// Add index for better performance on frequently searched columns
|
||||||
|
$table->index('sha256');
|
||||||
|
$table->index('uploaded_by');
|
||||||
|
$table->index('uploaded_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('files');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('deceased', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('last_name', 191)->nullable(false);
|
||||||
|
$table->string('first_name', 191)->nullable();
|
||||||
|
$table->date('birth_date')->nullable();
|
||||||
|
$table->date('death_date')->nullable();
|
||||||
|
$table->string('place_of_death', 255)->nullable();
|
||||||
|
$table->text('notes')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('deceased');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('deceased_documents', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('deceased_id')->constrained('deceased')->cascadeOnDelete();
|
||||||
|
$table->string('doc_type', 64);
|
||||||
|
$table->foreignId('file_id')->nullable()->constrained('files')->nullOnDelete();
|
||||||
|
$table->timestamp('generated_at')->useCurrent();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('deceased_documents');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('interventions', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('client_id')->constrained('clients');
|
||||||
|
$table->foreignId('deceased_id')->nullable()->constrained('deceased')->nullOnDelete();
|
||||||
|
$table->string('order_giver', 255)->nullable();
|
||||||
|
$table->foreignId('location_id')->nullable()->constrained('client_locations')->nullOnDelete();
|
||||||
|
$table->enum('type', [
|
||||||
|
'thanatopraxie',
|
||||||
|
'toilette_mortuaire',
|
||||||
|
'exhumation',
|
||||||
|
'retrait_pacemaker',
|
||||||
|
'retrait_bijoux',
|
||||||
|
'autre'
|
||||||
|
])->nullable(false);
|
||||||
|
$table->dateTime('scheduled_at')->nullable();
|
||||||
|
$table->integer('duration_min')->nullable();
|
||||||
|
$table->enum('status', [
|
||||||
|
'demande',
|
||||||
|
'planifie',
|
||||||
|
'en_cours',
|
||||||
|
'termine',
|
||||||
|
'annule'
|
||||||
|
])->default('demande');
|
||||||
|
$table->foreignId('assigned_practitioner_id')->nullable()->constrained('thanatopractitioners')->nullOnDelete();
|
||||||
|
$table->integer('attachments_count')->default(0);
|
||||||
|
$table->text('notes')->nullable();
|
||||||
|
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Indexes
|
||||||
|
$table->index(['client_id', 'status'], 'idx_interventions_company_status');
|
||||||
|
$table->index('scheduled_at', 'idx_interventions_scheduled');
|
||||||
|
$table->index(['client_id', 'scheduled_at'], 'idx_interventions_client_date');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('interventions');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('intervention_attachments', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('intervention_id')->constrained('interventions')->cascadeOnDelete();
|
||||||
|
$table->foreignId('file_id')->nullable()->constrained('files')->nullOnDelete();
|
||||||
|
$table->string('label', 191)->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('intervention_attachments');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('intervention_notifications', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('intervention_id')->constrained('interventions')->cascadeOnDelete();
|
||||||
|
$table->enum('channel', ['sms', 'mobile', 'email'])->nullable(false);
|
||||||
|
$table->string('destination', 191)->nullable(false);
|
||||||
|
$table->json('payload')->nullable();
|
||||||
|
$table->string('status', 32)->default('queued');
|
||||||
|
$table->timestamp('sent_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('intervention_notifications');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -13,6 +13,9 @@ use App\Http\Controllers\Api\ProductCategoryController;
|
|||||||
use App\Http\Controllers\Api\EmployeeController;
|
use App\Http\Controllers\Api\EmployeeController;
|
||||||
use App\Http\Controllers\Api\ThanatopractitionerController;
|
use App\Http\Controllers\Api\ThanatopractitionerController;
|
||||||
use App\Http\Controllers\Api\PractitionerDocumentController;
|
use App\Http\Controllers\Api\PractitionerDocumentController;
|
||||||
|
use App\Http\Controllers\Api\DeceasedController;
|
||||||
|
use App\Http\Controllers\Api\InterventionController;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -92,4 +95,24 @@ Route::middleware('auth:sanctum')->group(function () {
|
|||||||
Route::get('/practitioner-documents/expiring', [PractitionerDocumentController::class, 'getExpiringDocuments']);
|
Route::get('/practitioner-documents/expiring', [PractitionerDocumentController::class, 'getExpiringDocuments']);
|
||||||
Route::apiResource('practitioner-documents', PractitionerDocumentController::class);
|
Route::apiResource('practitioner-documents', PractitionerDocumentController::class);
|
||||||
Route::patch('/practitioner-documents/{id}/verify', [PractitionerDocumentController::class, 'verifyDocument']);
|
Route::patch('/practitioner-documents/{id}/verify', [PractitionerDocumentController::class, 'verifyDocument']);
|
||||||
|
|
||||||
|
// Deceased Routes
|
||||||
|
Route::prefix('deceased')->group(function () {
|
||||||
|
Route::get('/', [DeceasedController::class, 'index']);
|
||||||
|
Route::post('/', [DeceasedController::class, 'store']);
|
||||||
|
Route::get('/{deceased}', [DeceasedController::class, 'show']);
|
||||||
|
Route::put('/{deceased}', [DeceasedController::class, 'update']);
|
||||||
|
Route::delete('/{deceased}', [DeceasedController::class, 'destroy']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Intervention Routes
|
||||||
|
Route::prefix('interventions')->group(function () {
|
||||||
|
Route::get('/', [InterventionController::class, 'index']);
|
||||||
|
Route::post('/', [InterventionController::class, 'store']);
|
||||||
|
Route::get('/{intervention}', [InterventionController::class, 'show']);
|
||||||
|
Route::put('/{intervention}', [InterventionController::class, 'update']);
|
||||||
|
Route::delete('/{intervention}', [InterventionController::class, 'destroy']);
|
||||||
|
Route::patch('/{intervention}/status', [InterventionController::class, 'changeStatus']);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<add-defunt-template>
|
||||||
|
<template #defunt-form>
|
||||||
|
<defunt-form
|
||||||
|
:loading="loading"
|
||||||
|
:validation-errors="validationErrors"
|
||||||
|
:success="success"
|
||||||
|
@create-defunt="handleCreateDefunt"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</add-defunt-template>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import AddDefuntTemplate from "@/components/templates/Defunts/AddDefuntTemplate.vue";
|
||||||
|
import DefuntForm from "@/components/molecules/Defunts/DefuntForm.vue";
|
||||||
|
import { defineProps, defineEmits } from "vue";
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
validationErrors: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["createDefunt"]);
|
||||||
|
|
||||||
|
const handleCreateDefunt = (data) => {
|
||||||
|
emit("createDefunt", data);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<defunt-detail-template>
|
||||||
|
<template #button-return>
|
||||||
|
<div class="col-12">
|
||||||
|
<router-link
|
||||||
|
to="/defunts"
|
||||||
|
class="btn btn-outline-secondary btn-sm mb-3"
|
||||||
|
>
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Retour aux défunts
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #loading-state>
|
||||||
|
<div v-if="isLoading" class="text-center p-5">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Chargement...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #defunt-detail-sidebar>
|
||||||
|
<DefuntDetailSidebar :deceased="deceased" :is-loading="isLoading" />
|
||||||
|
</template>
|
||||||
|
<template #defunt-detail-content>
|
||||||
|
<DefuntDetailContent
|
||||||
|
:deceased="deceased"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
@update-deceased="handleUpdateDeceased"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</defunt-detail-template>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { defineProps, defineEmits, ref, computed } from "vue";
|
||||||
|
import DefuntDetailTemplate from "@/components/templates/Defunts/DefuntDetailTemplate.vue";
|
||||||
|
import DefuntDetailSidebar from "@/components/molecules/Defunts/DefuntDetailSidebar.vue";
|
||||||
|
import DefuntDetailContent from "@/components/molecules/Defunts/DefuntDetailContent.vue";
|
||||||
|
import { RouterLink } from "vue-router";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
deceased: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["updateDeceased"]);
|
||||||
|
|
||||||
|
const handleUpdateDeceased = (updateData) => {
|
||||||
|
emit("updateDeceased", updateData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitials = (firstName, lastName) => {
|
||||||
|
const first = firstName ? firstName.charAt(0).toUpperCase() : "";
|
||||||
|
const last = lastName ? lastName.charAt(0).toUpperCase() : "";
|
||||||
|
return first + last || "?";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFullName = computed(() => {
|
||||||
|
const parts = [props.deceased.first_name, props.deceased.last_name].filter(
|
||||||
|
Boolean
|
||||||
|
);
|
||||||
|
return parts.join(" ") || "Nom non renseigné";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<defunts-template>
|
<defunts-template>
|
||||||
<template #defunt-new-action>
|
<template #defunt-new-action>
|
||||||
<add-button text="Ajouter" />
|
<add-button text="Ajouter" @click="add" />
|
||||||
</template>
|
</template>
|
||||||
<template #select-filter>
|
<template #select-filter>
|
||||||
<filter-table />
|
<filter-table />
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<table-action />
|
<table-action />
|
||||||
</template>
|
</template>
|
||||||
<template #defunt-table>
|
<template #defunt-table>
|
||||||
<defunts-list />
|
<defunts-list :defunts="defunts" />
|
||||||
</template>
|
</template>
|
||||||
</defunts-template>
|
</defunts-template>
|
||||||
</template>
|
</template>
|
||||||
@ -20,4 +20,19 @@ import addButton from "@/components/molecules/new-button/addButton.vue";
|
|||||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||||
import DefuntsList from "@/components/molecules/Defunts/DefuntsList.vue";
|
import DefuntsList from "@/components/molecules/Defunts/DefuntsList.vue";
|
||||||
|
import { defineProps } from "vue";
|
||||||
|
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
defunts: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const add = () => {
|
||||||
|
router.push({ name: "Add Defunts" });
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<add-intervention-template>
|
||||||
|
<template #intervention-form>
|
||||||
|
<intervention-form
|
||||||
|
:loading="loading"
|
||||||
|
:validation-errors="validationErrors"
|
||||||
|
:success="success"
|
||||||
|
:deceased-list="deceasedList"
|
||||||
|
:deceased-loading="deceasedLoading"
|
||||||
|
:client-list="clientList"
|
||||||
|
:client-loading="clientLoading"
|
||||||
|
@create-intervention="handleCreateIntervention"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</add-intervention-template>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import AddInterventionTemplate from "@/components/templates/Interventions/AddInterventionTemplate.vue";
|
||||||
|
import InterventionForm from "@/components/molecules/Interventions/InterventionForm.vue";
|
||||||
|
import { defineProps, defineEmits } from "vue";
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
validationErrors: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
deceasedList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
deceasedLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
clientList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
clientLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["createIntervention"]);
|
||||||
|
|
||||||
|
const handleCreateIntervention = (data) => {
|
||||||
|
emit("createIntervention", data);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<interventions-template>
|
<interventions-template>
|
||||||
<template #intervention-new-action>
|
<template #intervention-new-action>
|
||||||
<add-button text="Ajouter" />
|
<add-button text="Ajouter" @click="go" />
|
||||||
</template>
|
</template>
|
||||||
<template #select-filter>
|
<template #select-filter>
|
||||||
<filter-table />
|
<filter-table />
|
||||||
@ -10,7 +10,24 @@
|
|||||||
<table-action />
|
<table-action />
|
||||||
</template>
|
</template>
|
||||||
<template #intervention-table>
|
<template #intervention-table>
|
||||||
<interventions-list />
|
<!-- Loading state -->
|
||||||
|
<div v-if="loading" class="text-center py-5">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Chargement...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">Chargement des interventions...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error state -->
|
||||||
|
<div v-else-if="error" class="alert alert-danger text-center py-4">
|
||||||
|
<p>{{ error }}</p>
|
||||||
|
<button @click="$emit('retry')" class="btn btn-outline-danger">
|
||||||
|
Réessayer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Data table -->
|
||||||
|
<interventions-list v-else :intervention-data="interventions" />
|
||||||
</template>
|
</template>
|
||||||
</interventions-template>
|
</interventions-template>
|
||||||
</template>
|
</template>
|
||||||
@ -20,4 +37,30 @@ import addButton from "@/components/molecules/new-button/addButton.vue";
|
|||||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||||
import interventionsList from "@/components/molecules/Interventions/interventionsList.vue";
|
import interventionsList from "@/components/molecules/Interventions/interventionsList.vue";
|
||||||
|
import { defineProps, defineEmits } from "vue";
|
||||||
|
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
// Props
|
||||||
|
defineProps({
|
||||||
|
interventions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
defineEmits(["retry"]);
|
||||||
|
|
||||||
|
const go = () => {
|
||||||
|
router.push({ name: "Add Intervention" });
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -0,0 +1,412 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Header with Title and Actions -->
|
||||||
|
<div class="row align-items-center mb-4">
|
||||||
|
<div class="col">
|
||||||
|
<h5 class="mb-0">Informations du défunt</h5>
|
||||||
|
<p class="text-sm text-secondary mb-0">
|
||||||
|
Détails et informations personnelles
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<soft-button
|
||||||
|
color="primary"
|
||||||
|
variant="gradient"
|
||||||
|
class="btn-sm"
|
||||||
|
@click="isEditing = !isEditing"
|
||||||
|
>
|
||||||
|
<i class="fas fa-edit me-2"></i>
|
||||||
|
{{ isEditing ? "Annuler" : "Modifier" }}
|
||||||
|
</soft-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div v-if="isLoading" class="text-center p-4">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Chargement...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div v-else>
|
||||||
|
<!-- View Mode -->
|
||||||
|
<div v-if="!isEditing" class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h6
|
||||||
|
class="text-uppercase text-secondary text-xs font-weight-bolder mb-3"
|
||||||
|
>
|
||||||
|
Informations personnelles
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Personal Info -->
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="border rounded-3 p-3 h-100">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div
|
||||||
|
class="avatar avatar-sm bg-gradient-secondary rounded-circle d-flex align-items-center justify-content-center me-3"
|
||||||
|
>
|
||||||
|
<i class="fas fa-user text-white"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 text-sm">{{ getFullName }}</h6>
|
||||||
|
<p class="text-xs text-secondary mb-0">Nom complet</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="border rounded-3 p-3 h-100">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div
|
||||||
|
class="avatar avatar-sm bg-gradient-info rounded-circle d-flex align-items-center justify-content-center me-3"
|
||||||
|
>
|
||||||
|
<i class="fas fa-calendar-alt text-white"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 text-sm">
|
||||||
|
{{ formatDate(deceased?.birth_date) }}
|
||||||
|
</h6>
|
||||||
|
<p class="text-xs text-secondary mb-0">Date de naissance</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="border rounded-3 p-3 h-100">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div
|
||||||
|
class="avatar avatar-sm bg-gradient-danger rounded-circle d-flex align-items-center justify-content-center me-3"
|
||||||
|
>
|
||||||
|
<i class="fas fa-cross text-white"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 text-sm">
|
||||||
|
{{ formatDate(deceased?.death_date) }}
|
||||||
|
</h6>
|
||||||
|
<p class="text-xs text-secondary mb-0">Date de décès</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="border rounded-3 p-3 h-100">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div
|
||||||
|
class="avatar avatar-sm bg-gradient-warning rounded-circle d-flex align-items-center justify-content-center me-3"
|
||||||
|
>
|
||||||
|
<i class="fas fa-map-marker-alt text-white"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 text-sm">
|
||||||
|
{{ deceased?.place_of_death || "Non renseigné" }}
|
||||||
|
</h6>
|
||||||
|
<p class="text-xs text-secondary mb-0">Lieu de décès</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Age at death -->
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="border rounded-3 p-3">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-auto">
|
||||||
|
<div
|
||||||
|
class="avatar avatar-lg bg-gradient-success rounded-circle d-flex align-items-center justify-content-center"
|
||||||
|
>
|
||||||
|
<i class="fas fa-birthday-cake text-white"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h3 class="mb-0">{{ calculateAgeAtDeath }}</h3>
|
||||||
|
<p class="text-sm text-secondary mb-0">Âge au décès</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notes -->
|
||||||
|
<div class="col-12">
|
||||||
|
<h6
|
||||||
|
class="text-uppercase text-secondary text-xs font-weight-bolder mb-3"
|
||||||
|
>
|
||||||
|
Notes et observations
|
||||||
|
</h6>
|
||||||
|
<div class="border rounded-3 p-3">
|
||||||
|
<p class="text-sm mb-0">
|
||||||
|
{{ deceased?.notes || "Aucune note renseignée" }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Mode -->
|
||||||
|
<div v-else class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h6
|
||||||
|
class="text-uppercase text-secondary text-xs font-weight-bolder mb-3"
|
||||||
|
>
|
||||||
|
Modifier les informations
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label"
|
||||||
|
>Nom de famille <span class="text-danger">*</span></label
|
||||||
|
>
|
||||||
|
<soft-input
|
||||||
|
v-model="editForm.last_name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Nom de famille"
|
||||||
|
:class="{ 'is-invalid': validationErrors.last_name }"
|
||||||
|
/>
|
||||||
|
<div v-if="validationErrors.last_name" class="invalid-feedback">
|
||||||
|
{{ validationErrors.last_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Prénom(s)</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="editForm.first_name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Prénom(s)"
|
||||||
|
:class="{ 'is-invalid': validationErrors.first_name }"
|
||||||
|
/>
|
||||||
|
<div v-if="validationErrors.first_name" class="invalid-feedback">
|
||||||
|
{{ validationErrors.first_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Date de naissance</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="editForm.birth_date"
|
||||||
|
type="date"
|
||||||
|
:class="{ 'is-invalid': validationErrors.birth_date }"
|
||||||
|
/>
|
||||||
|
<div v-if="validationErrors.birth_date" class="invalid-feedback">
|
||||||
|
{{ validationErrors.birth_date }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Date de décès</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="editForm.death_date"
|
||||||
|
type="date"
|
||||||
|
:class="{ 'is-invalid': validationErrors.death_date }"
|
||||||
|
/>
|
||||||
|
<div v-if="validationErrors.death_date" class="invalid-feedback">
|
||||||
|
{{ validationErrors.death_date }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<label class="form-label">Lieu de décès</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="editForm.place_of_death"
|
||||||
|
type="text"
|
||||||
|
placeholder="Lieu de décès"
|
||||||
|
:class="{ 'is-invalid': validationErrors.place_of_death }"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="validationErrors.place_of_death"
|
||||||
|
class="invalid-feedback"
|
||||||
|
>
|
||||||
|
{{ validationErrors.place_of_death }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<label class="form-label">Notes et observations</label>
|
||||||
|
<textarea
|
||||||
|
v-model="editForm.notes"
|
||||||
|
class="form-control"
|
||||||
|
rows="4"
|
||||||
|
placeholder="Notes complémentaires..."
|
||||||
|
:class="{ 'is-invalid': validationErrors.notes }"
|
||||||
|
></textarea>
|
||||||
|
<div v-if="validationErrors.notes" class="invalid-feedback">
|
||||||
|
{{ validationErrors.notes }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<soft-button
|
||||||
|
color="primary"
|
||||||
|
variant="gradient"
|
||||||
|
@click="saveChanges"
|
||||||
|
:disabled="isSaving"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="isSaving"
|
||||||
|
class="spinner-border spinner-border-sm me-2"
|
||||||
|
role="status"
|
||||||
|
></span>
|
||||||
|
{{ isSaving ? "Sauvegarde..." : "Sauvegarder" }}
|
||||||
|
</soft-button>
|
||||||
|
<soft-button
|
||||||
|
color="secondary"
|
||||||
|
variant="outline"
|
||||||
|
@click="cancelEdit"
|
||||||
|
:disabled="isSaving"
|
||||||
|
>
|
||||||
|
Annuler
|
||||||
|
</soft-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from "vue";
|
||||||
|
import { defineProps, defineEmits } from "vue";
|
||||||
|
import SoftInput from "@/components/SoftInput.vue";
|
||||||
|
import SoftButton from "@/components/SoftButton.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
deceased: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update-deceased"]);
|
||||||
|
|
||||||
|
const isEditing = ref(false);
|
||||||
|
const isSaving = ref(false);
|
||||||
|
const validationErrors = ref({});
|
||||||
|
|
||||||
|
const editForm = ref({
|
||||||
|
last_name: "",
|
||||||
|
first_name: "",
|
||||||
|
birth_date: "",
|
||||||
|
death_date: "",
|
||||||
|
place_of_death: "",
|
||||||
|
notes: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const getFullName = computed(() => {
|
||||||
|
if (!props.deceased) return "Nom non renseigné";
|
||||||
|
const parts = [props.deceased.first_name, props.deceased.last_name].filter(
|
||||||
|
Boolean
|
||||||
|
);
|
||||||
|
return parts.join(" ") || "Nom non renseigné";
|
||||||
|
});
|
||||||
|
|
||||||
|
const calculateAgeAtDeath = computed(() => {
|
||||||
|
if (
|
||||||
|
!props.deceased ||
|
||||||
|
!props.deceased.birth_date ||
|
||||||
|
!props.deceased.death_date
|
||||||
|
) {
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
const birthDate = new Date(props.deceased.birth_date);
|
||||||
|
const deathDate = new Date(props.deceased.death_date);
|
||||||
|
const age = deathDate.getFullYear() - birthDate.getFullYear();
|
||||||
|
|
||||||
|
// Adjust if birthday hasn't occurred yet in the year of death
|
||||||
|
const monthDiff = deathDate.getMonth() - birthDate.getMonth();
|
||||||
|
if (
|
||||||
|
monthDiff < 0 ||
|
||||||
|
(monthDiff === 0 && deathDate.getDate() < birthDate.getDate())
|
||||||
|
) {
|
||||||
|
return age - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return age;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
if (!dateString) return "Non renseignée";
|
||||||
|
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString("fr-FR", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const populateEditForm = () => {
|
||||||
|
if (!props.deceased) return;
|
||||||
|
editForm.value = {
|
||||||
|
last_name: props.deceased.last_name || "",
|
||||||
|
first_name: props.deceased.first_name || "",
|
||||||
|
birth_date: props.deceased.birth_date || "",
|
||||||
|
death_date: props.deceased.death_date || "",
|
||||||
|
place_of_death: props.deceased.place_of_death || "",
|
||||||
|
notes: props.deceased.notes || "",
|
||||||
|
};
|
||||||
|
validationErrors.value = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveChanges = async () => {
|
||||||
|
isSaving.value = true;
|
||||||
|
validationErrors.value = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await emit("update-deceased", { ...editForm.value });
|
||||||
|
isEditing.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response?.data?.errors) {
|
||||||
|
validationErrors.value = error.response.data.errors;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
isEditing.value = false;
|
||||||
|
validationErrors.value = {};
|
||||||
|
populateEditForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Watch for deceased changes
|
||||||
|
watch(
|
||||||
|
() => props.deceased,
|
||||||
|
(newDeceased) => {
|
||||||
|
if (newDeceased && isEditing.value) {
|
||||||
|
populateEditForm();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize form when component mounts
|
||||||
|
populateEditForm();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.avatar-sm {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-lg {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
border: 1px solid #dee2e6 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card position-sticky top-1">
|
||||||
|
<!-- Defunt Profile Card -->
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div
|
||||||
|
class="avatar avatar-xl bg-gradient-dark border-radius-md d-flex align-items-center justify-content-center mx-auto mb-3"
|
||||||
|
>
|
||||||
|
<i class="fa fa-user text-white fa-2x"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-1">{{ getFullName }}</h5>
|
||||||
|
<p class="text-xs text-secondary mb-2">
|
||||||
|
Âge au décès: {{ calculateAgeAtDeath }}
|
||||||
|
</p>
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col-4">
|
||||||
|
<h6 class="mb-0 text-sm">{{ deceased?.documents_count || 0 }}</h6>
|
||||||
|
<p class="mb-0 text-xs text-secondary">Documents</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<h6 class="mb-0 text-sm">{{ deceased?.interventions_count || 0 }}</h6>
|
||||||
|
<p class="mb-0 text-xs text-secondary">Interventions</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<h6 class="mb-0 text-sm">{{ formatDate(deceased?.created_at) }}</h6>
|
||||||
|
<p class="mb-0 text-xs text-secondary">Créé le</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="horizontal dark my-3 mx-3" />
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="card-body pt-0">
|
||||||
|
<h6 class="text-uppercase text-secondary text-xs font-weight-bolder mb-3">
|
||||||
|
Actions rapides
|
||||||
|
</h6>
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<soft-button
|
||||||
|
color="primary"
|
||||||
|
variant="gradient"
|
||||||
|
class="btn-sm"
|
||||||
|
@click="$emit('create-intervention')"
|
||||||
|
>
|
||||||
|
<i class="fas fa-plus me-2"></i>
|
||||||
|
Nouvelle intervention
|
||||||
|
</soft-button>
|
||||||
|
<soft-button
|
||||||
|
color="secondary"
|
||||||
|
variant="outline"
|
||||||
|
class="btn-sm"
|
||||||
|
@click="$emit('edit-deceased')"
|
||||||
|
>
|
||||||
|
<i class="fas fa-edit me-2"></i>
|
||||||
|
Modifier
|
||||||
|
</soft-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="horizontal dark my-3 mx-3" />
|
||||||
|
|
||||||
|
<!-- Key Dates -->
|
||||||
|
<div class="card-body pt-0">
|
||||||
|
<h6 class="text-uppercase text-secondary text-xs font-weight-bolder mb-3">
|
||||||
|
Dates importantes
|
||||||
|
</h6>
|
||||||
|
<div class="timeline timeline-one-side">
|
||||||
|
<div class="timeline-block mb-3">
|
||||||
|
<span class="timeline-step">
|
||||||
|
<i class="fas fa-calendar-alt text-primary"></i>
|
||||||
|
</span>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h6 class="text-dark text-sm font-weight-bold mb-0">Naissance</h6>
|
||||||
|
<p class="text-secondary text-xs mb-0">
|
||||||
|
{{ formatDate(deceased?.birth_date) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-block mb-0">
|
||||||
|
<span class="timeline-step">
|
||||||
|
<i class="fas fa-cross text-dark"></i>
|
||||||
|
</span>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h6 class="text-dark text-sm font-weight-bold mb-0">Décès</h6>
|
||||||
|
<p class="text-secondary text-xs mb-0">
|
||||||
|
{{ formatDate(deceased?.death_date) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { defineProps, defineEmits } from "vue";
|
||||||
|
import SoftButton from "@/components/SoftButton.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
deceased: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits(["create-intervention", "edit-deceased"]);
|
||||||
|
|
||||||
|
const getFullName = computed(() => {
|
||||||
|
if (!props.deceased) return "Nom non renseigné";
|
||||||
|
const parts = [props.deceased.first_name, props.deceased.last_name].filter(
|
||||||
|
Boolean
|
||||||
|
);
|
||||||
|
return parts.join(" ") || "Nom non renseigné";
|
||||||
|
});
|
||||||
|
|
||||||
|
const calculateAgeAtDeath = computed(() => {
|
||||||
|
if (
|
||||||
|
!props.deceased ||
|
||||||
|
!props.deceased.birth_date ||
|
||||||
|
!props.deceased.death_date
|
||||||
|
) {
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
const birthDate = new Date(props.deceased.birth_date);
|
||||||
|
const deathDate = new Date(props.deceased.death_date);
|
||||||
|
const age = deathDate.getFullYear() - birthDate.getFullYear();
|
||||||
|
|
||||||
|
// Adjust if birthday hasn't occurred yet in the year of death
|
||||||
|
const monthDiff = deathDate.getMonth() - birthDate.getMonth();
|
||||||
|
if (
|
||||||
|
monthDiff < 0 ||
|
||||||
|
(monthDiff === 0 && deathDate.getDate() < birthDate.getDate())
|
||||||
|
) {
|
||||||
|
return age - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return age;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
if (!dateString) return "Non renseignée";
|
||||||
|
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString("fr-FR", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.position-sticky {
|
||||||
|
top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
min-width: 80px;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-one-side {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-block {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-step {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0.25rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #dee2e6;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-block:not(:last-child)::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0.75rem;
|
||||||
|
top: 2rem;
|
||||||
|
width: 2px;
|
||||||
|
height: calc(100% - 2rem);
|
||||||
|
background: #dee2e6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
310
thanasoft-front/src/components/molecules/Defunts/DefuntForm.vue
Normal file
310
thanasoft-front/src/components/molecules/Defunts/DefuntForm.vue
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card p-3 border-radius-xl bg-white" data-animation="FadeIn">
|
||||||
|
<h5 class="font-weight-bolder mb-0">Informations du défunt</h5>
|
||||||
|
<p class="mb-0 text-sm">Informations de la personne décédée</p>
|
||||||
|
|
||||||
|
<div class="multisteps-form__content">
|
||||||
|
<!-- Nom du défunt -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label"
|
||||||
|
>Nom de famille <span class="text-danger">*</span></label
|
||||||
|
>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.last_name"
|
||||||
|
class="multisteps-form__input"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.last_name }"
|
||||||
|
type="text"
|
||||||
|
placeholder="ex. MARTIN"
|
||||||
|
/>
|
||||||
|
<div v-if="fieldErrors.last_name" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.last_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Prénom du défunt -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Prénom(s)</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.first_name"
|
||||||
|
class="multisteps-form__input"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.first_name }"
|
||||||
|
type="text"
|
||||||
|
placeholder="ex. Jean Pierre"
|
||||||
|
/>
|
||||||
|
<div v-if="fieldErrors.first_name" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.first_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dates de naissance et de décès -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<label class="form-label">Date de naissance</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.birth_date"
|
||||||
|
class="multisteps-form__input"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.birth_date }"
|
||||||
|
type="date"
|
||||||
|
/>
|
||||||
|
<div v-if="fieldErrors.birth_date" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.birth_date }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 mt-3 mt-sm-0">
|
||||||
|
<label class="form-label">Date de décès</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.death_date"
|
||||||
|
class="multisteps-form__input"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.death_date }"
|
||||||
|
type="date"
|
||||||
|
/>
|
||||||
|
<div v-if="fieldErrors.death_date" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.death_date }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lieu de décès -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Lieu de décès</label>
|
||||||
|
<soft-input
|
||||||
|
v-model="form.place_of_death"
|
||||||
|
class="multisteps-form__input"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.place_of_death }"
|
||||||
|
type="text"
|
||||||
|
placeholder="ex. Hôpital Saint-Louis, Paris"
|
||||||
|
/>
|
||||||
|
<div v-if="fieldErrors.place_of_death" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.place_of_death }}
|
||||||
|
</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, causes du décès, circonstances particulières..."
|
||||||
|
maxlength="2000"
|
||||||
|
></textarea>
|
||||||
|
<div v-if="fieldErrors.notes" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.notes }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Validation des dates -->
|
||||||
|
<div v-if="dateValidationError" class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
{{ dateValidationError }}
|
||||||
|
</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 || !!dateValidationError"
|
||||||
|
@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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, defineEmits, watch, computed } from "vue";
|
||||||
|
import SoftInput from "@/components/SoftInput.vue";
|
||||||
|
import SoftButton from "@/components/SoftButton.vue";
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
validationErrors: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(["createDefunt"]);
|
||||||
|
|
||||||
|
// Reactive data
|
||||||
|
const errors = ref([]);
|
||||||
|
const fieldErrors = ref({});
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
last_name: "",
|
||||||
|
first_name: "",
|
||||||
|
birth_date: "",
|
||||||
|
death_date: "",
|
||||||
|
place_of_death: "",
|
||||||
|
notes: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Computed property to validate date logic
|
||||||
|
const dateValidationError = computed(() => {
|
||||||
|
if (!form.value.birth_date || !form.value.death_date) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const birthDate = new Date(form.value.birth_date);
|
||||||
|
const deathDate = new Date(form.value.death_date);
|
||||||
|
|
||||||
|
if (birthDate >= deathDate) {
|
||||||
|
return "La date de décès doit être postérieure à la date de naissance";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if death date is not too far in the future
|
||||||
|
const now = new Date();
|
||||||
|
if (deathDate > now) {
|
||||||
|
return "La date de décès ne peut pas être dans le futur";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for validation errors from parent
|
||||||
|
watch(
|
||||||
|
() => props.validationErrors,
|
||||||
|
(newErrors) => {
|
||||||
|
fieldErrors.value = { ...newErrors };
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Watch for success from parent
|
||||||
|
watch(
|
||||||
|
() => props.success,
|
||||||
|
(newSuccess) => {
|
||||||
|
if (newSuccess) {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const submitForm = async () => {
|
||||||
|
// Clear errors before submitting
|
||||||
|
fieldErrors.value = {};
|
||||||
|
errors.value = [];
|
||||||
|
|
||||||
|
// Check for date validation errors
|
||||||
|
if (dateValidationError.value) {
|
||||||
|
errors.value.push(dateValidationError.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!form.value.last_name || form.value.last_name.trim() === "") {
|
||||||
|
fieldErrors.value.last_name = "Le nom de famille est obligatoire";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up form data: convert empty strings to null
|
||||||
|
const cleanedForm = {};
|
||||||
|
const formData = form.value;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(formData)) {
|
||||||
|
if (value === "" || value === null || value === undefined) {
|
||||||
|
cleanedForm[key] = null;
|
||||||
|
} else {
|
||||||
|
cleanedForm[key] = value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Defunt form data being emitted:", cleanedForm);
|
||||||
|
|
||||||
|
// Emit the cleaned form data to parent
|
||||||
|
emit("createDefunt", cleanedForm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
form.value = {
|
||||||
|
last_name: "",
|
||||||
|
first_name: "",
|
||||||
|
birth_date: "",
|
||||||
|
death_date: "",
|
||||||
|
place_of_death: "",
|
||||||
|
notes: "",
|
||||||
|
};
|
||||||
|
clearErrors();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearErrors = () => {
|
||||||
|
errors.value = [];
|
||||||
|
fieldErrors.value = {};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-label {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #f5365c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid-feedback {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border-sm {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert i {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,17 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div v-if="!defunts || defunts.length === 0" class="empty-state">
|
||||||
|
<div class="empty-message">
|
||||||
|
<i class="fas fa-inbox"></i>
|
||||||
|
<h3>Aucun défunt trouvé</h3>
|
||||||
|
<p>Il semble qu'il n'y ait pas encore de défunts dans cette liste.</p>
|
||||||
|
<soft-button @click="addDeceased" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus"></i> Ajouter un défunt
|
||||||
|
</soft-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="(defunt, index) in defunts"
|
v-for="(defunt, index) in defunts"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="col-xl-4 col-md-6 col-12 mb-4"
|
class="col-xl-4 col-md-6 col-12 mb-4"
|
||||||
>
|
>
|
||||||
<defunt-card
|
<defunt-card
|
||||||
:nom="defunt.nom"
|
:nom="defunt.last_name"
|
||||||
:prenom="defunt.prenom"
|
:prenom="defunt.first_name"
|
||||||
:date_naissance="defunt.date_naissance"
|
:date_naissance="defunt.birth_date"
|
||||||
:date_deces="defunt.date_deces"
|
:date_deces="defunt.death_date"
|
||||||
:lieu_deces="defunt.lieu_deces"
|
:lieu_deces="defunt.place_of_death"
|
||||||
:description="defunt.description"
|
:description="defunt.notes"
|
||||||
:dropdown="dropdownOptions"
|
:dropdown="dropdownOptions"
|
||||||
@dropdown-action="(action) => handleAction(action, defunt)"
|
@dropdown-action="(action) => handleAction(action, defunt)"
|
||||||
/>
|
/>
|
||||||
@ -21,101 +31,32 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import DefuntCard from "@/components/atoms/Defunts/DefuntCard.vue";
|
import DefuntCard from "@/components/atoms/Defunts/DefuntCard.vue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { defineProps } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import SoftButton from "@/components/SoftButton.vue";
|
||||||
|
|
||||||
// Données d'exemple
|
// Router
|
||||||
const defunts = ref([
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Options du dropdown
|
||||||
|
const dropdownOptions = ref([
|
||||||
|
{ label: "Voir les détails", action: "view", route: "#" },
|
||||||
|
{ label: "Modifier", action: "edit", route: "#" },
|
||||||
{
|
{
|
||||||
id: 1,
|
label: "Créer une intervention",
|
||||||
nom: "Dupont",
|
action: "create_intervention",
|
||||||
prenom: "Jean",
|
route: "#",
|
||||||
date_naissance: "1950-03-15",
|
|
||||||
date_deces: "2024-01-10",
|
|
||||||
lieu_deces: "Hôpital Saint-Louis, Paris 10ème",
|
|
||||||
description:
|
|
||||||
"Décédé des suites d'une longue maladie. Souhaitait une cérémonie religieuse catholique. Famille très présente.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
nom: "Martin",
|
|
||||||
prenom: "Sophie",
|
|
||||||
date_naissance: "1965-08-22",
|
|
||||||
date_deces: "2024-01-08",
|
|
||||||
lieu_deces: "Domicile, 92 Rue de Rivoli, Paris 4ème",
|
|
||||||
description:
|
|
||||||
"Décès naturel à domicile. Cérémonie laïque souhaitée. Environ 50 personnes attendues.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
nom: "Bernard",
|
|
||||||
prenom: "Pierre",
|
|
||||||
date_naissance: "1942-11-30",
|
|
||||||
date_deces: "2024-01-05",
|
|
||||||
lieu_deces: "Clinique du Val de Seine, Issy-les-Moulineaux",
|
|
||||||
description:
|
|
||||||
"Ancien militaire, décédé des suites d'un cancer. Demande d'honneurs militaires. Cérémonie au crématorium.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
nom: "Moreau",
|
|
||||||
prenom: "Marie",
|
|
||||||
date_naissance: "1938-07-14",
|
|
||||||
date_deces: "2024-01-03",
|
|
||||||
lieu_deces: "EHPAD Les Jardins Fleuris, Vincennes",
|
|
||||||
description:
|
|
||||||
"Décédée paisiblement à l'EHPAD. Cérémonie religieuse à l'église Saint-Louis de Vincennes. Famille nombreuse.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
nom: "Leroy",
|
|
||||||
prenom: "Philippe",
|
|
||||||
date_naissance: "1972-12-05",
|
|
||||||
date_deces: "2024-01-01",
|
|
||||||
lieu_deces: "Centre Hospitalier Universitaire, Créteil",
|
|
||||||
description:
|
|
||||||
"Décès accidentel. Jeune famille endeuillée. Cérémonie intime souhaitée. Crémation prévue.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
nom: "Petit",
|
|
||||||
prenom: "Claire",
|
|
||||||
date_naissance: "1980-04-18",
|
|
||||||
date_deces: "2023-12-28",
|
|
||||||
lieu_deces: "Hôpital Necker, Paris 15ème",
|
|
||||||
description:
|
|
||||||
"Décédée après une courte maladie. Professeure de musique. Cérémonie avec prestation musicale des élèves.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
nom: "Roux",
|
|
||||||
prenom: "Antoine",
|
|
||||||
date_naissance: "1955-09-12",
|
|
||||||
date_deces: "2023-12-25",
|
|
||||||
lieu_deces: "Maison de retraite Les Tilleuls, Sceaux",
|
|
||||||
description:
|
|
||||||
"Ancien artisan ébéniste. Décès pendant la nuit de Noël. Cérémonie au cimetière du Père Lachaise.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
nom: "Garcia",
|
|
||||||
prenom: "Elena",
|
|
||||||
date_naissance: "1968-02-28",
|
|
||||||
date_deces: "2023-12-20",
|
|
||||||
lieu_deces: "Hôpital Européen Georges Pompidou, Paris",
|
|
||||||
description:
|
|
||||||
"D'origine espagnole. Décédée des suites d'une opération. Double cérémonie française et espagnole prévue.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
nom: "Fournier",
|
|
||||||
prenom: "Michel",
|
|
||||||
date_naissance: "1948-06-08",
|
|
||||||
date_deces: "2023-12-18",
|
|
||||||
lieu_deces: "Centre de Soins Palliatifs, Boulogne-Billancourt",
|
|
||||||
description:
|
|
||||||
"Ancien chef d'entreprise. Décès après une longue maladie. Cérémonie civile au funérarium.",
|
|
||||||
},
|
},
|
||||||
|
{ label: "Supprimer", action: "delete", route: "#" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
defunts: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Gestion des actions du dropdown
|
// Gestion des actions du dropdown
|
||||||
const handleAction = (action, defunt) => {
|
const handleAction = (action, defunt) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@ -124,8 +65,8 @@ const handleAction = (action, defunt) => {
|
|||||||
// Ouvrir modal d'édition
|
// Ouvrir modal d'édition
|
||||||
break;
|
break;
|
||||||
case "view":
|
case "view":
|
||||||
console.log("Voir les détails:", defunt);
|
|
||||||
// Naviguer vers la page détail
|
// Naviguer vers la page détail
|
||||||
|
router.push({ name: "Defunt details", params: { id: defunt.id } });
|
||||||
break;
|
break;
|
||||||
case "create_intervention":
|
case "create_intervention":
|
||||||
console.log("Créer une intervention pour:", defunt);
|
console.log("Créer une intervention pour:", defunt);
|
||||||
@ -136,27 +77,16 @@ const handleAction = (action, defunt) => {
|
|||||||
// Confirmer la suppression
|
// Confirmer la suppression
|
||||||
if (
|
if (
|
||||||
confirm(
|
confirm(
|
||||||
`Êtes-vous sûr de vouloir supprimer ${defunt.prenom} ${defunt.nom} ?`
|
`Êtes-vous sûr de vouloir supprimer ${defunt.first_name} ${defunt.last_name} ?`
|
||||||
)
|
)
|
||||||
) {
|
)
|
||||||
defunts.value = defunts.value.filter((d) => d.id !== defunt.id);
|
break;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fonction pour ajouter un nouveau défunt (exemple)
|
// Navigation to add deceased page
|
||||||
const ajouterDefunt = () => {
|
const addDeceased = () => {
|
||||||
const nouveauDefunt = {
|
router.push({ name: "Add Defunts" });
|
||||||
id: defunts.value.length + 1,
|
|
||||||
nom: "Nouveau",
|
|
||||||
prenom: "Défunt",
|
|
||||||
date_naissance: "1950-01-01",
|
|
||||||
date_deces: "2024-01-15",
|
|
||||||
lieu_deces: "Lieu à définir",
|
|
||||||
description: "Description à compléter",
|
|
||||||
};
|
|
||||||
defunts.value.unshift(nouveauDefunt);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,440 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="multisteps-form__content">
|
||||||
|
<!-- Client selection -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label"
|
||||||
|
>Client <span class="text-danger">*</span></label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="form.client_id"
|
||||||
|
class="form-select multisteps-form__select"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.client_id }"
|
||||||
|
>
|
||||||
|
<option value="">Sélectionnez un client</option>
|
||||||
|
<option
|
||||||
|
v-for="client in clientList"
|
||||||
|
:key="client.id"
|
||||||
|
:value="client.id"
|
||||||
|
>
|
||||||
|
{{ client.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div v-if="fieldErrors.client_id" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.client_id }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Deceased selection -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Personne décédée</label>
|
||||||
|
<select
|
||||||
|
v-model="form.deceased_id"
|
||||||
|
class="form-select multisteps-form__select"
|
||||||
|
:class="{ 'is-invalid': fieldErrors.deceased_id }"
|
||||||
|
>
|
||||||
|
<option value="">Sélectionnez une personne décédée</option>
|
||||||
|
<option
|
||||||
|
v-for="deceased in deceasedList"
|
||||||
|
:key="deceased.id"
|
||||||
|
:value="deceased.id"
|
||||||
|
>
|
||||||
|
{{ deceased.last_name }} {{ deceased.first_name || "" }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div v-if="fieldErrors.deceased_id" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.deceased_id }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Intervention type -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<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="{ '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>
|
||||||
|
</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">
|
||||||
|
{{ 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">
|
||||||
|
{{ 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">
|
||||||
|
{{ 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">
|
||||||
|
{{ fieldErrors.status }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Order giver and notes -->
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
<div v-if="fieldErrors.order_giver" class="invalid-feedback">
|
||||||
|
{{ fieldErrors.order_giver }}
|
||||||
|
</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">
|
||||||
|
{{ fieldErrors.notes }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Validation errors -->
|
||||||
|
<div v-if="formValidationError" class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
{{ formValidationError }}
|
||||||
|
</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 || !!formValidationError"
|
||||||
|
@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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, defineEmits, watch, computed } from "vue";
|
||||||
|
import SoftInput from "@/components/SoftInput.vue";
|
||||||
|
import SoftButton from "@/components/SoftButton.vue";
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
validationErrors: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
deceasedList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
deceasedLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
clientList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
clientLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(["createIntervention"]);
|
||||||
|
|
||||||
|
// Reactive data
|
||||||
|
const errors = ref([]);
|
||||||
|
const fieldErrors = ref({});
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
client_id: "",
|
||||||
|
deceased_id: "",
|
||||||
|
type: "",
|
||||||
|
scheduled_date: "",
|
||||||
|
scheduled_time: "",
|
||||||
|
duration_min: "",
|
||||||
|
status: "",
|
||||||
|
order_giver: "",
|
||||||
|
notes: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Computed property to validate form
|
||||||
|
const formValidationError = computed(() => {
|
||||||
|
if (!form.value.client_id) {
|
||||||
|
return "Le client est obligatoire";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.value.type || form.value.type === "") {
|
||||||
|
return "Le type d'intervention est obligatoire";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for validation errors from parent
|
||||||
|
watch(
|
||||||
|
() => props.validationErrors,
|
||||||
|
(newErrors) => {
|
||||||
|
fieldErrors.value = { ...newErrors };
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Watch for success from parent
|
||||||
|
watch(
|
||||||
|
() => props.success,
|
||||||
|
(newSuccess) => {
|
||||||
|
if (newSuccess) {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const submitForm = async () => {
|
||||||
|
// Clear errors before submitting
|
||||||
|
fieldErrors.value = {};
|
||||||
|
errors.value = [];
|
||||||
|
|
||||||
|
// Check for form validation errors
|
||||||
|
if (formValidationError.value) {
|
||||||
|
errors.value.push(formValidationError.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!form.value.client_id || form.value.client_id === "") {
|
||||||
|
fieldErrors.value.client_id = "Le client est obligatoire";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.value.type || form.value.type === "") {
|
||||||
|
fieldErrors.value.type = "Le type d'intervention est obligatoire";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up form data: convert empty strings to null
|
||||||
|
const cleanedForm = {};
|
||||||
|
const formData = form.value;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(formData)) {
|
||||||
|
if (value === "" || value === null || value === undefined) {
|
||||||
|
cleanedForm[key] = null;
|
||||||
|
} else {
|
||||||
|
cleanedForm[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine date and time into scheduled_at (backend expects Y-m-d H:i:s format)
|
||||||
|
if (cleanedForm.scheduled_date && cleanedForm.scheduled_time) {
|
||||||
|
// Format: Y-m-d H:i:s (e.g., "2024-12-15 14:30:00")
|
||||||
|
const formattedDate = cleanedForm.scheduled_date;
|
||||||
|
const formattedTime = cleanedForm.scheduled_time;
|
||||||
|
cleanedForm.scheduled_at = `${formattedDate} ${formattedTime}:00`;
|
||||||
|
delete cleanedForm.scheduled_date;
|
||||||
|
delete cleanedForm.scheduled_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Intervention form data being emitted:", cleanedForm);
|
||||||
|
|
||||||
|
// Emit the cleaned form data to parent
|
||||||
|
emit("createIntervention", cleanedForm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
form.value = {
|
||||||
|
client_id: "",
|
||||||
|
deceased_id: "",
|
||||||
|
type: "",
|
||||||
|
scheduled_date: "",
|
||||||
|
scheduled_time: "",
|
||||||
|
duration_min: "",
|
||||||
|
status: "",
|
||||||
|
order_giver: "",
|
||||||
|
notes: "",
|
||||||
|
};
|
||||||
|
clearErrors();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearErrors = () => {
|
||||||
|
errors.value = [];
|
||||||
|
fieldErrors.value = {};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-label {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #f5365c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid-feedback {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border-sm {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert i {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multisteps-form__select {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #d2d6da;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: #495057;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multisteps-form__select:focus {
|
||||||
|
border-color: #5e72e4;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(94, 114, 228, 0.25);
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -29,155 +29,50 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import CardInterventions from "@/components/atoms/Interventions/CardInterventions.vue";
|
import CardInterventions from "@/components/atoms/Interventions/CardInterventions.vue";
|
||||||
import { defineProps } from "vue";
|
import { defineProps, computed } from "vue";
|
||||||
// Exemple de données
|
const props = defineProps({
|
||||||
const interventions = [
|
|
||||||
{
|
|
||||||
title: "Cérémonie religieuse",
|
|
||||||
status: {
|
|
||||||
label: "Confirmé",
|
|
||||||
color: "success",
|
|
||||||
variant: "fill",
|
|
||||||
size: "md",
|
|
||||||
},
|
|
||||||
date: "15 Décembre 2024 - 14:00",
|
|
||||||
defuntName: "Jean Dupont",
|
|
||||||
lieux: "Église Saint-Pierre, Paris",
|
|
||||||
duree: "1h30",
|
|
||||||
description:
|
|
||||||
"Cérémonie religieuse traditionnelle suivie d'une bénédiction.",
|
|
||||||
action: {
|
|
||||||
label: "Voir détails",
|
|
||||||
color: "primary",
|
|
||||||
},
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
image: "/images/avatar1.jpg",
|
|
||||||
name: "Marie Curie",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/avatar2.jpg",
|
|
||||||
name: "Pierre Durand",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Crémation",
|
|
||||||
status: {
|
|
||||||
label: "En attente",
|
|
||||||
color: "warning",
|
|
||||||
variant: "fill",
|
|
||||||
size: "md",
|
|
||||||
},
|
|
||||||
date: "16 Décembre 2024 - 10:00",
|
|
||||||
defuntName: "Sophie Martin",
|
|
||||||
lieux: "Crématorium de Lyon",
|
|
||||||
duree: "45 minutes",
|
|
||||||
description: "Cérémonie de crémation avec discours des proches.",
|
|
||||||
action: {
|
|
||||||
label: "Modifier",
|
|
||||||
color: "info",
|
|
||||||
},
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
image: "/images/avatar3.jpg",
|
|
||||||
name: "Luc Bernard",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Inhumation",
|
|
||||||
status: {
|
|
||||||
label: "Annulé",
|
|
||||||
color: "danger",
|
|
||||||
variant: "fill",
|
|
||||||
size: "md",
|
|
||||||
},
|
|
||||||
date: "17 Décembre 2024 - 11:00",
|
|
||||||
defuntName: "Paul Lefebvre",
|
|
||||||
lieux: "Cimetière du Père Lachaise, Paris",
|
|
||||||
duree: "2 heures",
|
|
||||||
description: "Inhumation suivie d'une réception familiale.",
|
|
||||||
action: {
|
|
||||||
label: "Voir raisons",
|
|
||||||
color: "secondary",
|
|
||||||
},
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
image: "/images/avatar4.jpg",
|
|
||||||
name: "Alice Moreau",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/avatar5.jpg",
|
|
||||||
name: "Robert Petit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/avatar6.jpg",
|
|
||||||
name: "Claire Blanc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Cérémonie laïque",
|
|
||||||
status: {
|
|
||||||
label: "Planifié",
|
|
||||||
color: "info",
|
|
||||||
variant: "gradient",
|
|
||||||
size: "md",
|
|
||||||
},
|
|
||||||
date: "18 Décembre 2024 - 15:30",
|
|
||||||
defuntName: "Michelle Rousseau",
|
|
||||||
lieux: "Salle des fêtes, Marseille",
|
|
||||||
duree: "1 heure",
|
|
||||||
description: "Cérémonie laïque avec partage de souvenirs et musique.",
|
|
||||||
action: {
|
|
||||||
label: "Préparer",
|
|
||||||
color: "success",
|
|
||||||
},
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
image: "/images/avatar7.jpg",
|
|
||||||
name: "Thomas Leroy",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/avatar8.jpg",
|
|
||||||
name: "Isabelle Marchand",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Transport funéraire",
|
|
||||||
status: {
|
|
||||||
label: "Terminé",
|
|
||||||
color: "dark",
|
|
||||||
variant: "fill",
|
|
||||||
size: "sm",
|
|
||||||
},
|
|
||||||
date: "14 Décembre 2024 - 09:00",
|
|
||||||
defuntName: "Henri Garnier",
|
|
||||||
lieux: "De l'hôpital au funérarium",
|
|
||||||
duree: "30 minutes",
|
|
||||||
description: "Transport du défunt vers le funérarium.",
|
|
||||||
action: {
|
|
||||||
label: "Voir rapport",
|
|
||||||
color: "dark",
|
|
||||||
},
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
image: "/images/avatar9.jpg",
|
|
||||||
name: "David Morel",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Props optionnelles pour permettre le passage de données externes
|
|
||||||
defineProps({
|
|
||||||
interventionData: {
|
interventionData: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Use provided data if available, otherwise fall back to default
|
||||||
|
const interventions = computed(() => {
|
||||||
|
return props.interventionData.length > 0
|
||||||
|
? props.interventionData
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
title: "Cérémonie religieuse",
|
||||||
|
status: {
|
||||||
|
label: "Confirmé",
|
||||||
|
color: "success",
|
||||||
|
variant: "fill",
|
||||||
|
size: "md",
|
||||||
|
},
|
||||||
|
date: "15 Décembre 2024 - 14:00",
|
||||||
|
defuntName: "Jean Dupont",
|
||||||
|
lieux: "Église Saint-Pierre, Paris",
|
||||||
|
duree: "1h30",
|
||||||
|
description:
|
||||||
|
"Cérémonie religieuse traditionnelle suivie d'une bénédiction.",
|
||||||
|
action: {
|
||||||
|
label: "Voir détails",
|
||||||
|
color: "primary",
|
||||||
|
},
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
image: "/images/avatar1.jpg",
|
||||||
|
name: "Marie Curie",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/avatar2.jpg",
|
||||||
|
name: "Pierre Durand",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="multisteps-form mb-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8 mx-auto my-5">
|
||||||
|
<slot name="multi-step" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--form panels-->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8 m-auto">
|
||||||
|
<slot name="defunt-form" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<div class="row mb-4">
|
||||||
|
<slot name="button-return" />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<slot name="defunt-detail-sidebar" />
|
||||||
|
<slot name="file-input" />
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-9 mt-lg-0 mt-4">
|
||||||
|
<slot name="defunt-detail-content" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="multisteps-form mb-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8 mx-auto my-5">
|
||||||
|
<slot name="multi-step" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--form panels-->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8 m-auto">
|
||||||
|
<slot name="intervention-form" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script></script>
|
||||||
@ -574,6 +574,11 @@ const routes = [
|
|||||||
name: "Interventions",
|
name: "Interventions",
|
||||||
component: () => import("@/views/pages/Interventions/Interventions.vue"),
|
component: () => import("@/views/pages/Interventions/Interventions.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/interventions/new",
|
||||||
|
name: "Add Intervention",
|
||||||
|
component: () => import("@/views/pages/Interventions/AddIntervention.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/intervention",
|
path: "/intervention",
|
||||||
name: "Intervention details",
|
name: "Intervention details",
|
||||||
@ -585,6 +590,16 @@ const routes = [
|
|||||||
name: "Defunts",
|
name: "Defunts",
|
||||||
component: () => import("@/views/pages/Defunts/ListeDefunts.vue"),
|
component: () => import("@/views/pages/Defunts/ListeDefunts.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/defunts/new",
|
||||||
|
name: "Add Defunts",
|
||||||
|
component: () => import("@/views/pages/Defunts/AddDefunt.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/defunts/:id",
|
||||||
|
name: "Defunt details",
|
||||||
|
component: () => import("@/views/pages/Defunts/DefuntDetails.vue"),
|
||||||
|
},
|
||||||
// Paramétrage
|
// Paramétrage
|
||||||
{
|
{
|
||||||
path: "/parametrage/droits",
|
path: "/parametrage/droits",
|
||||||
|
|||||||
148
thanasoft-front/src/services/deceased.ts
Normal file
148
thanasoft-front/src/services/deceased.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { request } from "./http";
|
||||||
|
|
||||||
|
export interface Deceased {
|
||||||
|
id?: number;
|
||||||
|
last_name: string;
|
||||||
|
first_name?: string;
|
||||||
|
full_name?: string;
|
||||||
|
birth_date?: string;
|
||||||
|
death_date?: string;
|
||||||
|
place_of_death?: string;
|
||||||
|
notes?: string;
|
||||||
|
documents_count?: number;
|
||||||
|
interventions_count?: number;
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeceasedListResponse {
|
||||||
|
data: Deceased[];
|
||||||
|
meta?: {
|
||||||
|
current_page: number;
|
||||||
|
last_page: number;
|
||||||
|
per_page: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeceasedResponse {
|
||||||
|
data: Deceased;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateDeceasedPayload {
|
||||||
|
last_name: string;
|
||||||
|
first_name?: string;
|
||||||
|
birth_date?: string;
|
||||||
|
death_date?: string;
|
||||||
|
place_of_death?: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateDeceasedPayload extends Partial<CreateDeceasedPayload> {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeceasedService = {
|
||||||
|
/**
|
||||||
|
* Get all deceased with pagination
|
||||||
|
*/
|
||||||
|
async getAllDeceased(params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
search?: string;
|
||||||
|
sort_by?: string;
|
||||||
|
sort_order?: "asc" | "desc";
|
||||||
|
}): Promise<DeceasedListResponse> {
|
||||||
|
const response = await request<DeceasedListResponse>({
|
||||||
|
url: "/api/deceased",
|
||||||
|
method: "get",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific deceased by ID
|
||||||
|
*/
|
||||||
|
async getDeceased(id: number): Promise<Deceased> {
|
||||||
|
const response = await request<Deceased>({
|
||||||
|
url: `/api/deceased/${id}`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new deceased
|
||||||
|
*/
|
||||||
|
async createDeceased(
|
||||||
|
payload: CreateDeceasedPayload
|
||||||
|
): Promise<DeceasedResponse> {
|
||||||
|
const response = await request<DeceasedResponse>({
|
||||||
|
url: "/api/deceased",
|
||||||
|
method: "post",
|
||||||
|
data: payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing deceased
|
||||||
|
*/
|
||||||
|
async updateDeceased(
|
||||||
|
payload: UpdateDeceasedPayload
|
||||||
|
): Promise<DeceasedResponse> {
|
||||||
|
const { id, ...updateData } = payload;
|
||||||
|
|
||||||
|
const response = await request<DeceasedResponse>({
|
||||||
|
url: `/api/deceased/${id}`,
|
||||||
|
method: "put",
|
||||||
|
data: updateData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a deceased
|
||||||
|
*/
|
||||||
|
async deleteDeceased(
|
||||||
|
id: number
|
||||||
|
): Promise<{ success: boolean; message: string }> {
|
||||||
|
const response = await request<{ success: boolean; message: string }>({
|
||||||
|
url: `/api/deceased/${id}`,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search deceased by name or other criteria
|
||||||
|
*/
|
||||||
|
async searchDeceased(
|
||||||
|
query: string,
|
||||||
|
params?: {
|
||||||
|
exact_match?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<Deceased[]> {
|
||||||
|
const response = await request<{
|
||||||
|
data: Deceased[];
|
||||||
|
count: number;
|
||||||
|
message: string;
|
||||||
|
}>({
|
||||||
|
url: "/api/deceased/searchBy",
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
search: query,
|
||||||
|
exact_match: params?.exact_match || false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data; // Return just the data array
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeceasedService;
|
||||||
237
thanasoft-front/src/services/intervention.ts
Normal file
237
thanasoft-front/src/services/intervention.ts
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import { request } from "./http";
|
||||||
|
|
||||||
|
export interface Intervention {
|
||||||
|
id?: number;
|
||||||
|
client_id: number;
|
||||||
|
deceased_id: number;
|
||||||
|
order_giver?: string;
|
||||||
|
location_id?: number;
|
||||||
|
type?: string;
|
||||||
|
scheduled_at?: string;
|
||||||
|
duration_min?: number;
|
||||||
|
status?: string;
|
||||||
|
assigned_practitioner_id?: number;
|
||||||
|
attachments_count?: number;
|
||||||
|
notes?: string;
|
||||||
|
created_by?: number;
|
||||||
|
// Relations
|
||||||
|
client?: any;
|
||||||
|
deceased?: any;
|
||||||
|
practitioner?: any;
|
||||||
|
location?: any;
|
||||||
|
// Timestamps
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InterventionListResponse {
|
||||||
|
data: Intervention[];
|
||||||
|
meta?: {
|
||||||
|
current_page: number;
|
||||||
|
last_page: number;
|
||||||
|
per_page: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InterventionResponse {
|
||||||
|
data: Intervention;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateInterventionPayload {
|
||||||
|
client_id: number;
|
||||||
|
deceased_id: number;
|
||||||
|
order_giver?: string;
|
||||||
|
location_id?: number;
|
||||||
|
type?: string;
|
||||||
|
scheduled_at?: string;
|
||||||
|
duration_min?: number;
|
||||||
|
status?: string;
|
||||||
|
assigned_practitioner_id?: number;
|
||||||
|
notes?: string;
|
||||||
|
created_by?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateInterventionPayload
|
||||||
|
extends Partial<CreateInterventionPayload> {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InterventionService = {
|
||||||
|
/**
|
||||||
|
* Get all interventions with pagination and filters
|
||||||
|
*/
|
||||||
|
async getAllInterventions(params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
search?: string;
|
||||||
|
deceased_id?: number;
|
||||||
|
client_id?: number;
|
||||||
|
status?: string;
|
||||||
|
scheduled_at?: string;
|
||||||
|
sort_by?: string;
|
||||||
|
sort_order?: "asc" | "desc";
|
||||||
|
}): Promise<InterventionListResponse> {
|
||||||
|
const response = await request<InterventionListResponse>({
|
||||||
|
url: "/api/interventions",
|
||||||
|
method: "get",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific intervention by ID
|
||||||
|
*/
|
||||||
|
async getIntervention(id: number): Promise<Intervention> {
|
||||||
|
const response = await request<Intervention>({
|
||||||
|
url: `/api/interventions/${id}`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new intervention
|
||||||
|
*/
|
||||||
|
async createIntervention(
|
||||||
|
payload: CreateInterventionPayload
|
||||||
|
): Promise<Intervention> {
|
||||||
|
const response = await request<Intervention>({
|
||||||
|
url: "/api/interventions",
|
||||||
|
method: "post",
|
||||||
|
data: payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing intervention
|
||||||
|
*/
|
||||||
|
async updateIntervention(
|
||||||
|
payload: UpdateInterventionPayload
|
||||||
|
): Promise<Intervention> {
|
||||||
|
const { id, ...updateData } = payload;
|
||||||
|
|
||||||
|
const response = await request<Intervention>({
|
||||||
|
url: `/api/interventions/${id}`,
|
||||||
|
method: "put",
|
||||||
|
data: updateData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an intervention
|
||||||
|
*/
|
||||||
|
async deleteIntervention(
|
||||||
|
id: number
|
||||||
|
): Promise<{ success: boolean; message: string }> {
|
||||||
|
const response = await request<{ success: boolean; message: string }>({
|
||||||
|
url: `/api/interventions/${id}`,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get interventions by deceased ID
|
||||||
|
*/
|
||||||
|
async getInterventionsByDeceased(
|
||||||
|
deceasedId: number,
|
||||||
|
params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
}
|
||||||
|
): Promise<InterventionListResponse> {
|
||||||
|
const response = await request<InterventionListResponse>({
|
||||||
|
url: `/api/deceased/${deceasedId}/interventions`,
|
||||||
|
method: "get",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get interventions by client ID
|
||||||
|
*/
|
||||||
|
async getInterventionsByClient(
|
||||||
|
clientId: number,
|
||||||
|
params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
}
|
||||||
|
): Promise<InterventionListResponse> {
|
||||||
|
const response = await request<InterventionListResponse>({
|
||||||
|
url: `/api/clients/${clientId}/interventions`,
|
||||||
|
method: "get",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search interventions by various criteria
|
||||||
|
*/
|
||||||
|
async searchInterventions(
|
||||||
|
query: string,
|
||||||
|
params?: {
|
||||||
|
exact_match?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<Intervention[]> {
|
||||||
|
const response = await request<{
|
||||||
|
data: Intervention[];
|
||||||
|
count: number;
|
||||||
|
message: string;
|
||||||
|
}>({
|
||||||
|
url: "/api/interventions/search",
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
search: query,
|
||||||
|
exact_match: params?.exact_match || false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update intervention status
|
||||||
|
*/
|
||||||
|
async updateInterventionStatus(
|
||||||
|
id: number,
|
||||||
|
status: string
|
||||||
|
): Promise<Intervention> {
|
||||||
|
const response = await request<Intervention>({
|
||||||
|
url: `/api/interventions/${id}/status`,
|
||||||
|
method: "patch",
|
||||||
|
data: { status },
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign practitioner to intervention
|
||||||
|
*/
|
||||||
|
async assignPractitioner(
|
||||||
|
id: number,
|
||||||
|
practitionerId: number
|
||||||
|
): Promise<Intervention> {
|
||||||
|
const response = await request<Intervention>({
|
||||||
|
url: `/api/interventions/${id}/assign`,
|
||||||
|
method: "patch",
|
||||||
|
data: { assigned_practitioner_id: practitionerId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InterventionService;
|
||||||
327
thanasoft-front/src/stores/deceasedStore.ts
Normal file
327
thanasoft-front/src/stores/deceasedStore.ts
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import DeceasedService, {
|
||||||
|
Deceased,
|
||||||
|
CreateDeceasedPayload,
|
||||||
|
UpdateDeceasedPayload,
|
||||||
|
DeceasedListResponse,
|
||||||
|
} from "@/services/deceased";
|
||||||
|
|
||||||
|
export const useDeceasedStore = defineStore("deceased", () => {
|
||||||
|
// State
|
||||||
|
const deceased = ref<Deceased[]>([]);
|
||||||
|
const currentDeceased = ref<Deceased | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
const success = ref(false);
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const pagination = ref({
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
per_page: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
const allDeceased = computed(() => deceased.value);
|
||||||
|
|
||||||
|
const isLoading = computed(() => loading.value);
|
||||||
|
const hasError = computed(() => error.value !== null);
|
||||||
|
const getError = computed(() => error.value);
|
||||||
|
const isSuccess = computed(() => success.value);
|
||||||
|
|
||||||
|
const getDeceasedById = computed(() => (id: number) =>
|
||||||
|
deceased.value.find((d) => d.id === id)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPagination = computed(() => pagination.value);
|
||||||
|
|
||||||
|
const getDeceasedFullName = computed(() => (deceased: Deceased) => {
|
||||||
|
const parts = [deceased.first_name, deceased.last_name].filter(Boolean);
|
||||||
|
return parts.join(" ").trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
const setLoading = (isLoading: boolean) => {
|
||||||
|
loading.value = isLoading;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setError = (err: string | null) => {
|
||||||
|
error.value = err;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearError = () => {
|
||||||
|
error.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSuccess = (isSuccess: boolean) => {
|
||||||
|
success.value = isSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSuccess = () => {
|
||||||
|
success.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setDeceased = (newDeceased: Deceased[]) => {
|
||||||
|
deceased.value = newDeceased;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCurrentDeceased = (deceased: Deceased | null) => {
|
||||||
|
currentDeceased.value = deceased;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPagination = (meta: any) => {
|
||||||
|
if (meta) {
|
||||||
|
pagination.value = {
|
||||||
|
current_page: meta.current_page || 1,
|
||||||
|
last_page: meta.last_page || 1,
|
||||||
|
per_page: meta.per_page || 10,
|
||||||
|
total: meta.total || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all deceased with pagination and filters
|
||||||
|
*/
|
||||||
|
const fetchDeceased = async (params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
search?: string;
|
||||||
|
sort_by?: string;
|
||||||
|
sort_order?: "asc" | "desc";
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await DeceasedService.getAllDeceased(params);
|
||||||
|
setDeceased(response.data);
|
||||||
|
if (response.meta) {
|
||||||
|
setPagination(response.meta);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec du chargement des personnes décédées";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single deceased by ID
|
||||||
|
*/
|
||||||
|
const fetchDeceasedById = async (id: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deceased = await DeceasedService.getDeceased(id);
|
||||||
|
setCurrentDeceased(deceased);
|
||||||
|
return deceased;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec du chargement de la personne décédée";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new deceased
|
||||||
|
*/
|
||||||
|
const createDeceased = async (payload: CreateDeceasedPayload) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await DeceasedService.createDeceased(payload);
|
||||||
|
// Add the new deceased to the list
|
||||||
|
deceased.value.unshift(response.data);
|
||||||
|
setCurrentDeceased(response.data);
|
||||||
|
setSuccess(true);
|
||||||
|
return response.data;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la création de la personne décédée";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing deceased
|
||||||
|
*/
|
||||||
|
const updateDeceased = async (payload: UpdateDeceasedPayload) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await DeceasedService.updateDeceased(payload);
|
||||||
|
const updatedDeceased = response.data;
|
||||||
|
|
||||||
|
// Update in the deceased list
|
||||||
|
const index = deceased.value.findIndex(
|
||||||
|
(d) => d.id === updatedDeceased.id
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
deceased.value[index] = updatedDeceased;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current deceased if it's the one being edited
|
||||||
|
if (
|
||||||
|
currentDeceased.value &&
|
||||||
|
currentDeceased.value.id === updatedDeceased.id
|
||||||
|
) {
|
||||||
|
setCurrentDeceased(updatedDeceased);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuccess(true);
|
||||||
|
return updatedDeceased;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la mise à jour de la personne décédée";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a deceased
|
||||||
|
*/
|
||||||
|
const deleteDeceased = async (id: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await DeceasedService.deleteDeceased(id);
|
||||||
|
|
||||||
|
// Remove from the deceased list
|
||||||
|
deceased.value = deceased.value.filter((d) => d.id !== id);
|
||||||
|
|
||||||
|
// Clear current deceased if it's the one being deleted
|
||||||
|
if (currentDeceased.value && currentDeceased.value.id === id) {
|
||||||
|
setCurrentDeceased(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la suppression de la personne décédée";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search deceased by name or other criteria
|
||||||
|
*/
|
||||||
|
const searchDeceased = async (
|
||||||
|
query: string,
|
||||||
|
params?: {
|
||||||
|
exact_match?: boolean;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const results = await DeceasedService.searchDeceased(query, params);
|
||||||
|
setDeceased(results);
|
||||||
|
return results;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la recherche de personnes décédées";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the state
|
||||||
|
*/
|
||||||
|
const resetState = () => {
|
||||||
|
deceased.value = [];
|
||||||
|
currentDeceased.value = null;
|
||||||
|
loading.value = false;
|
||||||
|
error.value = null;
|
||||||
|
success.value = false;
|
||||||
|
pagination.value = {
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
per_page: 10,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
deceased,
|
||||||
|
currentDeceased,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
success,
|
||||||
|
pagination,
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
allDeceased,
|
||||||
|
isLoading,
|
||||||
|
hasError,
|
||||||
|
getError,
|
||||||
|
isSuccess,
|
||||||
|
getDeceasedById,
|
||||||
|
getPagination,
|
||||||
|
getDeceasedFullName,
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setLoading,
|
||||||
|
setError,
|
||||||
|
clearError,
|
||||||
|
setSuccess,
|
||||||
|
clearSuccess,
|
||||||
|
setDeceased,
|
||||||
|
setCurrentDeceased,
|
||||||
|
setPagination,
|
||||||
|
fetchDeceased,
|
||||||
|
fetchDeceasedById,
|
||||||
|
createDeceased,
|
||||||
|
updateDeceased,
|
||||||
|
deleteDeceased,
|
||||||
|
searchDeceased,
|
||||||
|
resetState,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useDeceasedStore;
|
||||||
523
thanasoft-front/src/stores/interventionStore.ts
Normal file
523
thanasoft-front/src/stores/interventionStore.ts
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import InterventionService from "@/services/intervention";
|
||||||
|
import type {
|
||||||
|
Intervention,
|
||||||
|
CreateInterventionPayload,
|
||||||
|
UpdateInterventionPayload,
|
||||||
|
InterventionListResponse,
|
||||||
|
} from "@/services/intervention";
|
||||||
|
|
||||||
|
export const useInterventionStore = defineStore("intervention", () => {
|
||||||
|
// State
|
||||||
|
const interventions = ref<Intervention[]>([]);
|
||||||
|
const currentIntervention = ref<Intervention | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
const success = ref(false);
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const pagination = ref({
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
per_page: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
const allInterventions = computed(() => interventions.value);
|
||||||
|
|
||||||
|
const isLoading = computed(() => loading.value);
|
||||||
|
const hasError = computed(() => error.value !== null);
|
||||||
|
const getError = computed(() => error.value);
|
||||||
|
const isSuccess = computed(() => success.value);
|
||||||
|
|
||||||
|
const getInterventionById = computed(() => (id: number) =>
|
||||||
|
interventions.value.find((i) => i.id === id)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPagination = computed(() => pagination.value);
|
||||||
|
|
||||||
|
const getInterventionsByDeceased = computed(() => (deceasedId: number) =>
|
||||||
|
interventions.value.filter((i) => i.deceased_id === deceasedId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getInterventionsByClient = computed(() => (clientId: number) =>
|
||||||
|
interventions.value.filter((i) => i.client_id === clientId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getScheduledInterventions = computed(() =>
|
||||||
|
interventions.value.filter(
|
||||||
|
(i) => i.scheduled_at && new Date(i.scheduled_at) > new Date()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getCompletedInterventions = computed(() =>
|
||||||
|
interventions.value.filter((i) => i.status === "completed")
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPendingInterventions = computed(() =>
|
||||||
|
interventions.value.filter((i) => i.status === "pending")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
const setLoading = (isLoading: boolean) => {
|
||||||
|
loading.value = isLoading;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setError = (err: string | null) => {
|
||||||
|
error.value = err;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearError = () => {
|
||||||
|
error.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSuccess = (isSuccess: boolean) => {
|
||||||
|
success.value = isSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSuccess = () => {
|
||||||
|
success.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setInterventions = (newInterventions: Intervention[]) => {
|
||||||
|
interventions.value = newInterventions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCurrentIntervention = (intervention: Intervention | null) => {
|
||||||
|
currentIntervention.value = intervention;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPagination = (meta: any) => {
|
||||||
|
if (meta) {
|
||||||
|
pagination.value = {
|
||||||
|
current_page: meta.current_page || 1,
|
||||||
|
last_page: meta.last_page || 1,
|
||||||
|
per_page: meta.per_page || 10,
|
||||||
|
total: meta.total || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all interventions with pagination and filters
|
||||||
|
*/
|
||||||
|
const fetchInterventions = async (params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
search?: string;
|
||||||
|
deceased_id?: number;
|
||||||
|
client_id?: number;
|
||||||
|
status?: string;
|
||||||
|
scheduled_at?: string;
|
||||||
|
sort_by?: string;
|
||||||
|
sort_order?: "asc" | "desc";
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await InterventionService.getAllInterventions(params);
|
||||||
|
setInterventions(response.data);
|
||||||
|
if (response.meta) {
|
||||||
|
setPagination(response.meta);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec du chargement des interventions";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single intervention by ID
|
||||||
|
*/
|
||||||
|
const fetchInterventionById = async (id: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const intervention = await InterventionService.getIntervention(id);
|
||||||
|
setCurrentIntervention(intervention);
|
||||||
|
return intervention;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec du chargement de l'intervention";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new intervention
|
||||||
|
*/
|
||||||
|
const createIntervention = async (payload: CreateInterventionPayload) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const intervention = await InterventionService.createIntervention(
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
// Add the new intervention to the list
|
||||||
|
interventions.value.unshift(intervention);
|
||||||
|
setCurrentIntervention(intervention);
|
||||||
|
setSuccess(true);
|
||||||
|
return intervention;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la création de l'intervention";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing intervention
|
||||||
|
*/
|
||||||
|
const updateIntervention = async (payload: UpdateInterventionPayload) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const intervention = await InterventionService.updateIntervention(
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update in the interventions list
|
||||||
|
const index = interventions.value.findIndex(
|
||||||
|
(i) => i.id === intervention.id
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
interventions.value[index] = intervention;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current intervention if it's the one being edited
|
||||||
|
if (
|
||||||
|
currentIntervention.value &&
|
||||||
|
currentIntervention.value.id === intervention.id
|
||||||
|
) {
|
||||||
|
setCurrentIntervention(intervention);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuccess(true);
|
||||||
|
return intervention;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la mise à jour de l'intervention";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an intervention
|
||||||
|
*/
|
||||||
|
const deleteIntervention = async (id: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await InterventionService.deleteIntervention(id);
|
||||||
|
|
||||||
|
// Remove from the interventions list
|
||||||
|
interventions.value = interventions.value.filter((i) => i.id !== id);
|
||||||
|
|
||||||
|
// Clear current intervention if it's the one being deleted
|
||||||
|
if (currentIntervention.value && currentIntervention.value.id === id) {
|
||||||
|
setCurrentIntervention(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la suppression de l'intervention";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get interventions by deceased ID
|
||||||
|
*/
|
||||||
|
const fetchInterventionsByDeceased = async (
|
||||||
|
deceasedId: number,
|
||||||
|
params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await InterventionService.getInterventionsByDeceased(
|
||||||
|
deceasedId,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
setInterventions(response.data);
|
||||||
|
if (response.meta) {
|
||||||
|
setPagination(response.meta);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec du chargement des interventions du défunt";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get interventions by client ID
|
||||||
|
*/
|
||||||
|
const fetchInterventionsByClient = async (
|
||||||
|
clientId: number,
|
||||||
|
params?: {
|
||||||
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await InterventionService.getInterventionsByClient(
|
||||||
|
clientId,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
setInterventions(response.data);
|
||||||
|
if (response.meta) {
|
||||||
|
setPagination(response.meta);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec du chargement des interventions du client";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search interventions by various criteria
|
||||||
|
*/
|
||||||
|
const searchInterventions = async (
|
||||||
|
query: string,
|
||||||
|
params?: {
|
||||||
|
exact_match?: boolean;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const results = await InterventionService.searchInterventions(
|
||||||
|
query,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
setInterventions(results);
|
||||||
|
return results;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la recherche d'interventions";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update intervention status
|
||||||
|
*/
|
||||||
|
const updateInterventionStatus = async (id: number, status: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const intervention = await InterventionService.updateInterventionStatus(
|
||||||
|
id,
|
||||||
|
status
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update in the interventions list
|
||||||
|
const index = interventions.value.findIndex(
|
||||||
|
(i) => i.id === intervention.id
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
interventions.value[index] = intervention;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current intervention if it's the one being updated
|
||||||
|
if (
|
||||||
|
currentIntervention.value &&
|
||||||
|
currentIntervention.value.id === intervention.id
|
||||||
|
) {
|
||||||
|
setCurrentIntervention(intervention);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuccess(true);
|
||||||
|
return intervention;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de la mise à jour du statut";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign practitioner to intervention
|
||||||
|
*/
|
||||||
|
const assignPractitioner = async (id: number, practitionerId: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const intervention = await InterventionService.assignPractitioner(
|
||||||
|
id,
|
||||||
|
practitionerId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update in the interventions list
|
||||||
|
const index = interventions.value.findIndex(
|
||||||
|
(i) => i.id === intervention.id
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
interventions.value[index] = intervention;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current intervention if it's the one being updated
|
||||||
|
if (
|
||||||
|
currentIntervention.value &&
|
||||||
|
currentIntervention.value.id === intervention.id
|
||||||
|
) {
|
||||||
|
setCurrentIntervention(intervention);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuccess(true);
|
||||||
|
return intervention;
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage =
|
||||||
|
err.response?.data?.message ||
|
||||||
|
err.message ||
|
||||||
|
"Échec de l'assignation du praticien";
|
||||||
|
setError(errorMessage);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the state
|
||||||
|
*/
|
||||||
|
const resetState = () => {
|
||||||
|
interventions.value = [];
|
||||||
|
currentIntervention.value = null;
|
||||||
|
loading.value = false;
|
||||||
|
error.value = null;
|
||||||
|
success.value = false;
|
||||||
|
pagination.value = {
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
per_page: 10,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
interventions,
|
||||||
|
currentIntervention,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
success,
|
||||||
|
pagination,
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
allInterventions,
|
||||||
|
isLoading,
|
||||||
|
hasError,
|
||||||
|
getError,
|
||||||
|
isSuccess,
|
||||||
|
getInterventionById,
|
||||||
|
getPagination,
|
||||||
|
getInterventionsByDeceased,
|
||||||
|
getInterventionsByClient,
|
||||||
|
getScheduledInterventions,
|
||||||
|
getCompletedInterventions,
|
||||||
|
getPendingInterventions,
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setLoading,
|
||||||
|
setError,
|
||||||
|
clearError,
|
||||||
|
setSuccess,
|
||||||
|
clearSuccess,
|
||||||
|
setInterventions,
|
||||||
|
setCurrentIntervention,
|
||||||
|
setPagination,
|
||||||
|
fetchInterventions,
|
||||||
|
fetchInterventionById,
|
||||||
|
createIntervention,
|
||||||
|
updateIntervention,
|
||||||
|
deleteIntervention,
|
||||||
|
fetchInterventionsByDeceased,
|
||||||
|
fetchInterventionsByClient,
|
||||||
|
searchInterventions,
|
||||||
|
updateInterventionStatus,
|
||||||
|
assignPractitioner,
|
||||||
|
resetState,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useInterventionStore;
|
||||||
59
thanasoft-front/src/views/pages/Defunts/AddDefunt.vue
Normal file
59
thanasoft-front/src/views/pages/Defunts/AddDefunt.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<add-defunt-presentation
|
||||||
|
:loading="deceasedStore.isLoading"
|
||||||
|
:validation-errors="validationErrors"
|
||||||
|
:success="showSuccess"
|
||||||
|
@create-defunt="handleCreateDefunt"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import AddDefuntPresentation from "@/components/Organism/Defunts/AddDefuntPresentation.vue";
|
||||||
|
import { useDeceasedStore } from "@/stores/deceasedStore";
|
||||||
|
import { useNotificationStore } from "@/stores/notification";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const deceasedStore = useDeceasedStore();
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
const validationErrors = ref({});
|
||||||
|
const showSuccess = ref(false);
|
||||||
|
|
||||||
|
const handleCreateDefunt = async (form) => {
|
||||||
|
try {
|
||||||
|
// Clear previous errors
|
||||||
|
validationErrors.value = {};
|
||||||
|
showSuccess.value = false;
|
||||||
|
|
||||||
|
// Call the store to create deceased
|
||||||
|
const deceased = await deceasedStore.createDeceased(form);
|
||||||
|
|
||||||
|
// Show success notification
|
||||||
|
notificationStore.created("Personne décédée");
|
||||||
|
showSuccess.value = true;
|
||||||
|
|
||||||
|
// Redirect after 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push({ name: "Liste Defunts" });
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating deceased:", error);
|
||||||
|
|
||||||
|
// Handle validation errors from Laravel
|
||||||
|
if (error.response && error.response.status === 422) {
|
||||||
|
validationErrors.value = error.response.data.errors || {};
|
||||||
|
notificationStore.error(
|
||||||
|
"Erreur de validation",
|
||||||
|
"Veuillez corriger les erreurs dans le formulaire"
|
||||||
|
);
|
||||||
|
} else if (error.response && error.response.data) {
|
||||||
|
// Handle other API errors
|
||||||
|
const errorMessage =
|
||||||
|
error.response.data.message || "Une erreur est survenue";
|
||||||
|
notificationStore.error("Erreur", errorMessage);
|
||||||
|
} else {
|
||||||
|
notificationStore.error("Erreur", "Une erreur inattendue s'est produite");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
61
thanasoft-front/src/views/pages/Defunts/DefuntDetails.vue
Normal file
61
thanasoft-front/src/views/pages/Defunts/DefuntDetails.vue
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="deceasedStore.currentDeceased" class="container-fluid">
|
||||||
|
<defunt-detail-presentation
|
||||||
|
:deceased="deceasedStore.currentDeceased"
|
||||||
|
:is-loading="deceasedStore.isLoading"
|
||||||
|
@update-deceased="updateDeceased"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!deceasedStore.isLoading" class="text-center p-5">
|
||||||
|
<h4 class="text-muted">Personne décédée introuvable</h4>
|
||||||
|
<router-link to="/defunts" class="btn btn-primary mt-3">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>Retour à la liste
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-center p-5">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Chargement...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useDeceasedStore } from "@/stores/deceasedStore";
|
||||||
|
import { useNotificationStore } from "@/stores/notification";
|
||||||
|
import DefuntDetailPresentation from "@/components/Organism/Defunts/DefuntDetailPresentation.vue";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const deceasedStore = useDeceasedStore();
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
|
||||||
|
// Ensure deceased_id is a number
|
||||||
|
const deceased_id = Number(route.params.id);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (deceased_id) {
|
||||||
|
const cc = await deceasedStore.fetchDeceasedById(deceased_id);
|
||||||
|
console.log(cc);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateDeceased = async (data) => {
|
||||||
|
if (!deceased_id) {
|
||||||
|
console.error("Missing deceased id");
|
||||||
|
notificationStore.error("Erreur", "ID du défunt manquant");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deceasedStore.updateDeceased({ id: deceased_id, ...data });
|
||||||
|
notificationStore.updated("Personne décédée");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating deceased:", error);
|
||||||
|
notificationStore.error(
|
||||||
|
"Erreur",
|
||||||
|
"Impossible de mettre à jour la personne décédée"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -1,6 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<defunt-presentation />
|
<defunt-presentation :defunts="deceasedStore.deceased" />
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import DefuntPresentation from "@/components/Organism/Defunts/DefuntPresentation.vue";
|
import DefuntPresentation from "@/components/Organism/Defunts/DefuntPresentation.vue";
|
||||||
|
import { useDeceasedStore } from "@/stores/deceasedStore";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
|
const deceasedStore = useDeceasedStore();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await deceasedStore.fetchDeceased();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<add-intervention-presentation
|
||||||
|
:loading="interventionStore.isLoading"
|
||||||
|
:validation-errors="validationErrors"
|
||||||
|
:success="showSuccess"
|
||||||
|
:deceased-list="deceasedStore.allDeceased"
|
||||||
|
:deceased-loading="deceasedStore.isLoading"
|
||||||
|
:client-list="clientStore.allClients"
|
||||||
|
:client-loading="clientStore.isLoading"
|
||||||
|
@create-intervention="handleCreateIntervention"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import AddInterventionPresentation from "@/components/Organism/Interventions/AddInterventionPresentation.vue";
|
||||||
|
import { useInterventionStore } from "@/stores/interventionStore";
|
||||||
|
import { useDeceasedStore } from "@/stores/deceasedStore";
|
||||||
|
import { useClientStore } from "@/stores/clientStore";
|
||||||
|
import { useNotificationStore } from "@/stores/notification";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const interventionStore = useInterventionStore();
|
||||||
|
const deceasedStore = useDeceasedStore();
|
||||||
|
const clientStore = useClientStore();
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
const validationErrors = ref({});
|
||||||
|
const showSuccess = ref(false);
|
||||||
|
|
||||||
|
const handleCreateIntervention = async (form) => {
|
||||||
|
try {
|
||||||
|
// Clear previous errors
|
||||||
|
validationErrors.value = {};
|
||||||
|
showSuccess.value = false;
|
||||||
|
|
||||||
|
// Call the store to create intervention
|
||||||
|
const intervention = await interventionStore.createIntervention(form);
|
||||||
|
|
||||||
|
// Show success notification
|
||||||
|
notificationStore.created("Intervention");
|
||||||
|
showSuccess.value = true;
|
||||||
|
|
||||||
|
// Redirect after 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push({ name: "Interventions" });
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating intervention:", error);
|
||||||
|
|
||||||
|
// Handle validation errors from Laravel
|
||||||
|
if (error.response && error.response.status === 422) {
|
||||||
|
validationErrors.value = error.response.data.errors || {};
|
||||||
|
notificationStore.error(
|
||||||
|
"Erreur de validation",
|
||||||
|
"Veuillez corriger les erreurs dans le formulaire"
|
||||||
|
);
|
||||||
|
} else if (error.response && error.response.data) {
|
||||||
|
// Handle other API errors
|
||||||
|
const errorMessage =
|
||||||
|
error.response.data.message || "Une erreur est survenue";
|
||||||
|
notificationStore.error("Erreur", errorMessage);
|
||||||
|
} else {
|
||||||
|
notificationStore.error("Erreur", "Une erreur inattendue s'est produite");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load deceased and client lists for selection
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
deceasedStore.fetchDeceased(),
|
||||||
|
clientStore.fetchClients(),
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading data:", error);
|
||||||
|
notificationStore.error(
|
||||||
|
"Erreur",
|
||||||
|
"Impossible de charger les données nécessaires"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1,6 +1,85 @@
|
|||||||
<template>
|
<template>
|
||||||
<intervention-presentation />
|
<intervention-presentation
|
||||||
|
:interventions="transformedInterventions"
|
||||||
|
:loading="interventionStore.isLoading"
|
||||||
|
:error="interventionStore.getError"
|
||||||
|
@retry="loadInterventions"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted, computed } from "vue";
|
||||||
import InterventionPresentation from "@/components/Organism/Interventions/InterventionPresentation.vue";
|
import InterventionPresentation from "@/components/Organism/Interventions/InterventionPresentation.vue";
|
||||||
|
import { useInterventionStore } from "@/stores/interventionStore";
|
||||||
|
|
||||||
|
const interventionStore = useInterventionStore();
|
||||||
|
|
||||||
|
// Load interventions on component mount
|
||||||
|
const loadInterventions = async () => {
|
||||||
|
try {
|
||||||
|
await interventionStore.fetchInterventions();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load interventions:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Transform store data to match component expectations
|
||||||
|
const transformedInterventions = computed(() => {
|
||||||
|
return interventionStore.interventions.map((intervention) => ({
|
||||||
|
title: intervention.title || intervention.type || "Intervention",
|
||||||
|
status: {
|
||||||
|
label: intervention.status || "En attente",
|
||||||
|
color: getStatusColor(intervention.status),
|
||||||
|
variant: "fill",
|
||||||
|
size: "md",
|
||||||
|
},
|
||||||
|
date: formatDate(intervention.scheduled_at || intervention.date),
|
||||||
|
defuntName:
|
||||||
|
intervention.deceased_name ||
|
||||||
|
intervention.defuntName ||
|
||||||
|
"Nom non disponible",
|
||||||
|
lieux: intervention.location || intervention.lieux || "Lieu non spécifié",
|
||||||
|
duree: intervention.duration || intervention.duree || "Durée non définie",
|
||||||
|
description: intervention.description || "Aucune description disponible",
|
||||||
|
action: {
|
||||||
|
label: "Voir détails",
|
||||||
|
color: "primary",
|
||||||
|
},
|
||||||
|
members: intervention.members || [],
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to get status color
|
||||||
|
const getStatusColor = (status) => {
|
||||||
|
const statusColors = {
|
||||||
|
pending: "warning",
|
||||||
|
confirmed: "success",
|
||||||
|
completed: "success",
|
||||||
|
cancelled: "danger",
|
||||||
|
planned: "info",
|
||||||
|
in_progress: "info",
|
||||||
|
finished: "dark",
|
||||||
|
};
|
||||||
|
return statusColors[status?.toLowerCase()] || "secondary";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to format date
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
if (!dateString) return "Date non définie";
|
||||||
|
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date
|
||||||
|
.toLocaleDateString("fr-FR", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
})
|
||||||
|
.replace(",", " -");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load data on component mount
|
||||||
|
onMounted(() => {
|
||||||
|
loadInterventions();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user