assigner thanato
This commit is contained in:
parent
4b7e075918
commit
a51e05559a
@ -359,4 +359,66 @@ class InterventionController extends Controller
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a practitioner to an intervention
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function assignPractitioner(Request $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'principal_practitioner_id' => 'nullable|integer|exists:thanatopractitioners,id',
|
||||
'assistant_practitioner_ids' => 'nullable|array',
|
||||
'assistant_practitioner_ids.*' => 'integer|exists:thanatopractitioners,id',
|
||||
]);
|
||||
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
if (!$intervention) {
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Sync practitioners with their roles
|
||||
$practitioners = [];
|
||||
|
||||
if (isset($validated['principal_practitioner_id'])) {
|
||||
$practitioners[$validated['principal_practitioner_id']] = ['role' => 'principal'];
|
||||
}
|
||||
|
||||
if (isset($validated['assistant_practitioner_ids'])) {
|
||||
foreach ($validated['assistant_practitioner_ids'] as $assistantId) {
|
||||
$practitioners[$assistantId] = ['role' => 'assistant'];
|
||||
}
|
||||
}
|
||||
|
||||
// Sync the practitioners (this will replace existing assignments)
|
||||
$intervention->practitioners()->sync($practitioners);
|
||||
|
||||
// Reload the intervention with relationships
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
return response()->json([
|
||||
'data' => new InterventionResource($intervention),
|
||||
'message' => 'Praticien(s) assigné(s) avec succès.'
|
||||
], Response::HTTP_OK);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error assigning practitioner to intervention: ' . $e->getMessage(), [
|
||||
'intervention_id' => $id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'assignation du praticien.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,6 +258,41 @@ class ThanatopractitionerController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search thanatopractitioners by employee name.
|
||||
*/
|
||||
public function searchByEmployeeName(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$query = $request->get('query', '');
|
||||
|
||||
if (strlen($query) < 2) {
|
||||
return response()->json([
|
||||
'data' => [],
|
||||
'message' => 'Veuillez entrer au moins 2 caractères pour la recherche.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$thanatopractitioners = $this->thanatopractitionerRepository->searchByEmployeeName($query);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ThanatopractitionerCollection($thanatopractitioners),
|
||||
'message' => 'Recherche effectuée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching thanatopractitioners by employee name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'query' => $request->get('query'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified thanatopractitioner.
|
||||
*/
|
||||
|
||||
@ -45,7 +45,11 @@ class StoreInterventionRequest extends FormRequest
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'practitioners' => ['nullable', 'array'],
|
||||
'practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
@ -68,7 +72,11 @@ class StoreInterventionRequest extends FormRequest
|
||||
'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.',
|
||||
'practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
}
|
||||
|
||||
@ -83,7 +83,11 @@ class StoreInterventionWithAllDataRequest extends FormRequest
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'intervention.assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'intervention.practitioners' => ['nullable', 'array'],
|
||||
'intervention.practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'intervention.principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'intervention.assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'intervention.assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'intervention.order_giver' => ['nullable', 'string', 'max:255'],
|
||||
'intervention.notes' => ['nullable', 'string'],
|
||||
'intervention.created_by' => ['nullable', 'exists:users,id']
|
||||
@ -131,7 +135,11 @@ class StoreInterventionWithAllDataRequest extends FormRequest
|
||||
'intervention.duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||
'intervention.duration_min.min' => 'La durée ne peut pas être négative.',
|
||||
'intervention.status.in' => 'Le statut de l\'intervention est invalide.',
|
||||
'intervention.assigned_practitioner_id.exists' => 'Le praticien sélectionné est invalide.',
|
||||
'intervention.practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'intervention.practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'intervention.principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'intervention.assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'intervention.assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'intervention.order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||
'intervention.created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
|
||||
@ -45,7 +45,11 @@ class UpdateInterventionRequest extends FormRequest
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'assigned_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'practitioners' => ['nullable', 'array'],
|
||||
'practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
@ -68,7 +72,11 @@ class UpdateInterventionRequest extends FormRequest
|
||||
'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.',
|
||||
'practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
}
|
||||
|
||||
@ -36,8 +36,12 @@ class InterventionResource extends JsonResource
|
||||
'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);
|
||||
'practitioners' => $this->whenLoaded('practitioners', function () {
|
||||
return ThanatopractitionerResource::collection($this->practitioners);
|
||||
}),
|
||||
'principal_practitioner' => $this->whenLoaded('practitioners', function () {
|
||||
$principal = $this->practitioners->where('pivot.role', 'principal')->first();
|
||||
return $principal ? new ThanatopractitionerResource($principal) : null;
|
||||
}),
|
||||
'attachments_count' => $this->attachments_count,
|
||||
'notes' => $this->notes,
|
||||
|
||||
@ -5,6 +5,7 @@ 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\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Intervention extends Model
|
||||
@ -25,7 +26,6 @@ class Intervention extends Model
|
||||
'scheduled_at',
|
||||
'duration_min',
|
||||
'status',
|
||||
'assigned_practitioner_id',
|
||||
'attachments_count',
|
||||
'notes',
|
||||
'created_by'
|
||||
@ -66,11 +66,43 @@ class Intervention extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the practitioner assigned to the intervention.
|
||||
* Get the practitioners assigned to the intervention.
|
||||
*/
|
||||
public function assignedPractitioner(): BelongsTo
|
||||
public function practitioners(): BelongsToMany
|
||||
{
|
||||
return $this->belongsTo(Thanatopractitioner::class, 'assigned_practitioner_id');
|
||||
return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for practitioners relationship (for backward compatibility).
|
||||
*/
|
||||
public function assignedPractitioner(): BelongsToMany
|
||||
{
|
||||
return $this->practitioners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the principal practitioner assigned to the intervention.
|
||||
*/
|
||||
public function principalPractitioner(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id')
|
||||
->wherePivot('role', 'principal')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assistant practitioners assigned to the intervention.
|
||||
*/
|
||||
public function assistantPractitioners(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Thanatopractitioner::class, 'intervention_practitioner', 'intervention_id', 'practitioner_id')
|
||||
->wherePivot('role', 'assistant')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -5,6 +5,7 @@ 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\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Thanatopractitioner extends Model
|
||||
@ -55,6 +56,38 @@ class Thanatopractitioner extends Model
|
||||
return $this->hasMany(PractitionerDocument::class, 'practitioner_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interventions assigned to the thanatopractitioner.
|
||||
*/
|
||||
public function interventions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Intervention::class, 'intervention_practitioner')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interventions where this practitioner is the principal.
|
||||
*/
|
||||
public function principalInterventions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Intervention::class, 'intervention_practitioner')
|
||||
->wherePivot('role', 'principal')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interventions where this practitioner is an assistant.
|
||||
*/
|
||||
public function assistantInterventions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Intervention::class, 'intervention_practitioner')
|
||||
->wherePivot('role', 'assistant')
|
||||
->withPivot('role', 'assigned_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include practitioners with valid authorization.
|
||||
*/
|
||||
|
||||
@ -73,7 +73,7 @@ class InterventionRepository implements InterventionRepositoryInterface
|
||||
'client',
|
||||
'deceased',
|
||||
'location',
|
||||
'assignedPractitioner',
|
||||
'practitioners',
|
||||
'attachments',
|
||||
'notifications'
|
||||
])->findOrFail($id);
|
||||
|
||||
@ -132,4 +132,20 @@ class ThanatopractitionerRepository extends BaseRepository implements Thanatopra
|
||||
'with_documents' => $this->model->newQuery()->has('documents')->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search thanatopractitioners by employee name.
|
||||
*/
|
||||
public function searchByEmployeeName(string $query): Collection
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->with(['employee'])
|
||||
->whereHas('employee', function ($q) use ($query) {
|
||||
$q->where('first_name', 'LIKE', "%{$query}%")
|
||||
->orWhere('last_name', 'LIKE', "%{$query}%")
|
||||
->orWhereRaw("CONCAT(first_name, ' ', last_name) LIKE ?", ["%{$query}%"]);
|
||||
})
|
||||
->limit(10)
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,5 +70,14 @@ interface ThanatopractitionerRepositoryInterface
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Search thanatopractitioners by employee name.
|
||||
*
|
||||
* @param string $query
|
||||
* @return Collection<int, Thanatopractitioner>
|
||||
*/
|
||||
public function searchByEmployeeName(string $query): Collection;
|
||||
|
||||
public function getStatistics(): array;
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ return [
|
||||
|
||||
// IMPORTANT: Do NOT use '*' when sending credentials. List explicit origins.
|
||||
// Set FRONTEND_URL in .env to override the default if needed.
|
||||
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:8080', 'http://localhost:8081')],
|
||||
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:8081')],
|
||||
|
||||
// Alternatively, use patterns (kept empty for clarity)
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
<?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_practitioner', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('intervention_id')->constrained('interventions')->onDelete('cascade');
|
||||
$table->foreignId('practitioner_id')->constrained('thanatopractitioners')->onDelete('cascade');
|
||||
$table->enum('role', ['principal', 'assistant'])->default('principal');
|
||||
$table->timestamp('assigned_at')->useCurrent();
|
||||
$table->timestamps();
|
||||
|
||||
// Unique constraint to prevent duplicate assignments
|
||||
$table->unique(['intervention_id', 'practitioner_id']);
|
||||
|
||||
// Indexes for better query performance
|
||||
$table->index('practitioner_id', 'idx_intervention_practitioner_practitioner');
|
||||
$table->index('intervention_id', 'idx_intervention_practitioner_intervention');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('intervention_practitioner');
|
||||
}
|
||||
};
|
||||
@ -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
|
||||
{
|
||||
// First, let's migrate existing data from assigned_practitioner_id to the new pivot table
|
||||
Schema::table('interventions', function (Blueprint $table) {
|
||||
// Add temporary columns to handle data migration
|
||||
$table->dropForeign(['assigned_practitioner_id']);
|
||||
$table->dropColumn('assigned_practitioner_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// This migration is not easily reversible since we'd lose the many-to-many relationship
|
||||
// In a real scenario, you'd want to handle this differently
|
||||
Schema::table('interventions', function (Blueprint $table) {
|
||||
$table->foreignId('assigned_practitioner_id')->nullable()->constrained('thanatopractitioners')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -87,6 +87,7 @@ Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::apiResource('employees', EmployeeController::class);
|
||||
|
||||
// Thanatopractitioner management
|
||||
Route::get('/thanatopractitioners/search', [ThanatopractitionerController::class, 'searchByEmployeeName']);
|
||||
Route::apiResource('thanatopractitioners', ThanatopractitionerController::class);
|
||||
Route::get('employees/{employeeId}/thanatopractitioners', [ThanatopractitionerController::class, 'getByEmployee']);
|
||||
Route::get('/thanatopractitioners/{id}/documents', [PractitionerDocumentController::class, 'getByThanatopractitioner']);
|
||||
@ -130,6 +131,7 @@ 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']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -41,6 +41,7 @@
|
||||
@change-tab="activeTab = $event"
|
||||
@update-intervention="handleUpdateIntervention"
|
||||
@cancel="handleCancel"
|
||||
@assign-practitioner="handleAssignPractitioner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -119,16 +120,19 @@ const mappedIntervention = computed(() => {
|
||||
"Non disponible"
|
||||
: "Non disponible",
|
||||
prestationsSupplementaires: "À définir",
|
||||
members: props.intervention.practitioner
|
||||
? [
|
||||
{
|
||||
name: `${props.intervention.practitioner.first_name || ""} ${
|
||||
props.intervention.practitioner.last_name || ""
|
||||
}`.trim(),
|
||||
members:
|
||||
props.intervention.practitioners &&
|
||||
props.intervention.practitioners.length > 0
|
||||
? props.intervention.practitioners.map((p) => ({
|
||||
name: p.employee
|
||||
? `${p.employee.first_name || ""} ${
|
||||
p.employee.last_name || ""
|
||||
}`.trim()
|
||||
: `${p.first_name || ""} ${p.last_name || ""}`.trim(),
|
||||
image: "/images/avatar-default.png",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
role: p.pivot?.role || "assistant",
|
||||
}))
|
||||
: [],
|
||||
|
||||
// Map status from API string to expected object format
|
||||
status: props.intervention.status
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<!-- Modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
:class="{ show: show, 'd-block': show }"
|
||||
:style="{ display: show ? 'block' : 'none' }"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="addPractitionerModalLabel"
|
||||
:aria-hidden="!show"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addPractitionerModalLabel">
|
||||
<i class="fas fa-user-plus me-2"></i>
|
||||
Ajouter un praticien
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
@click="handleClose"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<PractitionerSearchInput
|
||||
:loading="loading"
|
||||
:results="searchResults"
|
||||
@search="handleSearch"
|
||||
@select="handleSelect"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm bg-gradient-secondary"
|
||||
@click="handleClose"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Backdrop -->
|
||||
<div
|
||||
v-if="show"
|
||||
class="modal-backdrop fade"
|
||||
:class="{ show: show }"
|
||||
@click="handleClose"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import PractitionerSearchInput from '@/components/molecules/thanatopractitioner/PractitionerSearchInput.vue';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
searchResults: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'search', 'select']);
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const handleSearch = (query) => {
|
||||
emit('search', query);
|
||||
};
|
||||
|
||||
const handleSelect = (practitioner) => {
|
||||
emit('select', practitioner);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1040;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
@ -239,26 +239,30 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="intervention.members && intervention.members.length > 0"
|
||||
class="row"
|
||||
v-if="
|
||||
intervention.practitioners &&
|
||||
intervention.practitioners.length > 0
|
||||
"
|
||||
class="list-group list-group-flush"
|
||||
>
|
||||
<div
|
||||
v-for="(member, index) in intervention.members"
|
||||
v-for="(practitioner, index) in intervention.practitioners"
|
||||
:key="index"
|
||||
class="col-md-4 mb-3"
|
||||
class="list-group-item d-flex justify-content-between align-items-center border-0 px-0"
|
||||
>
|
||||
<div class="card border-0">
|
||||
<div class="card-body text-center">
|
||||
<div class="avatar avatar-xl mb-3">
|
||||
<img
|
||||
alt="Image placeholder"
|
||||
:src="member.image || '/images/avatar-default.png'"
|
||||
class="rounded-circle"
|
||||
/>
|
||||
</div>
|
||||
<h6 class="text-sm">{{ member.name }}</h6>
|
||||
<p class="text-xs text-muted">Praticien</p>
|
||||
</div>
|
||||
<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>
|
||||
@ -375,7 +379,12 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["change-tab", "update-intervention", "cancel"]);
|
||||
const emit = defineEmits([
|
||||
"change-tab",
|
||||
"update-intervention",
|
||||
"cancel",
|
||||
"assign-practitioner",
|
||||
]);
|
||||
|
||||
// État local pour l'édition
|
||||
const editMode = ref(false);
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Assign Practitioner Button -->
|
||||
<div v-if="!practitioners.length" class="mx-3 mb-3">
|
||||
<div class="mx-3 mb-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-primary w-100"
|
||||
|
||||
@ -235,7 +235,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Gérer l'équipe</h5>
|
||||
<h5 class="modal-title">Ajouter équipe</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
|
||||
@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div
|
||||
class="modal fade"
|
||||
:class="{ show: isOpen }"
|
||||
:style="{ display: isOpen ? 'block' : 'none' }"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
@click.self="close"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Assigner un thanatopracteur</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
@click="close"
|
||||
aria-label="Fermer"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Search Input -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Rechercher par nom</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Tapez le nom du thanatopracteur..."
|
||||
@input="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="text-center py-3">
|
||||
<div
|
||||
class="spinner-border spinner-border-sm text-primary"
|
||||
role="status"
|
||||
>
|
||||
<span class="visually-hidden">Chargement...</span>
|
||||
</div>
|
||||
<span class="ms-2">Recherche en cours...</span>
|
||||
</div>
|
||||
|
||||
<!-- Results List -->
|
||||
<div v-else-if="searchResults.length > 0" class="list-group">
|
||||
<button
|
||||
v-for="practitioner in searchResults"
|
||||
:key="practitioner.id"
|
||||
type="button"
|
||||
class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
:class="{ active: selectedPractitioner?.id === practitioner.id }"
|
||||
@click="selectPractitioner(practitioner)"
|
||||
>
|
||||
<div class="avatar avatar-sm me-3">
|
||||
<img
|
||||
:src="practitioner.avatar || '/images/avatar-default.png'"
|
||||
alt="Avatar"
|
||||
class="rounded-circle"
|
||||
style="width: 40px; height: 40px; object-fit: cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-0">
|
||||
{{
|
||||
practitioner.employee.full_name ||
|
||||
`${practitioner.employee.first_name} ${practitioner.employee.last_name}`
|
||||
}}
|
||||
</h6>
|
||||
<small class="text-muted">{{
|
||||
practitioner.employee.email || "Email non disponible"
|
||||
}}</small>
|
||||
</div>
|
||||
<i
|
||||
v-if="selectedPractitioner?.id === practitioner.id"
|
||||
class="fas fa-check text-success"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- No Results -->
|
||||
<div
|
||||
v-else-if="searchQuery.length >= 2 && !loading"
|
||||
class="text-center text-muted py-3"
|
||||
>
|
||||
<i class="fas fa-user-slash fa-2x mb-2"></i>
|
||||
<p class="mb-0">Aucun thanatopracteur trouvé</p>
|
||||
</div>
|
||||
|
||||
<!-- Initial State -->
|
||||
<div v-else class="text-center text-muted py-3">
|
||||
<i class="fas fa-search fa-2x mb-2"></i>
|
||||
<p class="mb-0">Tapez au moins 2 caractères pour rechercher</p>
|
||||
</div>
|
||||
|
||||
<!-- Role Selection -->
|
||||
<div v-if="selectedPractitioner" class="mt-3">
|
||||
<label class="form-label">Rôle dans l'intervention</label>
|
||||
<select v-model="selectedRole" class="form-select">
|
||||
<option value="principal">Principal</option>
|
||||
<option value="assistant">Assistant</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="close">
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
:disabled="!selectedPractitioner"
|
||||
@click="confirmAssignment"
|
||||
>
|
||||
<i class="fas fa-user-plus me-2"></i>Assigner
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Backdrop -->
|
||||
<div v-if="isOpen" class="modal-backdrop fade show"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { useThanatopractitionerStore } from "@/stores/thanatopractitionerStore";
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["close", "assign"]);
|
||||
|
||||
const thanatopractitionerStore = useThanatopractitionerStore();
|
||||
|
||||
const searchQuery = ref("");
|
||||
const searchResults = ref([]);
|
||||
const selectedPractitioner = ref(null);
|
||||
const selectedRole = ref("principal");
|
||||
const loading = ref(false);
|
||||
|
||||
let searchTimeout = null;
|
||||
|
||||
const handleSearch = () => {
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
if (searchQuery.value.length < 2) {
|
||||
searchResults.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
searchTimeout = setTimeout(async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await thanatopractitionerStore.searchThanatopractitioners(
|
||||
searchQuery.value
|
||||
);
|
||||
// The service returns the data directly, not wrapped in response.data
|
||||
searchResults.value = Array.isArray(response) ? response : [];
|
||||
} catch (error) {
|
||||
console.error("Error searching practitioners:", error);
|
||||
searchResults.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const selectPractitioner = (practitioner) => {
|
||||
selectedPractitioner.value = practitioner;
|
||||
};
|
||||
|
||||
const confirmAssignment = () => {
|
||||
if (selectedPractitioner.value) {
|
||||
emit("assign", {
|
||||
practitionerId: selectedPractitioner.value.id,
|
||||
role: selectedRole.value,
|
||||
});
|
||||
resetForm();
|
||||
}
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
resetForm();
|
||||
emit("close");
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
searchQuery.value = "";
|
||||
searchResults.value = [];
|
||||
selectedPractitioner.value = null;
|
||||
selectedRole.value = "principal";
|
||||
};
|
||||
|
||||
// Reset form when modal closes
|
||||
watch(
|
||||
() => props.isOpen,
|
||||
(newValue) => {
|
||||
if (!newValue) {
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal.show {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.list-group-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.list-group-item.active {
|
||||
background-color: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.list-group-item.active:hover {
|
||||
background-color: #bbdefb;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="practitioner-search-input">
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Rechercher un praticien..."
|
||||
v-model="searchQuery"
|
||||
@input="handleInput"
|
||||
@keyup.enter="handleSearch"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
type="button"
|
||||
@click="handleSearch"
|
||||
:disabled="loading || !searchQuery.trim()"
|
||||
>
|
||||
<i v-if="!loading" class="fas fa-search"></i>
|
||||
<span
|
||||
v-else
|
||||
class="spinner-border spinner-border-sm"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search Results Dropdown -->
|
||||
<div
|
||||
v-if="results && results.length > 0"
|
||||
class="search-results-dropdown card"
|
||||
>
|
||||
<div class="list-group list-group-flush">
|
||||
<button
|
||||
v-for="practitioner in results"
|
||||
:key="practitioner.id"
|
||||
type="button"
|
||||
class="list-group-item list-group-item-action"
|
||||
@click="handleSelect(practitioner)"
|
||||
>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="avatar avatar-sm me-3">
|
||||
<img
|
||||
:src="practitioner.image || '/images/avatar-default.png'"
|
||||
:alt="practitioner.full_name"
|
||||
class="rounded-circle"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-0 text-sm">{{ practitioner.full_name }}</h6>
|
||||
<p class="text-xs text-muted mb-0">
|
||||
{{ practitioner.job_title || 'Praticien' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No Results Message -->
|
||||
<div
|
||||
v-else-if="results && results.length === 0 && searchQuery.trim()"
|
||||
class="alert alert-info text-sm mb-0"
|
||||
>
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Aucun praticien trouvé pour "{{ searchQuery }}"
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, defineEmits } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
results: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['search', 'select']);
|
||||
|
||||
const searchQuery = ref('');
|
||||
|
||||
const handleInput = () => {
|
||||
// Optional: Implement debounce here if needed
|
||||
// For now, we'll just search on button click or Enter
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
if (searchQuery.value.trim()) {
|
||||
emit('search', searchQuery.value.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (practitioner) => {
|
||||
emit('select', practitioner);
|
||||
searchQuery.value = ''; // Clear search after selection
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.practitioner-search-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-results-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.list-group-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
</style>
|
||||
@ -10,14 +10,14 @@ export interface Intervention {
|
||||
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;
|
||||
practitioners?: any[];
|
||||
principal_practitioner?: any;
|
||||
location?: any;
|
||||
// Timestamps
|
||||
created_at?: string;
|
||||
@ -47,7 +47,9 @@ export interface CreateInterventionPayload {
|
||||
scheduled_at?: string;
|
||||
duration_min?: number;
|
||||
status?: string;
|
||||
assigned_practitioner_id?: number;
|
||||
practitioners?: number[];
|
||||
principal_practitioner_id?: number;
|
||||
assistant_practitioner_ids?: number[];
|
||||
notes?: string;
|
||||
created_by?: number;
|
||||
}
|
||||
@ -233,16 +235,54 @@ export const InterventionService = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Assign practitioner to intervention
|
||||
* Assign practitioner(s) to intervention
|
||||
*/
|
||||
async assignPractitioner(
|
||||
id: number,
|
||||
practitionerId: number
|
||||
practitionerData: {
|
||||
practitioners?: number[];
|
||||
principal_practitioner_id?: number;
|
||||
assistant_practitioner_ids?: number[];
|
||||
}
|
||||
): Promise<Intervention> {
|
||||
const response = await request<Intervention>({
|
||||
url: `/api/interventions/${id}/assign`,
|
||||
method: "patch",
|
||||
data: { assigned_practitioner_id: practitionerId },
|
||||
data: practitionerData,
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
/**
|
||||
* Assign multiple practitioners to intervention
|
||||
*/
|
||||
async assignPractitioners(
|
||||
id: number,
|
||||
practitionerIds: number[],
|
||||
principalPractitionerId?: number
|
||||
): Promise<Intervention> {
|
||||
return this.assignPractitioner(id, {
|
||||
practitioners: practitionerIds,
|
||||
principal_practitioner_id: principalPractitionerId,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update practitioners for intervention (replace all existing)
|
||||
*/
|
||||
async updatePractitioners(
|
||||
id: number,
|
||||
practitionerData: {
|
||||
practitioners?: number[];
|
||||
principal_practitioner_id?: number;
|
||||
assistant_practitioner_ids?: number[];
|
||||
}
|
||||
): Promise<Intervention> {
|
||||
const response = await request<Intervention>({
|
||||
url: `/api/interventions/${id}/practitioners`,
|
||||
method: "patch",
|
||||
data: practitionerData,
|
||||
});
|
||||
|
||||
return response;
|
||||
|
||||
@ -263,10 +263,10 @@ export const ThanatopractitionerService = {
|
||||
data: Thanatopractitioner[];
|
||||
};
|
||||
}>({
|
||||
url: "/api/thanatopractitioners",
|
||||
url: "/api/thanatopractitioners/search",
|
||||
method: "get",
|
||||
params: {
|
||||
search: query,
|
||||
query: query,
|
||||
},
|
||||
});
|
||||
return response.data.data;
|
||||
|
||||
@ -444,9 +444,16 @@ export const useInterventionStore = defineStore("intervention", () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign practitioner to intervention
|
||||
* Assign practitioner(s) to intervention
|
||||
*/
|
||||
const assignPractitioner = async (id: number, practitionerId: number) => {
|
||||
const assignPractitioner = async (
|
||||
id: number,
|
||||
practitionerData: {
|
||||
practitioners?: number[];
|
||||
principal_practitioner_id?: number;
|
||||
assistant_practitioner_ids?: number[];
|
||||
}
|
||||
) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
@ -454,7 +461,7 @@ export const useInterventionStore = defineStore("intervention", () => {
|
||||
try {
|
||||
const intervention = await InterventionService.assignPractitioner(
|
||||
id,
|
||||
practitionerId
|
||||
practitionerData
|
||||
);
|
||||
|
||||
// Update in the interventions list
|
||||
@ -479,7 +486,72 @@ export const useInterventionStore = defineStore("intervention", () => {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Échec de l'assignation du praticien";
|
||||
"Échec de l'assignation des praticiens";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign multiple practitioners to intervention
|
||||
*/
|
||||
const assignPractitioners = async (
|
||||
id: number,
|
||||
practitionerIds: number[],
|
||||
principalPractitionerId?: number
|
||||
) => {
|
||||
return assignPractitioner(id, {
|
||||
practitioners: practitionerIds,
|
||||
principal_practitioner_id: principalPractitionerId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update practitioners for intervention (replace all existing)
|
||||
*/
|
||||
const updatePractitioners = async (
|
||||
id: number,
|
||||
practitionerData: {
|
||||
practitioners?: number[];
|
||||
principal_practitioner_id?: number;
|
||||
assistant_practitioner_ids?: number[];
|
||||
}
|
||||
) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
|
||||
try {
|
||||
const intervention = await InterventionService.updatePractitioners(
|
||||
id,
|
||||
practitionerData
|
||||
);
|
||||
|
||||
// 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 des praticiens";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
@ -577,6 +649,8 @@ export const useInterventionStore = defineStore("intervention", () => {
|
||||
searchInterventions,
|
||||
updateInterventionStatus,
|
||||
assignPractitioner,
|
||||
assignPractitioners,
|
||||
updatePractitioners,
|
||||
fetchInterventionsByMonth,
|
||||
resetState,
|
||||
};
|
||||
|
||||
@ -8,7 +8,14 @@
|
||||
:practitioners="practitioners"
|
||||
@update-intervention="handleUpdate"
|
||||
@cancel="handleCancel"
|
||||
@assign-practitioner="handleAssignPractitioner"
|
||||
@assign-practitioner="openAssignModal"
|
||||
/>
|
||||
|
||||
<!-- Assign Practitioner Modal -->
|
||||
<AssignPractitionerModal
|
||||
:is-open="isModalOpen"
|
||||
@close="closeAssignModal"
|
||||
@assign="handleAssignPractitioner"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@ -16,6 +23,7 @@
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import InterventionDetailPresentation from "@/components/Organism/Interventions/InterventionDetailPresentation.vue";
|
||||
import AssignPractitionerModal from "@/components/molecules/intervention/AssignPractitionerModal.vue";
|
||||
import { useInterventionStore } from "@/stores/interventionStore";
|
||||
import { useNotificationStore } from "@/stores/notification";
|
||||
|
||||
@ -27,6 +35,7 @@ const notificationStore = useNotificationStore();
|
||||
const intervention = ref(null);
|
||||
const activeTab = ref("overview");
|
||||
const practitioners = ref([]);
|
||||
const isModalOpen = ref(false);
|
||||
|
||||
// Fetch intervention data
|
||||
const fetchIntervention = async () => {
|
||||
@ -47,18 +56,37 @@ const fetchIntervention = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Open assign modal
|
||||
const openAssignModal = () => {
|
||||
isModalOpen.value = true;
|
||||
};
|
||||
|
||||
// Close assign modal
|
||||
const closeAssignModal = () => {
|
||||
isModalOpen.value = false;
|
||||
};
|
||||
|
||||
// Handle practitioner assignment
|
||||
const handleAssignPractitioner = async (practitionerData) => {
|
||||
try {
|
||||
if (intervention.value?.id) {
|
||||
// Build the assignment payload based on role
|
||||
const payload = {};
|
||||
if (practitionerData.role === "principal") {
|
||||
payload.principal_practitioner_id = practitionerData.practitionerId;
|
||||
} else {
|
||||
payload.assistant_practitioner_ids = [practitionerData.practitionerId];
|
||||
}
|
||||
|
||||
await interventionStore.assignPractitioner(
|
||||
intervention.value.id,
|
||||
practitionerData.practitionerId
|
||||
payload
|
||||
);
|
||||
|
||||
// Refresh intervention data to get updated practitioner info
|
||||
await fetchIntervention();
|
||||
notificationStore.created("Praticien assigné");
|
||||
closeAssignModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error assigning practitioner:", error);
|
||||
|
||||
@ -30,23 +30,21 @@
|
||||
<div class="mb-3">
|
||||
<SoftInput
|
||||
id="email"
|
||||
:value="email"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
:is-required="true"
|
||||
@input="email = $event.target.value"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<SoftInput
|
||||
id="password"
|
||||
:value="password"
|
||||
v-model="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Mot de passe"
|
||||
:is-required="true"
|
||||
@input="password = $event.target.value"
|
||||
/>
|
||||
</div>
|
||||
<SoftSwitch
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user