ajout table thanato dans intervention

This commit is contained in:
Nyavokevin 2025-11-26 17:53:17 +03:00
parent a51e05559a
commit 496b427e13
15 changed files with 968 additions and 274 deletions

View File

@ -4,11 +4,12 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreInterventionRequest;
use App\Http\Requests\StoreInterventionWithAllDataRequest;
use App\HttpRequests\StoreInterventionWithAllDataRequest;
use App\Http\Requests\UpdateInterventionRequest;
use App\Http\Resources\Intervention\InterventionResource;
use App\Http\Resources\Intervention\InterventionCollection;
use App\Repositories\InterventionRepositoryInterface;
use App\Repositories\InterventionPractitionerRepositoryInterface;
use App\Repositories\ClientRepositoryInterface;
use App\Repositories\ContactRepositoryInterface;
use App\Repositories\DeceasedRepositoryInterface;
@ -25,6 +26,11 @@ class InterventionController extends Controller
*/
protected $interventionRepository;
/**
* @var InterventionPractitionerRepositoryInterface
*/
protected $interventionPractitionerRepository;
/**
* @var ClientRepositoryInterface
*/
@ -44,17 +50,20 @@ class InterventionController extends Controller
* InterventionController constructor.
*
* @param InterventionRepositoryInterface $interventionRepository
* @param InterventionPractitionerRepositoryInterface $interventionPractitionerRepository
* @param ClientRepositoryInterface $clientRepository
* @param ContactRepositoryInterface $contactRepository
* @param DeceasedRepositoryInterface $deceasedRepository
*/
public function __construct(
InterventionRepositoryInterface $interventionRepository,
InterventionPractitionerRepositoryInterface $interventionPractitionerRepository,
ClientRepositoryInterface $clientRepository,
ContactRepositoryInterface $contactRepository,
DeceasedRepositoryInterface $deceasedRepository
) {
$this->interventionRepository = $interventionRepository;
$this->interventionPractitionerRepository = $interventionPractitionerRepository;
$this->clientRepository = $clientRepository;
$this->contactRepository = $contactRepository;
$this->deceasedRepository = $deceasedRepository;
@ -361,13 +370,13 @@ class InterventionController extends Controller
}
/**
* Assign a practitioner to an intervention
* Create assignment of practitioners to an intervention
*
* @param Request $request
* @param int $id
* @return JsonResponse
*/
public function assignPractitioner(Request $request, int $id): JsonResponse
public function createAssignment(Request $request, int $id): JsonResponse
{
try {
$validated = $request->validate([
@ -376,6 +385,7 @@ class InterventionController extends Controller
'assistant_practitioner_ids.*' => 'integer|exists:thanatopractitioners,id',
]);
$intervention = $this->interventionRepository->findById($id);
if (!$intervention) {
@ -384,41 +394,164 @@ class InterventionController extends Controller
], Response::HTTP_NOT_FOUND);
}
// Sync practitioners with their roles
$practitioners = [];
// Remove existing principal practitioner first
if (isset($validated['principal_practitioner_id'])) {
$practitioners[$validated['principal_practitioner_id']] = ['role' => 'principal'];
$principalId = $validated['principal_practitioner_id'];
$this->interventionPractitionerRepository->createAssignment($id, $principalId, 'principal');
}
if (isset($validated['assistant_practitioner_ids'])) {
// Handle assistant practitioners
if (isset($validated['assistant_practitioner_ids']) && is_array($validated['assistant_practitioner_ids'])) {
foreach ($validated['assistant_practitioner_ids'] as $assistantId) {
$practitioners[$assistantId] = ['role' => 'assistant'];
$this->interventionPractitionerRepository->createAssignment($id, $assistantId, 'assistant');
}
}
// Sync the practitioners (this will replace existing assignments)
$intervention->practitioners()->sync($practitioners);
// Reload the intervention with relationships
$intervention = $this->interventionRepository->findById($id);
// Load the intervention with practitioners to return updated data
$intervention->load('practitioners');
$practitioners = $intervention->practitioners;
return response()->json([
'data' => new InterventionResource($intervention),
'message' => 'Praticien(s) assigné(s) avec succès.'
'message' => 'Assignment(s) créé(s) avec succès.',
'practitioners_count' => $practitioners->count(),
'practitioners' => $practitioners->map(function($p) {
return [
'id' => $p->id,
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
'role' => $p->pivot->role ?? 'unknown'
];
})->toArray()
], Response::HTTP_OK);
} catch (\Exception $e) {
Log::error('Error assigning practitioner to intervention: ' . $e->getMessage(), [
'intervention_id' => $id,
return response()->json([
'message' => 'Une erreur est survenue lors de la création de l\'assignment.',
'error' => $e->getMessage()
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Unassign a practitioner from an intervention
*
* @param Request $request
* @param int $interventionId
* @param int $practitionerId
* @return JsonResponse
*/
public function unassignPractitioner(Request $request, int $interventionId, int $practitionerId): JsonResponse
{
try {
Log::info('Unassigning practitioner from intervention', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId
]);
// Validate that the intervention exists
$intervention = $this->interventionRepository->findById($interventionId);
if (!$intervention) {
return response()->json([
'message' => 'Intervention non trouvée.'
], Response::HTTP_NOT_FOUND);
}
// Check if the practitioner is actually assigned to this intervention
$isAssigned = $this->interventionPractitionerRepository->isPractitionerAssigned($interventionId, $practitionerId);
if (!$isAssigned) {
return response()->json([
'message' => 'Le praticien n\'est pas assigné à cette intervention.'
], Response::HTTP_NOT_FOUND);
}
// Remove the practitioner assignment
$deleted = $this->interventionPractitionerRepository->removeAssignment($interventionId, $practitionerId);
if ($deleted > 0) {
Log::info('Practitioner unassigned successfully', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'deleted_records' => $deleted
]);
// Reload intervention with remaining practitioners
$intervention->load('practitioners');
$remainingPractitioners = $intervention->practitioners;
return response()->json([
'data' => new InterventionResource($intervention),
'message' => 'Praticien désassigné avec succès.',
'remaining_practitioners_count' => $remainingPractitioners->count(),
'remaining_practitioners' => $remainingPractitioners->map(function($p) {
return [
'id' => $p->id,
'employee_name' => $p->employee->full_name ?? ($p->employee->first_name . ' ' . $p->employee->last_name),
'role' => $p->pivot->role ?? 'unknown'
];
})->toArray()
], Response::HTTP_OK);
} else {
Log::warning('No practitioner assignment found to delete', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId
]);
return response()->json([
'message' => 'Aucun assignment de praticien trouvé à supprimer.'
], Response::HTTP_NOT_FOUND);
}
} catch (\Exception $e) {
Log::error('Error unassigning practitioner from intervention: ' . $e->getMessage(), [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'request_data' => $request->all(),
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return response()->json([
'message' => 'Une erreur est survenue lors de l\'assignation du praticien.',
'message' => 'Une erreur est survenue lors de la désassignation du praticien.',
'error' => $e->getMessage()
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Debug endpoint to check practitioners in database
*/
public function debugPractitioners(int $id): JsonResponse
{
try {
$intervention = $this->interventionRepository->findById($id);
// Direct database query
$dbPractitioners = DB::table('intervention_practitioner')
->where('intervention_id', $id)
->get();
// Eager loaded practitioners
$eagerPractitioners = $intervention->practitioners()->get();
return response()->json([
'intervention_id' => $id,
'database_records' => $dbPractitioners,
'eager_loaded_count' => $eagerPractitioners->count(),
'eager_loaded_data' => $eagerPractitioners->map(function($p) {
return [
'id' => $p->id,
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
'role' => $p->pivot->role ?? 'unknown'
];
})->toArray()
]);
} catch (\Exception $e) {
Log::error('Error in debug practitioners: ' . $e->getMessage());
return response()->json(['error' => $e->getMessage()], 500);
}
}
}

View File

@ -3,6 +3,8 @@
namespace App\Http\Resources\Employee;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Employee\EmployeeResource;
use App\Http\Resources\Employee\PractitionerDocumentResource;
class ThanatopractitionerResource extends JsonResource
{
@ -17,6 +19,10 @@ class ThanatopractitionerResource extends JsonResource
return [
'id' => $this->id,
'employee_id' => $this->employee_id,
'employee_name' => $this->when(
$this->relationLoaded('employee'),
$this->employee->full_name ?? ($this->employee->first_name . ' ' . $this->employee->last_name)
),
'diploma_number' => $this->diploma_number,
'diploma_date' => $this->diploma_date?->format('Y-m-d'),
'authorization_number' => $this->authorization_number,

View File

@ -37,11 +37,44 @@ class InterventionResource extends JsonResource
'duration_min' => $this->duration_min,
'status' => $this->status,
'practitioners' => $this->whenLoaded('practitioners', function () {
return ThanatopractitionerResource::collection($this->practitioners);
return $this->practitioners->map(function ($practitioner) {
return [
'id' => $practitioner->id,
'employee_id' => $practitioner->employee_id,
'employee_name' => $practitioner->employee->full_name ?? ($practitioner->employee->first_name . ' ' . $practitioner->employee->last_name),
'diploma_number' => $practitioner->diploma_number,
'diploma_date' => $practitioner->diploma_date?->format('Y-m-d'),
'authorization_number' => $practitioner->authorization_number,
'authorization_issue_date' => $practitioner->authorization_issue_date?->format('Y-m-d'),
'authorization_expiry_date' => $practitioner->authorization_expiry_date?->format('Y-m-d'),
'notes' => $practitioner->notes,
'is_authorization_valid' => $practitioner->is_authorization_valid,
'created_at' => $practitioner->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $practitioner->updated_at?->format('Y-m-d H:i:s'),
'role' => $practitioner->pivot->role ?? null,
];
});
}),
'principal_practitioner' => $this->whenLoaded('practitioners', function () {
$principal = $this->practitioners->where('pivot.role', 'principal')->first();
return $principal ? new ThanatopractitionerResource($principal) : null;
if (!$principal) {
return null;
}
return [
'id' => $principal->id,
'employee_id' => $principal->employee_id,
'employee_name' => $principal->employee->full_name ?? ($principal->employee->first_name . ' ' . $principal->employee->last_name),
'diploma_number' => $principal->diploma_number,
'diploma_date' => $principal->diploma_date?->format('Y-m-d'),
'authorization_number' => $principal->authorization_number,
'authorization_issue_date' => $principal->authorization_issue_date?->format('Y-m-d'),
'authorization_expiry_date' => $principal->authorization_expiry_date?->format('Y-m-d'),
'notes' => $principal->notes,
'is_authorization_valid' => $principal->is_authorization_valid,
'created_at' => $principal->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $principal->updated_at?->format('Y-m-d H:i:s'),
'role' => $principal->pivot->role ?? null,
];
}),
'attachments_count' => $this->attachments_count,
'notes' => $this->notes,

View File

@ -0,0 +1,72 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class InterventionPractitioner extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'intervention_practitioner';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'intervention_id',
'practitioner_id',
'role',
'assigned_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'assigned_at' => 'datetime'
];
/**
* Get the intervention that owns the practitioner assignment.
*/
public function intervention(): BelongsTo
{
return $this->belongsTo(Intervention::class);
}
/**
* Get the practitioner assigned to the intervention.
*/
public function practitioner(): BelongsTo
{
return $this->belongsTo(Thanatopractitioner::class, 'practitioner_id');
}
/**
* Scope to get principal practitioners.
*/
public function scopePrincipal($query)
{
return $query->where('role', 'principal');
}
/**
* Scope to get assistant practitioners.
*/
public function scopeAssistant($query)
{
return $query->where('role', 'assistant');
}
}

View File

@ -60,6 +60,8 @@ class AppServiceProvider extends ServiceProvider
$this->app->bind(\App\Repositories\InterventionRepositoryInterface::class, \App\Repositories\InterventionRepository::class);
$this->app->bind(\App\Repositories\DeceasedRepositoryInterface::class, \App\Repositories\DeceasedRepository::class);
$this->app->bind(\App\Repositories\InterventionPractitionerRepositoryInterface::class, \App\Repositories\InterventionPractitionerRepository::class);
}
/**

View File

@ -0,0 +1,151 @@
<?php
namespace App\Repositories;
use App\Models\InterventionPractitioner;
use Illuminate\Support\Facades\Log;
class InterventionPractitionerRepository implements InterventionPractitionerRepositoryInterface
{
/**
* Create a new intervention-practitioner assignment.
*/
public function createAssignment(int $interventionId, int $practitionerId, string $role, ?string $assignedAt = null): InterventionPractitioner
{
try {
// Check if assignment already exists
if ($this->isPractitionerAssigned($interventionId, $practitionerId)) {
// If exists, update the role
$this->updatePractitionerRole($interventionId, $practitionerId, $role);
// Return the updated record
return InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->first();
}
$assignment = InterventionPractitioner::create([
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'role' => $role,
'assigned_at' => $assignedAt ?: now()
]);
Log::info('Intervention-practitioner assignment created', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'role' => $role,
'assignment_id' => $assignment->id
]);
return $assignment;
} catch (\Exception $e) {
Log::error('Error creating intervention-practitioner assignment', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'role' => $role,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Remove all practitioner assignments for an intervention.
*/
public function removeAllAssignments(int $interventionId): int
{
$deleted = InterventionPractitioner::where('intervention_id', $interventionId)->delete();
Log::info('Removed all practitioner assignments for intervention', [
'intervention_id' => $interventionId,
'deleted_count' => $deleted
]);
return $deleted;
}
/**
* Remove specific practitioner assignment.
*/
public function removeAssignment(int $interventionId, int $practitionerId): int
{
$deleted = InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->delete();
if ($deleted > 0) {
Log::info('Removed intervention-practitioner assignment', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId
]);
}
return $deleted;
}
/**
* Check if a practitioner is already assigned to an intervention.
*/
public function isPractitionerAssigned(int $interventionId, int $practitionerId): bool
{
return InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->exists();
}
/**
* Get all practitioner assignments for an intervention.
*/
public function getAssignmentsForIntervention(int $interventionId)
{
return InterventionPractitioner::with('practitioner')
->where('intervention_id', $interventionId)
->get();
}
/**
* Get principal practitioners for an intervention.
*/
public function getPrincipalPractitioners(int $interventionId)
{
return InterventionPractitioner::with('practitioner')
->where('intervention_id', $interventionId)
->principal()
->get();
}
/**
* Get assistant practitioners for an intervention.
*/
public function getAssistantPractitioners(int $interventionId)
{
return InterventionPractitioner::with('practitioner')
->where('intervention_id', $interventionId)
->assistant()
->get();
}
/**
* Update practitioner role for an intervention.
*/
public function updatePractitionerRole(int $interventionId, int $practitionerId, string $role): bool
{
$updated = InterventionPractitioner::where('intervention_id', $interventionId)
->where('practitioner_id', $practitionerId)
->update([
'role' => $role,
'assigned_at' => now()
]);
if ($updated > 0) {
Log::info('Updated practitioner role for intervention', [
'intervention_id' => $interventionId,
'practitioner_id' => $practitionerId,
'new_role' => $role
]);
}
return $updated > 0;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Repositories;
use App\Models\InterventionPractitioner;
interface InterventionPractitionerRepositoryInterface
{
/**
* Create a new intervention-practitioner assignment.
*
* @param int $interventionId
* @param int $practitionerId
* @param string $role
* @param string|null $assignedAt
* @return InterventionPractitioner
*/
public function createAssignment(int $interventionId, int $practitionerId, string $role, ?string $assignedAt = null): InterventionPractitioner;
/**
* Remove all practitioner assignments for an intervention.
*
* @param int $interventionId
* @return int
*/
public function removeAllAssignments(int $interventionId): int;
/**
* Remove specific practitioner assignment.
*
* @param int $interventionId
* @param int $practitionerId
* @return int
*/
public function removeAssignment(int $interventionId, int $practitionerId): int;
/**
* Check if a practitioner is already assigned to an intervention.
*
* @param int $interventionId
* @param int $practitionerId
* @return bool
*/
public function isPractitionerAssigned(int $interventionId, int $practitionerId): bool;
/**
* Get all practitioner assignments for an intervention.
*
* @param int $interventionId
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getAssignmentsForIntervention(int $interventionId);
/**
* Get principal practitioners for an intervention.
*
* @param int $interventionId
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getPrincipalPractitioners(int $interventionId);
/**
* Get assistant practitioners for an intervention.
*
* @param int $interventionId
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getAssistantPractitioners(int $interventionId);
/**
* Update practitioner role for an intervention.
*
* @param int $interventionId
* @param int $practitionerId
* @param string $role
* @return bool
*/
public function updatePractitionerRole(int $interventionId, int $practitionerId, string $role): bool;
}

View File

@ -55,7 +55,7 @@ class InterventionRepository implements InterventionRepositoryInterface
'client',
'deceased',
'location',
'assignedPractitioner'
'practitioners'
]);
return $query->paginate($perPage);
@ -149,8 +149,43 @@ class InterventionRepository implements InterventionRepositoryInterface
return Intervention::query()
->whereBetween('scheduled_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59'])
->with(['client', 'deceased', 'location', 'assignedPractitioner'])
->with(['client', 'deceased', 'location', 'practitioners'])
->orderBy('scheduled_at', 'asc')
->get();
}
/**
* Add practitioners to an intervention
*
* @param Intervention $intervention
* @param array $practitionerData
* @return array Array with 'principal' and 'assistant' results
*/
public function addPractitioners(Intervention $intervention, array $practitionerData): array
{
// This method is deprecated in favor of using InterventionPractitionerRepository directly
// Implementation kept for interface compatibility
$results = [
'principal' => null,
'assistant' => []
];
if (isset($practitionerData['principal_practitioner_id'])) {
$results['principal'] = [
'id' => $practitionerData['principal_practitioner_id'],
'status' => 'handled_by_practitioner_repository'
];
}
if (isset($practitionerData['assistant_practitioner_ids']) && is_array($practitionerData['assistant_practitioner_ids'])) {
foreach ($practitionerData['assistant_practitioner_ids'] as $assistantId) {
$results['assistant'][] = [
'id' => $assistantId,
'status' => 'handled_by_practitioner_repository'
];
}
}
return $results;
}
}

View File

@ -66,4 +66,13 @@ interface InterventionRepositoryInterface
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getByMonth(int $year, int $month): \Illuminate\Database\Eloquent\Collection;
/**
* Add practitioners to an intervention
*
* @param Intervention $intervention
* @param array $practitionerData
* @return array Array with 'principal' and 'assistant' results
*/
public function addPractitioners(Intervention $intervention, array $practitionerData): array;
}

View File

@ -131,7 +131,9 @@ Route::middleware('auth:sanctum')->group(function () {
Route::put('/{intervention}', [InterventionController::class, 'update']);
Route::delete('/{intervention}', [InterventionController::class, 'destroy']);
Route::patch('/{intervention}/status', [InterventionController::class, 'changeStatus']);
Route::patch('/{intervention}/assign', [InterventionController::class, 'assignPractitioner']);
Route::patch('/{intervention}/assign', [InterventionController::class, 'createAssignment']);
Route::patch('/{intervention}/{practitionerId}/unassignPractitioner', [InterventionController::class, 'unassignPractitioner']);
Route::get('/{intervention}/debug', [InterventionController::class, 'debugPractitioners']);
});
});

View File

@ -19,15 +19,13 @@
</template>
<template #intervention-detail-sidebar>
<div class="card">
<div class="card-body">
<InterventionDetailSidebar
:intervention="mappedIntervention"
:active-tab="activeTab"
:practitioners="practitioners"
@change-tab="activeTab = $event"
@assign-practitioner="handleAssignPractitioner"
/>
</div>
<InterventionDetailSidebar
:intervention="mappedIntervention"
:active-tab="activeTab"
:practitioners="practitioners"
@change-tab="activeTab = $event"
@assign-practitioner="handleAssignPractitioner"
/>
</div>
</template>
<template #intervention-detail-content>

View File

@ -27,183 +27,207 @@
<template v-else-if="intervention">
<!-- Overview Tab -->
<div v-if="activeTab === 'overview'" class="tab-pane fade show active">
<div class="mb-4">
<h6 class="mb-3">Informations Générales</h6>
<div class="row">
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-user text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Nom du défunt:</b> {{ intervention.defuntName }}
</p>
</div>
</div>
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<h6 class="mb-0">Détails de l'intervention</h6>
<button
type="button"
class="btn btn-sm btn-primary ms-auto"
@click="toggleEditMode"
:disabled="loading"
>
<i class="fas fa-edit me-1"></i
>{{ editMode ? "Sauvegarder" : "Modifier" }}
</button>
</div>
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-calendar text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Date:</b> {{ intervention.date }}
</p>
</div>
</div>
<div class="card-body">
<div class="row">
<!-- Basic Information Card -->
<div class="col-md-6 mb-3">
<InfoCard
title="Informations Générales"
icon="fas fa-user text-primary"
>
<ul class="list-group list-group-flush">
<li class="list-group-item border-0 ps-0 pt-0 text-sm">
<strong class="text-dark">Nom du défunt:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.defuntName || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.defuntName"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Date:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.date || "-"
}}</span>
<SoftInput
v-else
type="datetime-local"
v-model="localIntervention.date"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Lieu:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.lieux || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.lieux"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Durée:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.duree || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.duree"
class="mt-2"
/>
</li>
</ul>
</InfoCard>
</div>
</div>
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-map-marker text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Lieu:</b> {{ intervention.lieux }}
</p>
</div>
<!-- Contact Information Card -->
<div class="col-md-6 mb-3">
<InfoCard
title="Contact et Communication"
icon="fas fa-phone text-primary"
>
<ul class="list-group list-group-flush">
<li class="list-group-item border-0 ps-0 pt-0 text-sm">
<strong class="text-dark">Contact familial:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.contactFamilial || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.contactFamilial"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Coordonnées:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.coordonneesContact || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.coordonneesContact"
class="mt-2"
/>
</li>
<li class="list-group-item border-0 ps-0 text-sm">
<strong class="text-dark">Type de cérémonie:</strong>
<span v-if="!editMode" class="ms-2">{{
intervention.title || "-"
}}</span>
<SoftInput
v-else
v-model="localIntervention.title"
class="mt-2"
/>
</li>
</ul>
</InfoCard>
</div>
</div>
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-clock text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Durée:</b> {{ intervention.duree }}
</p>
</div>
<!-- Additional Information Card (Full Width) -->
<div class="col-12 mb-3">
<InfoCard
title="Informations Supplémentaires"
icon="fas fa-info-circle text-info"
>
<div class="row">
<div class="col-md-6">
<ul class="list-group list-group-flush">
<li class="list-group-item border-0 ps-0 pt-0 text-sm">
<strong class="text-dark"
>Nombre de personnes attendues:</strong
>
<span v-if="!editMode" class="ms-2">{{
intervention.nombrePersonnes || "-"
}}</span>
<SoftInput
v-else
type="number"
v-model="localIntervention.nombrePersonnes"
class="mt-2"
/>
</li>
</ul>
</div>
<div class="col-md-6">
<template v-if="!editMode">
<p class="text-sm">
<strong class="text-dark"
>Prestations supplémentaires:</strong
>
<span class="ms-2">{{
intervention.prestationsSupplementaires || "-"
}}</span>
</p>
</template>
<template v-else>
<p class="text-sm">
<strong class="text-dark"
>Prestations supplémentaires:</strong
>
</p>
<SoftInput
type="textarea"
rows="3"
v-model="localIntervention.prestationsSupplementaires"
class="mt-2"
/>
</template>
</div>
</div>
</InfoCard>
</div>
<!-- Description Card (Full Width) -->
<div class="col-12 mb-3">
<InfoCard
title="Description"
icon="fas fa-file-alt text-warning"
>
<template v-if="!editMode">
<p class="text-sm mb-0">
{{
intervention.description ||
"Aucune description disponible"
}}
</p>
</template>
<template v-else>
<SoftInput
type="textarea"
rows="3"
v-model="localIntervention.description"
class="mt-2"
/>
</template>
</InfoCard>
</div>
</div>
</div>
</div>
<div class="mb-4">
<h6 class="mb-3">Contact et Communication</h6>
<div class="row">
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-phone text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Contact:</b> {{ intervention.contactFamilial }}
</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="info-horizontal">
<div class="icon-sm text-center">
<i class="fas fa-envelope text-primary"></i>
</div>
<div class="description">
<p class="text-xs mb-0">
<b>Coordonnées:</b> {{ intervention.coordonneesContact }}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="mb-4">
<h6 class="mb-3">Description</h6>
<p class="text-sm">{{ intervention.description }}</p>
</div>
</div>
<!-- Details Tab -->
<div v-if="activeTab === 'details'" class="tab-pane fade show active">
<div class="mb-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<h6 class="mb-0">Détails Complets</h6>
<button
type="button"
class="btn btn-sm bg-gradient-secondary"
@click="toggleEditMode"
:disabled="loading"
>
{{ editMode ? "Sauvegarder" : "Modifier" }}
</button>
</div>
<div class="row">
<div class="col-md-6">
<SoftInput
label="Nom du défunt"
v-model="localIntervention.defuntName"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Date de l'intervention"
type="datetime-local"
v-model="localIntervention.date"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Lieu"
v-model="localIntervention.lieux"
:disabled="!editMode"
class="mb-3"
/>
</div>
<div class="col-md-6">
<SoftInput
label="Durée prévue"
v-model="localIntervention.duree"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Type de cérémonie"
v-model="localIntervention.title"
:disabled="!editMode"
class="mb-3"
/>
<SoftInput
label="Contact familial"
v-model="localIntervention.contactFamilial"
:disabled="!editMode"
class="mb-3"
/>
</div>
</div>
</div>
<div class="mb-4">
<h6 class="mb-3">Informations Suplementaires</h6>
<div class="row">
<div class="col-md-6">
<SoftInput
label="Nombre de personnes attendues"
type="number"
v-model="localIntervention.nombrePersonnes"
:disabled="!editMode"
class="mb-3"
/>
</div>
<div class="col-md-6">
<SoftInput
label="Prestations supplémentaires"
type="textarea"
rows="3"
v-model="localIntervention.prestationsSupplementaires"
:disabled="!editMode"
class="mb-3"
/>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="d-flex justify-content-end mt-4">
<!-- Action Buttons for Overview -->
<div class="d-flex justify-content-end mt-3" v-if="editMode">
<button
type="button"
class="btn btn-sm bg-gradient-secondary me-2"
@ -225,104 +249,157 @@
<!-- Team Tab -->
<div v-if="activeTab === 'team'" class="tab-pane fade show active">
<div class="mb-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<h6 class="mb-0">Équipe Assignée</h6>
<button
type="button"
class="btn btn-sm bg-gradient-info"
@click="$emit('assign-practitioner')"
:disabled="loading"
>
Gérer l'équipe
</button>
</div>
<div
v-if="
intervention.practitioners &&
intervention.practitioners.length > 0
"
class="list-group list-group-flush"
>
<div
v-for="(practitioner, index) in intervention.practitioners"
:key="index"
class="list-group-item d-flex justify-content-between align-items-center border-0 px-0"
>
<div>
<h6 class="text-sm mb-0">
{{
practitioner.employee?.full_name ||
(practitioner.employee?.first_name &&
practitioner.employee?.last_name
? practitioner.employee.first_name +
" " +
practitioner.employee.last_name
: "Praticien " + (index + 1))
}}
</h6>
<p class="text-xs text-muted mb-0">Praticien</p>
</div>
</div>
</div>
<div v-else class="text-center py-4">
<div class="avatar avatar-xl mb-3">
<div
class="avatar-title bg-gradient-secondary text-white h5 mb-0"
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<h6 class="mb-0">Équipe Assignée</h6>
<button
type="button"
class="btn btn-sm btn-outline-info ms-auto"
@click="$emit('assign-practitioner')"
:disabled="loading"
>
<i class="fas fa-user-plus"></i>
</div>
Ajouter un thanatopracteur
</button>
</div>
<h6 class="text-sm text-muted">Aucun praticien assigné</h6>
<p class="text-xs text-muted">
Cliquez sur "Gérer l'équipe" pour assigner des praticiens
</p>
</div>
<div class="card-body">
<InfoCard
title="Praticiens Assignés"
icon="fas fa-user-md text-success"
>
<template
v-if="
intervention.practitioners &&
intervention.practitioners.length > 0
"
>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle">
<thead></thead>
<tbody>
<tr
v-for="(
practitioner, index
) in intervention.practitioners"
:key="index"
>
<td class="ps-3">
<div class="d-flex align-items-center">
<div class="avatar avatar-sm me-3">
<div
class="avatar-placeholder bg-gradient-secondary text-white d-flex align-items-center justify-content-center rounded-circle"
>
{{ getInitials(practitioner.employee_name) }}
</div>
</div>
<div class="flex-grow-1">
<h6 class="mb-0 text-sm">
{{ practitioner.employee_name }}
</h6>
</div>
</div>
</td>
<td class="text-end pe-3">
<button
class="btn btn-sm btn-outline-danger"
@click="
$emit('unassign-practitioner', {
practitionerId: practitioner.id,
practitionerName: practitioner.employee_name,
})
"
title="Désassigner le praticien"
:disabled="loading"
>
<i class="fas fa-times"></i>
<span class="d-none d-sm-inline ms-1"
>Désassigner</span
>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<div v-else class="text-center py-4">
<div class="avatar avatar-xl mb-3">
<div
class="avatar-title bg-gradient-secondary text-white h5 mb-0"
>
<i class="fas fa-user-plus"></i>
</div>
</div>
<h6 class="text-sm text-muted">Aucun praticien assigné</h6>
<p class="text-xs text-muted mb-3">
Cliquez sur "Gérer l'équipe" pour assigner des praticiens
</p>
</div>
</InfoCard>
</div>
</div>
</div>
<!-- Documents Tab -->
<div v-if="activeTab === 'documents'" class="tab-pane fade show active">
<div class="text-center py-5">
<div class="avatar avatar-xl mb-3">
<div class="avatar-title bg-gradient-info text-white h5 mb-0">
<i class="fas fa-file-alt"></i>
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<h6 class="mb-0">Documents</h6>
</div>
</div>
<div class="card-body">
<div class="text-center py-5">
<div class="avatar avatar-xl mb-3">
<div class="avatar-title bg-gradient-info text-white h5 mb-0">
<i class="fas fa-file-alt"></i>
</div>
</div>
<h6 class="text-sm text-muted">Documents</h6>
<p class="text-xs text-muted">
Interface de gestion des documents à implémenter...
</p>
</div>
</div>
<h6 class="text-sm text-muted">Documents</h6>
<p class="text-xs text-muted">
Interface de gestion des documents à implémenter...
</p>
</div>
</div>
<!-- History Tab -->
<div v-if="activeTab === 'history'" class="tab-pane fade show active">
<div class="text-center py-5">
<div class="avatar avatar-xl mb-3">
<div class="avatar-title bg-gradient-warning text-white h5 mb-0">
<i class="fas fa-history"></i>
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<h6 class="mb-0">Historique</h6>
</div>
</div>
<div class="card-body">
<div class="text-center py-5">
<div class="avatar avatar-xl mb-3">
<div
class="avatar-title bg-gradient-warning text-white h5 mb-0"
>
<i class="fas fa-history"></i>
</div>
</div>
<h6 class="text-sm text-muted">Historique</h6>
<p class="text-xs text-muted">
Interface d'historique des modifications à implémenter...
</p>
</div>
</div>
<h6 class="text-sm text-muted">Historique</h6>
<p class="text-xs text-muted">
Interface d'historique des modifications à implémenter...
</p>
</div>
</div>
<!-- Navigation Actions -->
<hr class="horizontal dark" />
<div class="d-flex justify-content-between">
<div class="d-flex justify-content-between mt-3">
<button
type="button"
class="btn btn-sm bg-gradient-danger"
@click="$emit('cancel')"
:disabled="loading"
>
Annuler
<i class="fas fa-arrow-left me-2"></i>Retour
</button>
<div>
@ -338,11 +415,11 @@
<button
type="button"
class="btn btn-sm"
:class="`bg-gradient-${intervention.action.color}`"
:class="`bg-gradient-${intervention.action?.color || 'primary'}`"
@click="saveChanges"
:disabled="!hasChanges || loading"
>
{{ intervention.action.label }}
{{ intervention.action?.label || "Sauvegarder" }}
</button>
</div>
</div>
@ -358,6 +435,7 @@
<script setup>
import { ref, computed, watch } from "vue";
import SoftInput from "@/components/SoftInput.vue";
import InfoCard from "@/components/atoms/client/InfoCard.vue";
import { defineProps, defineEmits } from "vue";
const props = defineProps({
@ -384,6 +462,7 @@ const emit = defineEmits([
"update-intervention",
"cancel",
"assign-practitioner",
"unassign-practitioner",
]);
// État local pour l'édition
@ -431,4 +510,35 @@ watch(
},
{ deep: true, immediate: true }
);
const getInitials = (name) => {
if (!name) return "?";
return name
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase()
.substring(0, 2);
};
const unassignPractitioner = async (practitionerId) => {
if (!props.intervention?.id) return;
try {
// Use the intervention store to unassign the practitioner
const { useInterventionStore } = await import("@/stores/interventionStore");
const interventionStore = useInterventionStore();
await interventionStore.unassignPractitioner(
props.intervention.id,
practitionerId
);
// Refresh the intervention data by emitting an event to parent
emit("refresh-intervention");
} catch (error) {
console.error("Error unassigning practitioner:", error);
// You might want to show a toast notification here
}
};
</script>

View File

@ -288,6 +288,21 @@ export const InterventionService = {
return response;
},
/**
* Unassign a practitioner from intervention
*/
async unassignPractitioner(
interventionId: number,
practitionerId: number
): Promise<Intervention> {
const response = await request<Intervention>({
url: `/api/interventions/${interventionId}/${practitionerId}/unassignPractitioner`,
method: "patch",
});
return response;
},
/**
* Get interventions by month
*/

View File

@ -559,6 +559,53 @@ export const useInterventionStore = defineStore("intervention", () => {
}
};
/**
* Unassign a practitioner from intervention
*/
const unassignPractitioner = async (
interventionId: number,
practitionerId: number
) => {
setLoading(true);
setError(null);
setSuccess(false);
try {
const intervention = await InterventionService.unassignPractitioner(
interventionId,
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 la désassignation du praticien";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Get interventions by month
*/
@ -651,6 +698,7 @@ export const useInterventionStore = defineStore("intervention", () => {
assignPractitioner,
assignPractitioners,
updatePractitioners,
unassignPractitioner,
fetchInterventionsByMonth,
resetState,
};

View File

@ -47,6 +47,7 @@ const fetchIntervention = async () => {
);
intervention.value = result; // Store method returns the intervention directly
}
console.log(intervention.value);
} catch (error) {
console.error("Error loading intervention:", error);
notificationStore.error(