This commit is contained in:
Nyavokevin 2025-11-07 16:51:09 +03:00
parent 8d1d65e27b
commit bbf60fb380
33 changed files with 3063 additions and 1014 deletions

426
thanas
View File

@ -1,370 +1,70 @@
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import EmployeeService from "@/services/employee";
<template>
<thanatopractitioner-template>
<template #thanatopractitioner-new-action>
<add-button text="Ajouter" @click="goToAdd" />
</template>
<template #select-filter>
<filter-table />
</template>
<template #thanatopractitioner-other-action>
<table-action />
</template>
<template #thanatopractitioner-table>
<thanatopractitioner-table
:data="thanatopractitionerData"
:loading="loadingData"
:pagination="pagination"
@view="goToDetails"
@delete="deleteThanatopractitioner"
@change-page="$emit('change-page', $event)"
/>
</template>
</thanatopractitioner-template>
</template>
<script setup>
import ThanatopractitionerTemplate from "@/components/templates/CRM/ThanatopractitionerTemplate.vue";
import ThanatopractitionerTable from "@/components/molecules/Thanatopractitioners/ThanatopractitionerTable.vue";
import addButton from "@/components/molecules/new-button/addButton.vue";
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
import TableAction from "@/components/molecules/Tables/TableAction.vue";
import { defineProps, defineEmits } from "vue";
import { useRouter } from "vue-router";
import type {
Employee,
CreateEmployeePayload,
UpdateEmployeePayload,
EmployeeListResponse,
} from "@/services/employee";
const router = useRouter();
export const useEmployeeStore = defineStore("employee", () => {
// State
const employees = ref<Employee[]>([]);
const currentEmployee = ref<Employee | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);
const searchResults = ref<Employee[]>([]);
const emit = defineEmits(["pushDetails", "deleteThanatopractitioner", "changePage"]);
// Pagination state
const pagination = ref({
current_page: 1,
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
});
// Getters
const allEmployees = computed(() => employees.value);
const activeEmployees = computed(() =>
employees.value.filter((employee) => employee.is_active)
);
const inactiveEmployees = computed(() =>
employees.value.filter((employee) => !employee.is_active)
);
const isLoading = computed(() => loading.value);
const hasError = computed(() => error.value !== null);
const getError = computed(() => error.value);
const getEmployeeById = computed(
() => (id: number) => employees.value.find((employee) => employee.id === id)
);
const getPagination = computed(() => pagination.value);
// Actions
const setLoading = (isLoading: boolean) => {
loading.value = isLoading;
};
const setError = (err: string | null) => {
error.value = err;
};
const clearError = () => {
error.value = null;
};
const setEmployees = (newEmployees: Employee[]) => {
employees.value = newEmployees;
};
const setCurrentEmployee = (employee: Employee | null) => {
currentEmployee.value = employee;
};
const setSearchEmployee = (searchEmployee: Employee[]) => {
searchResults.value = searchEmployee;
};
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,
from: meta.from || 0,
to: meta.to || 0,
};
}
};
/**
* Fetch all employees with optional pagination and filters
*/
const fetchEmployees = async (params?: {
page?: number;
per_page?: number;
search?: string;
active?: boolean;
sort_by?: string;
sort_direction?: string;
}) => {
setLoading(true);
setError(null);
try {
const response = await EmployeeService.getAllEmployees(params);
setEmployees(response.data);
setPagination(response.pagination);
return response;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to fetch employees";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Fetch a single employee by ID
*/
const fetchEmployee = async (id: number) => {
setLoading(true);
setError(null);
try {
const response = await EmployeeService.getEmployee(id);
setCurrentEmployee(response.data);
return response.data;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to fetch employee";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Create a new employee
*/
const createEmployee = async (payload: CreateEmployeePayload) => {
setLoading(true);
setError(null);
try {
const response = await EmployeeService.createEmployee(payload);
// Add the new employee to the list
employees.value.push(response.data);
setCurrentEmployee(response.data);
return response.data;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to create employee";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Update an existing employee
*/
const updateEmployee = async (payload: UpdateEmployeePayload) => {
setLoading(true);
setError(null);
try {
console.log(payload);
const response = await EmployeeService.updateEmployee(payload);
const updatedEmployee = response.data;
// Update in the employees list
const index = employees.value.findIndex(
(employee) => employee.id === updatedEmployee.id
);
if (index !== -1) {
employees.value[index] = updatedEmployee;
}
// Update current employee if it's the one being edited
if (
currentEmployee.value &&
currentEmployee.value.id === updatedEmployee.id
) {
setCurrentEmployee(updatedEmployee);
}
return updatedEmployee;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to update employee";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Delete an employee
*/
const deleteEmployee = async (id: number) => {
setLoading(true);
setError(null);
try {
const response = await EmployeeService.deleteEmployee(id);
// Remove from the employees list
employees.value = employees.value.filter(
(employee) => employee.id !== id
);
// Clear current employee if it's the one being deleted
if (currentEmployee.value && currentEmployee.value.id === id) {
setCurrentEmployee(null);
}
return response;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to delete employee";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Search employees
*/
const searchEmployees = async (query: string) => {
setLoading(true);
error.value = null;
try {
const results = await EmployeeService.searchEmployees(query);
setSearchEmployee(results);
return results;
} catch (err) {
error.value = "Erreur lors de la recherche des employés";
console.error("Error searching employees:", err);
setSearchEmployee([]);
throw err;
} finally {
setLoading(false);
}
};
/**
* Toggle employee active status
*/
const toggleEmployeeStatus = async (id: number, isActive: boolean) => {
setLoading(true);
setError(null);
try {
const response = await EmployeeService.toggleEmployeeStatus(id, isActive);
const updatedEmployee = response.data;
// Update in the employees list
const index = employees.value.findIndex((employee) => employee.id === id);
if (index !== -1) {
employees.value[index] = updatedEmployee;
}
// Update current employee if it's the one being toggled
if (currentEmployee.value && currentEmployee.value.id === id) {
setCurrentEmployee(updatedEmployee);
}
return updatedEmployee;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to toggle employee status";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Get employee statistics
*/
const fetchStatistics = async () => {
setLoading(true);
setError(null);
try {
const response = await EmployeeService.getStatistics();
return response.data;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to fetch statistics";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
}
};
/**
* Clear current employee
*/
const clearCurrentEmployee = () => {
setCurrentEmployee(null);
};
/**
* Clear all state
*/
const clearStore = () => {
employees.value = [];
currentEmployee.value = null;
error.value = null;
pagination.value = {
defineProps({
thanatopractitionerData: {
type: Array,
default: [],
},
loadingData: {
type: Boolean,
default: false,
},
pagination: {
type: Object,
default: () => ({
current_page: 1,
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
};
};
return {
// State
employees,
currentEmployee,
loading,
error,
searchResults,
// Getters
allEmployees,
activeEmployees,
inactiveEmployees,
isLoading,
hasError,
getError,
getEmployeeById,
getPagination,
// Actions
fetchEmployees,
fetchEmployee,
createEmployee,
updateEmployee,
deleteEmployee,
searchEmployees,
toggleEmployeeStatus,
fetchStatistics,
clearCurrentEmployee,
clearStore,
clearError,
};
last_page: 1,
}),
},
});
const goToAdd = () => {
router.push({
name: "Creation thanatopractitioner",
});
};
const goToDetails = (thanatopractitioner) => {
emit("pushDetails", thanatopractitioner);
};
const deleteThanatopractitioner = (thanatopractitioner) => {
emit("deleteThanatopractitioner", thanatopractitioner);
};
</script>

187
thanasoft
View File

@ -1,141 +1,60 @@
<template>
<div class="container-fluid py-4">
<div class="row">
<div class="col-12">
<div class="card mb-4">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<p class="mb-0 font-weight-bold text-lg">Gestion des Employés</p>
</div>
</div>
<div class="card-body px-0 pt-0 pb-2">
<div class="table-responsive p-0">
<!-- Filter and Action Bar -->
<div
class="d-flex justify-content-between align-items-center mb-4 px-4"
>
<div class="d-flex align-items-center">
<slot name="select-filter">
<filter-table />
</slot>
</div>
<div class="d-flex align-items-center gap-2">
<slot name="employee-other-action">
<table-action />
</slot>
<slot name="employee-new-action">
<add-button text="Ajouter" @click="goToEmployee" />
</slot>
</div>
</div>
<!-- Main Content Area -->
<div class="employee-content">
<slot name="employee-table">
<!-- Default table slot - will be overridden by specific implementations -->
</slot>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<ul class="nav nav-pills flex-column">
<TabNavigationItem
icon="fas fa-eye"
label="Aperçu"
:is-active="activeTab === 'overview'"
spacing=""
@click="$emit('change-tab', 'overview')"
/>
<TabNavigationItem
icon="fas fa-info-circle"
label="Informations"
:is-active="activeTab === 'info'"
@click="$emit('change-tab', 'info')"
/>
<TabNavigationItem
icon="fas fa-calendar"
label="Agenda"
:is-active="activeTab === 'agenda'"
@click="$emit('change-tab', 'agenda')"
/>
<TabNavigationItem
icon="fas fa-info-circle"
label="Activités récentes"
:is-active="activeTab === 'activity'"
@click="$emit('change-tab', 'activity')"
/>
<TabNavigationItem
icon="fas fa-folder"
label="Documents"
:is-active="activeTab === 'documents'"
:badge="documentsCount > 0 ? documentsCount : null"
@click="$emit('change-tab', 'documents')"
/>
<TabNavigationItem
icon="fas fa-sticky-note"
label="Notes"
:is-active="activeTab === 'notes'"
@click="$emit('change-tab', 'notes')"
/>
</ul>
</template>
<script setup>
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
import TableAction from "@/components/molecules/Tables/TableAction.vue";
import addButton from "@/components/molecules/new-button/addButton.vue";
import { useRouter } from "vue-router";
import TabNavigationItem from "@/components/atoms/client/TabNavigationItem.vue";
const router = useRouter();
import { defineProps, defineEmits } from "vue";
defineProps({
activeTab: {
type: String,
required: true,
},
documentsCount: {
type: Number,
default: 0,
},
});
const goToEmployee = () => {
router.push({
name: "Creation employé",
});
};
defineEmits(["change-tab"]);
</script>
<style scoped>
.container-fluid {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.card {
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
border: none;
}
.card-header {
background-color: transparent;
border-bottom: 1px solid #e9ecef;
padding: 1.5rem;
}
.card-body {
padding: 1.5rem;
}
.text-lg {
font-size: 1.125rem;
}
.employee-content {
position: relative;
}
.d-flex {
display: flex;
}
.align-items-center {
align-items: center;
}
.justify-content-between {
justify-content: space-between;
}
.gap-2 {
gap: 0.5rem;
}
.mb-4 {
margin-bottom: 1.5rem;
}
.px-4 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.font-weight-bold {
font-weight: 600;
}
.text-primary {
color: #5e72e4 !important;
}
@media (max-width: 768px) {
.container-fluid {
padding-left: 1rem;
padding-right: 1rem;
}
.d-flex {
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.d-flex.gap-2 {
flex-direction: row;
justify-content: center;
}
}
</style>

View File

@ -206,7 +206,7 @@ class ThanatopractitionerController extends Controller
public function show(string $id): ThanatopractitionerResource|JsonResponse
{
try {
$thanatopractitioner = $this->thanatopractitionerRepository->find($id);
$thanatopractitioner = $this->thanatopractitionerRepository->findById((int) $id);
if (!$thanatopractitioner) {
return response()->json([

View File

@ -26,7 +26,8 @@
/>
<router-view />
<app-footer v-show="showFooter" />
<configurator
<BotMessageConfigurator
:toggle="toggleConfigurator"
:class="[showConfig ? 'show' : '', hideConfigButton ? 'd-none' : '']"
/>
@ -35,7 +36,7 @@
</template>
<script>
import Sidenav from "./examples/Sidenav";
import Configurator from "@/examples/Configurator.vue";
import BotMessageConfigurator from "./examples/BotMessageConfigurator.vue";
import Navbar from "@/examples/Navbars/Navbar.vue";
import AppFooter from "@/examples/Footer.vue";
import NotificationContainer from "@/components/NotificationContainer.vue";
@ -44,10 +45,10 @@ export default {
name: "App",
components: {
Sidenav,
Configurator,
Navbar,
AppFooter,
NotificationContainer,
BotMessageConfigurator,
},
computed: {
...mapState([

View File

@ -0,0 +1,80 @@
<template>
<div>
<!-- Overview Tab -->
<div v-show="activeTab === 'overview'">
<ThanatopractitionerOverview
:thanatopractitioner="thanatopractitioner"
:formatted-date="formattedDate"
:thanatopractitioner-id="thanatopractitionerId"
/>
</div>
<!-- Information Tab -->
<div v-show="activeTab === 'info'">
<ThanatopractitionerInfoTab
:thanatopractitioner="thanatopractitioner"
@thanatopractitioner-updated="updateThanatopractitioner"
/>
</div>
<div v-show="activeTab === 'agenda'">
<ThanatopractitionerAgendaTab
:thanatopractitioner="thanatopractitioner"
/>
</div>
<div v-show="activeTab === 'activity'">
<ThanatopractitionerActivityTab
:thanatopractitioner="thanatopractitioner"
/>
</div>
<!-- Documents Tab -->
<div v-show="activeTab === 'documents'">
<ThanatopractitionerDocumentsTab
:thanatopractitioner="thanatopractitioner"
:thanatopractitioner-id="thanatopractitioner.id"
/>
</div>
<!-- Notes Tab -->
<div v-show="activeTab === 'notes'">
<ThanatopractitionerNotesTab :notes="thanatopractitioner.notes" />
</div>
</div>
</template>
<script setup>
import ThanatopractitionerOverview from "@/components/molecules/thanatopractitioner/ThanatopractitionerOverview.vue";
import ThanatopractitionerInfoTab from "@/components/molecules/thanatopractitioner/ThanatopractitionerInfoTab.vue";
import ThanatopractitionerAgendaTab from "@/components/molecules/thanatopractitioner/ThanatopractitionerAgendaTab.vue";
import ThanatopractitionerActivityTab from "@/components/molecules/thanatopractitioner/ThanatopractitionerActivityTab.vue";
import ThanatopractitionerDocumentsTab from "@/components/molecules/thanatopractitioner/ThanatopractitionerDocumentsTab.vue";
import ThanatopractitionerNotesTab from "@/components/molecules/thanatopractitioner/ThanatopractitionerNotesTab.vue";
import { defineProps, defineEmits } from "vue";
defineProps({
activeTab: {
type: String,
required: true,
},
thanatopractitioner: {
type: Object,
required: true,
},
formattedDate: {
type: String,
default: "N/A",
},
thanatopractitionerId: {
type: [Number, String],
required: true,
},
});
const emit = defineEmits(["change-tab", "updatingThanatopractitioner"]);
const updateThanatopractitioner = (updatedThanatopractitioner) => {
emit("updatingThanatopractitioner", updatedThanatopractitioner);
};
</script>

View File

@ -0,0 +1,167 @@
<template>
<thanatopractitioner-detail-template>
<template #button-return>
<div class="col-12">
<router-link
to="/crm/thanatopractitioners"
class="btn btn-outline-secondary btn-sm mb-3"
>
<i class="fas fa-arrow-left me-2"></i>Retour aux thanatopraticiens
</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 #thanatopractitioner-detail-sidebar>
<ThanatopractitionerDetailSidebar
:avatar-url="thanatopractitionerAvatar"
:initials="
getInitials(thanatopractitioner.full_name || thanatopractitioner.name)
"
:thanatopractitioner-name="
thanatopractitioner.full_name || thanatopractitioner.name
"
:thanatopractitioner-type="
thanatopractitioner.job_title || 'Thanatopraticien'
"
:documents-count="documents.length"
:is-active="thanatopractitioner.active"
:active-tab="activeTab"
@edit-avatar="triggerFileInput"
@change-tab="activeTab = $event"
/>
</template>
<template #file-input>
<input
:ref="fileInput"
type="file"
class="d-none"
accept="image/*"
@change="handleAvatarUpload"
/>
</template>
<template #thanatopractitioner-detail-content>
<ThanatopractitionerDetailContent
:active-tab="activeTab"
:thanatopractitioner="thanatopractitioner"
:documents="documents"
:formatted-address="formatAddress(thanatopractitioner)"
:thanatopractitioner-id="thanatopractitioner.id"
:document-is-loading="documentLoading"
@change-tab="activeTab = $event"
@updating-thanatopractitioner="handleUpdateThanatopractitioner"
@create-document="handleAddDocument"
@updating-document="handleModifiedDocument"
/>
</template>
</thanatopractitioner-detail-template>
</template>
<script setup>
import { defineProps, defineEmits, ref } from "vue";
import ThanatopractitionerDetailTemplate from "@/components/templates/CRM/ThanatopractitionerDetailTemplate.vue";
import ThanatopractitionerDetailSidebar from "@/components/Organism/Thanatopractitioner/ThanatopractitionerDetailSidebar.vue";
import ThanatopractitionerDetailContent from "@/components/Organism/Thanatopractitioner/ThanatopractitionerDetailContent.vue";
import { RouterLink } from "vue-router";
const props = defineProps({
thanatopractitioner: {
type: Object,
required: true,
},
documents: {
type: Array,
required: false,
default: () => [],
},
isLoading: {
type: Boolean,
default: false,
},
thanatopractitionerAvatar: {
type: String,
default: "",
},
activeTab: {
type: String,
default: "overview",
},
fileInput: {
type: Object,
required: true,
},
documentLoading: {
type: Boolean,
default: false,
},
});
const localAvatar = ref(props.thanatopractitionerAvatar);
const emit = defineEmits([
"updateThanatopractitioner",
"handleFileInput",
"add-new-document",
"updating-document",
]);
const handleAvatarUpload = (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
localAvatar.value = e.target.result;
// TODO: Upload to server
console.log("Upload avatar to server");
};
reader.readAsDataURL(file);
}
};
const handleUpdateThanatopractitioner = (updateData) => {
emit("updateThanatopractitioner", updateData);
};
const triggerFileInput = () => {
emit("handleFileInput");
};
const handleAddDocument = (data) => {
emit("add-new-document", data);
};
const handleModifiedDocument = (modifiedDocument) => {
emit("updating-document", modifiedDocument);
};
const getInitials = (name) => {
if (!name) return "?";
return name
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase()
.substring(0, 2);
};
const formatAddress = (thanatopractitioner) => {
const parts = [
thanatopractitioner.address?.line1,
thanatopractitioner.address?.line2,
thanatopractitioner.address?.postal_code,
thanatopractitioner.address?.city,
thanatopractitioner.address?.country_code,
].filter(Boolean);
return parts.length > 0 ? parts.join(", ") : "Aucune adresse renseignée";
};
</script>

View File

@ -0,0 +1,75 @@
<template>
<div class="card position-sticky top-1">
<!-- Thanatopractitioner Profile Card -->
<ThanatopractitionerProfileCard
:avatar-url="avatarUrl"
:initials="initials"
:thanatopractitioner-name="thanatopractitionerName"
:thanatopractitioner-type="thanatopractitionerType"
:documents-count="documentsCount"
:is-active="isActive"
@edit-avatar="$emit('edit-avatar')"
/>
<hr class="horizontal dark my-3 mx-3" />
<!-- Tab Navigation -->
<div class="card-body pt-0">
<ThanatopractitionerTabNavigation
:active-tab="activeTab"
:documents-count="documentsCount"
@change-tab="$emit('change-tab', $event)"
/>
</div>
</div>
</template>
<script setup>
import ThanatopractitionerProfileCard from "@/components/molecules/thanatopractitioner/ThanatopractitionerProfileCard.vue";
import ThanatopractitionerTabNavigation from "@/components/molecules/thanatopractitioner/ThanatopractitionerTabNavigation.vue";
import { defineProps, defineEmits } from "vue";
defineProps({
avatarUrl: {
type: String,
default: null,
},
initials: {
type: String,
required: true,
},
thanatopractitionerName: {
type: String,
required: true,
},
thanatopractitionerType: {
type: String,
default: "Thanatopraticien",
},
documentsCount: {
type: Number,
default: 0,
},
isActive: {
type: Boolean,
default: true,
},
activeTab: {
type: String,
required: true,
},
});
defineEmits(["edit-avatar", "change-tab"]);
</script>
<style scoped>
.position-sticky {
top: 1rem;
}
.card {
border: 0;
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, 0.15);
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<thanatopractitioner-template>
<template #thanatopractitioner-new-action>
<add-button text="Ajouter" @click="goToThanatopractitioner" />
<add-button text="Ajouter" @click="goToAdd" />
</template>
<template #select-filter>
<filter-table />
@ -16,16 +16,15 @@
:pagination="pagination"
@view="goToDetails"
@delete="deleteThanatopractitioner"
@changePage="handleChangePage"
@change-page="$emit('change-page', $event)"
/>
</template>
</thanatopractitioner-template>
</template>
<script setup>
import ThanatopractitionerTemplate from "@/components/templates/CRM/ThanatopractitionerTemplate.vue";
import ThanatopractitionerTable from "@/components/molecules/Thanatopractitioners/ThanatopractitionerTable.vue";
import AddButton from "@/components/molecules/new-button/addButton.vue";
import addButton from "@/components/molecules/new-button/addButton.vue";
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
import TableAction from "@/components/molecules/Tables/TableAction.vue";
import { defineProps, defineEmits } from "vue";
@ -39,7 +38,7 @@ const emit = defineEmits([
"changePage",
]);
const props = defineProps({
defineProps({
thanatopractitionerData: {
type: Array,
default: [],
@ -50,43 +49,26 @@ const props = defineProps({
},
pagination: {
type: Object,
default: null,
default: () => ({
current_page: 1,
per_page: 10,
total: 0,
last_page: 1,
}),
},
});
const goToThanatopractitioner = () => {
const goToAdd = () => {
router.push({
name: "Creation thanatopractitioner",
});
};
const goToDetails = (thanatopractitionerId) => {
emit("pushDetails", thanatopractitionerId);
const goToDetails = (thanatopractitioner) => {
emit("pushDetails", thanatopractitioner);
};
const deleteThanatopractitioner = (thanatopractitionerId) => {
console.log(
"deleteThanatopractitioner called in ThanatopractitionerPresentation with ID:",
thanatopractitionerId
);
emit("deleteThanatopractitioner", thanatopractitionerId);
};
const handleChangePage = (page) => {
console.log(
"handleChangePage called in ThanatopractitionerPresentation with page:",
page
);
if (page >= 1 && page <= (props.pagination?.last_page || 1)) {
console.log(
"Emitting changePage event from ThanatopractitionerPresentation:",
page
);
emit("changePage", page);
}
const deleteThanatopractitioner = (thanatopractitioner) => {
emit("deleteThanatopractitioner", thanatopractitioner);
};
</script>
<style scoped>
/* Component-specific styles */
</style>

View File

@ -0,0 +1,231 @@
<template>
<div class="card">
<div class="card-body">
<!-- Overview Tab -->
<div v-if="activeTab === 'overview'" class="tab-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="font-weight-bolder">Informations Générales</h5>
<div>
<button
v-if="!isEditing"
class="btn btn-sm btn-outline-primary me-2"
@click="startEditing"
>
<i class="fas fa-edit me-1"></i> Modifier
</button>
<template v-else>
<button class="btn btn-sm btn-success me-2" @click="saveChanges">
<i class="fas fa-save me-1"></i> Enregistrer
</button>
<button
class="btn btn-sm btn-outline-secondary"
@click="cancelEditing"
>
Annuler
</button>
</template>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label text-xs text-uppercase">Nom Complet</label>
<input
v-model="editableThanatopractitioner.name"
type="text"
class="form-control"
:disabled="!isEditing"
/>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label text-xs text-uppercase">Email</label>
<input
v-model="editableThanatopractitioner.email"
type="email"
class="form-control"
:disabled="!isEditing"
/>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label text-xs text-uppercase">Téléphone</label>
<input
v-model="editableThanatopractitioner.phone"
type="tel"
class="form-control"
:disabled="!isEditing"
/>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label text-xs text-uppercase">Spécialité</label>
<input
v-model="editableThanatopractitioner.speciality"
type="text"
class="form-control"
:disabled="!isEditing"
/>
</div>
<div class="col-12 mb-3">
<label class="form-label text-xs text-uppercase">Adresse</label>
<input
v-model="editableThanatopractitioner.address.line1"
type="text"
class="form-control mb-2"
placeholder="Ligne 1"
:disabled="!isEditing"
/>
<input
v-model="editableThanatopractitioner.address.line2"
type="text"
class="form-control mb-2"
placeholder="Ligne 2"
:disabled="!isEditing"
/>
<div class="row">
<div class="col-4">
<input
v-model="editableThanatopractitioner.address.postal_code"
type="text"
class="form-control"
placeholder="Code Postal"
:disabled="!isEditing"
/>
</div>
<div class="col-8">
<input
v-model="editableThanatopractitioner.address.city"
type="text"
class="form-control"
placeholder="Ville"
:disabled="!isEditing"
/>
</div>
</div>
</div>
</div>
</div>
<!-- Agenda Tab -->
<div v-else-if="activeTab === 'agenda'" class="tab-content">
<h5 class="font-weight-bolder mb-4">Agenda</h5>
<!-- Placeholder for agenda component -->
<p class="text-center text-muted">Aucun événement programmé</p>
</div>
<!-- Activity Tab -->
<div v-else-if="activeTab === 'activity'" class="tab-content">
<h5 class="font-weight-bolder mb-4">Activité Récente</h5>
<!-- Placeholder for activity feed -->
<p class="text-center text-muted">Aucune activité récente</p>
</div>
<!-- Documents Tab -->
<div v-else-if="activeTab === 'documents'" class="tab-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="font-weight-bolder">Documents</h5>
<button class="btn btn-sm btn-primary" @click="openDocumentModal">
<i class="fas fa-plus me-1"></i> Ajouter un document
</button>
</div>
<div v-if="documents.length === 0" class="text-center text-muted">
Aucun document
</div>
<div v-else class="row">
<div
v-for="(document, index) in documents"
:key="index"
class="col-12 col-md-4 mb-3"
>
<div class="card">
<div
class="card-body d-flex justify-content-between align-items-center"
>
<div>
<h6 class="mb-1">{{ document.name }}</h6>
<small class="text-muted">{{ document.type }}</small>
</div>
<div>
<button
class="btn btn-sm btn-outline-info me-2"
@click="viewDocument(document)"
>
<i class="fas fa-eye"></i>
</button>
<button
class="btn btn-sm btn-outline-danger"
@click="deleteDocument(document)"
>
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, defineProps, defineEmits, computed } from "vue";
const props = defineProps({
thanatopractitioner: {
type: Object,
required: true,
},
documents: {
type: Array,
default: () => [],
},
activeTab: {
type: String,
default: "overview",
},
documentIsLoading: {
type: Boolean,
default: false,
},
});
const emit = defineEmits([
"change-tab",
"updating-thanatopractitioner",
"create-document",
"updating-document",
]);
const isEditing = ref(false);
const editableThanatopractitioner = ref({ ...props.thanatopractitioner });
const startEditing = () => {
isEditing.value = true;
};
const cancelEditing = () => {
isEditing.value = false;
editableThanatopractitioner.value = { ...props.thanatopractitioner };
};
const saveChanges = () => {
emit("updating-thanatopractitioner", editableThanatopractitioner.value);
isEditing.value = false;
};
const openDocumentModal = () => {
// TODO: Implement document upload modal
console.log("Open document upload modal");
};
const viewDocument = (document) => {
// TODO: Implement document view functionality
console.log("View document", document);
};
const deleteDocument = (document) => {
// TODO: Implement document deletion
console.log("Delete document", document);
};
</script>

View File

@ -0,0 +1,147 @@
<template>
<div class="card card-profile">
<div
class="card-header text-center border-0 pt-0 pt-lg-2 pb-4 pb-lg-3 bg-transparent"
>
<div class="d-flex justify-content-between">
<button
class="btn btn-sm btn-outline-secondary mb-0 ms-auto"
@click="$emit('edit-avatar')"
>
<i class="fas fa-edit me-1"></i> Modifier
</button>
</div>
</div>
<div class="card-body pt-0">
<div class="text-center">
<div
v-if="avatarUrl"
class="avatar avatar-xl rounded-circle position-relative"
>
<img
:src="avatarUrl"
alt="Profile Image"
class="w-100 h-100 object-fit-cover"
/>
</div>
<div
v-else
class="avatar avatar-xl rounded-circle bg-gradient-primary text-white d-flex align-items-center justify-content-center"
>
{{ initials }}
</div>
<h5 class="font-weight-bold mt-3 mb-2">
{{ thanatopractitionerName }}
</h5>
<p class="text-secondary text-sm">
{{ thanatopractitionerType }}
</p>
<div class="row mt-3">
<div class="col-4">
<small class="text-muted d-block">Documents</small>
<h6 class="text-dark text-sm font-weight-bold mb-0">
{{ documentsCount }}
</h6>
</div>
<div class="col-4">
<small class="text-muted d-block">Statut</small>
<span
:class="[
'badge',
isActive ? 'bg-gradient-success' : 'bg-gradient-secondary',
]"
>
{{ isActive ? "Actif" : "Inactif" }}
</span>
</div>
</div>
</div>
</div>
<div class="card-footer p-3">
<div class="nav-wrapper position-relative">
<ul class="nav nav-pills nav-fill p-1" role="tablist">
<li class="nav-item">
<a
class="nav-link mb-0 px-0 py-1"
:class="{ active: activeTab === 'overview' }"
href="#"
@click.prevent="$emit('change-tab', 'overview')"
>
<span class="ms-1">Aperçu</span>
</a>
</li>
<li class="nav-item">
<a
class="nav-link mb-0 px-0 py-1"
:class="{ active: activeTab === 'agenda' }"
href="#"
@click.prevent="$emit('change-tab', 'agenda')"
>
<span class="ms-1">Agenda</span>
</a>
</li>
<li class="nav-item">
<a
class="nav-link mb-0 px-0 py-1"
:class="{ active: activeTab === 'activity' }"
href="#"
@click.prevent="$emit('change-tab', 'activity')"
>
<span class="ms-1">Activité</span>
</a>
</li>
<li class="nav-item">
<a
class="nav-link mb-0 px-0 py-1"
:class="{ active: activeTab === 'documents' }"
href="#"
@click.prevent="$emit('change-tab', 'documents')"
>
<span class="ms-1">Documents</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
avatarUrl: {
type: String,
default: "",
},
initials: {
type: String,
default: "?",
},
thanatopractitionerName: {
type: String,
required: true,
},
thanatopractitionerType: {
type: String,
default: "Thanatopraticien",
},
documentsCount: {
type: Number,
default: 0,
},
isActive: {
type: Boolean,
default: true,
},
activeTab: {
type: String,
default: "overview",
},
});
defineEmits(["edit-avatar", "change-tab"]);
</script>

View File

@ -115,13 +115,6 @@
<!-- Name Column (from employee relation) -->
<td class="font-weight-bold">
<div class="d-flex align-items-center">
<soft-avatar
:img="getRandomAvatar()"
size="xs"
class="me-2"
alt="user image"
circular
/>
<div>
<span>{{
thanatopractitioner.employee?.full_name ||
@ -273,7 +266,7 @@
>
<div class="text-sm text-muted">
Affichage de {{ pagination.from }} à {{ pagination.to }} sur
{{ pagination.total }} thanatopractitioners
{{ pagination.total }} thanatopracteurs
</div>
<div class="d-flex align-items-center gap-2">
<!-- Previous Button -->
@ -325,9 +318,9 @@
<div class="empty-icon">
<i class="fas fa-user-md fa-3x text-muted"></i>
</div>
<h5 class="empty-title">Aucun thanatopractitioner trouvé</h5>
<h5 class="empty-title">Aucun thanatopracteur trouvé</h5>
<p class="empty-text text-muted">
Aucun thanatopractitioner à afficher pour le moment.
Aucun thanatopracteur à afficher pour le moment.
</p>
</div>
</div>
@ -378,12 +371,6 @@ const props = defineProps({
},
});
// Methods
const getRandomAvatar = () => {
const randomIndex = Math.floor(Math.random() * avatarImages.length);
return avatarImages[randomIndex];
};
const formatDate = (dateString) => {
if (!dateString) return "N/A";
const date = new Date(dateString);
@ -399,23 +386,16 @@ const isAuthorizationValid = (expiryDate) => {
// Direct button handlers
const handleView = (thanatopractitionerId) => {
console.log("Direct view button clicked for ID:", thanatopractitionerId);
emit("view", thanatopractitionerId);
};
const handleDelete = (thanatopractitionerId) => {
console.log("Direct delete button clicked for ID:", thanatopractitionerId);
emit("delete", thanatopractitionerId);
};
// Pagination methods
const changePage = (page) => {
console.log("changePage called in ThanatopractitionerTable with page:", page);
if (page >= 1 && page <= props.pagination.last_page) {
console.log(
"Emitting changePage event from ThanatopractitionerTable:",
page
);
emit("changePage", page);
}
};
@ -458,32 +438,6 @@ const getVisiblePages = () => {
return pages;
};
// Watch for data changes
watch(
() => props.data,
() => {
if (!props.loading) {
console.log("ThanatopractitionerTable: Data changed");
}
},
{ deep: true }
);
onUnmounted(() => {
// Clean up any event listeners if needed
});
// Initialize data
onMounted(() => {
if (!props.loading && props.data.length > 0) {
console.log(
"ThanatopractitionerTable: Component mounted with",
props.data.length,
"thanatopractitioners"
);
}
});
</script>
<style scoped>

View File

@ -0,0 +1,205 @@
<template>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<p class="font-weight-bold mb-0">Vue d'ensemble</p>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-6 mb-xl-0">
<div class="card card-profile-card">
<div class="card-body">
<h6 class="mb-0 font-weight-bold text-sm">
Informations Personnelles
</h6>
<div class="mt-3">
<div class="d-flex align-items-center mb-2">
<span class="text-xs text-secondary text-uppercase"
>Nom complet</span
>
</div>
<p class="text-dark text-sm">
{{ thanatopractitioner.employee?.full_name || "N/A" }}
</p>
<div class="d-flex align-items-center mb-2 mt-3">
<span class="text-xs text-secondary text-uppercase"
>Email</span
>
</div>
<p class="text-dark text-sm">
{{ thanatopractitioner.employee?.email || "N/A" }}
</p>
<div class="d-flex align-items-center mb-2 mt-3">
<span class="text-xs text-secondary text-uppercase"
>Téléphone</span
>
</div>
<p class="text-dark text-sm">
{{ thanatopractitioner.employee?.phone || "N/A" }}
</p>
<div class="d-flex align-items-center mb-2 mt-3">
<span class="text-xs text-secondary text-uppercase"
>Poste</span
>
</div>
<p class="text-dark text-sm">
{{ thanatopractitioner.employee?.job_title || "N/A" }}
</p>
</div>
</div>
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-6">
<div class="card card-profile-card">
<div class="card-body">
<h6 class="mb-0 font-weight-bold text-sm">
Informations Professionnelles
</h6>
<div class="mt-3">
<div class="d-flex align-items-center mb-2">
<span class="text-xs text-secondary text-uppercase"
>Numéro de Diplôme</span
>
</div>
<p class="text-dark text-sm">
{{ thanatopractitioner.diploma_number || "N/A" }}
</p>
<div class="d-flex align-items-center mb-2 mt-3">
<span class="text-xs text-secondary text-uppercase"
>Date du Diplôme</span
>
</div>
<p class="text-dark text-sm">
{{ formatDate(thanatopractitioner.diploma_date) }}
</p>
<div class="d-flex align-items-center mb-2 mt-3">
<span class="text-xs text-secondary text-uppercase"
>Numéro d'Autorisation</span
>
</div>
<p class="text-dark text-sm">
{{ thanatopractitioner.authorization_number || "N/A" }}
</p>
<div class="d-flex align-items-center mb-2 mt-3">
<span class="text-xs text-secondary text-uppercase"
>Date d'Émission</span
>
</div>
<p class="text-dark text-sm">
{{
formatDate(thanatopractitioner.authorization_issue_date)
}}
</p>
<div class="d-flex align-items-center mb-2 mt-3">
<span class="text-xs text-secondary text-uppercase"
>Date d'Expiration</span
>
</div>
<p class="text-dark text-sm">
{{
formatDate(
thanatopractitioner.authorization_expiry_date
)
}}
</p>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card card-profile-card">
<div class="card-body">
<h6 class="mb-0 font-weight-bold text-sm">
Statut et Créé le
</h6>
<div class="mt-3">
<div class="d-flex align-items-center">
<div
class="badge me-3"
:class="{
'bg-success': thanatopractitioner.active,
'bg-danger': !thanatopractitioner.active
}"
>
<i
:class="{
'fas fa-check-circle me-1': thanatopractitioner.active,
'fas fa-times-circle me-1': !thanatopractitioner.active
}"
></i>
{{ thanatopractitioner.active ? "Actif" : "Inactif" }}
</div>
<div class="text-xs text-secondary">
Créé le {{ formattedDate }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
thanatopractitioner: {
type: Object,
required: true,
},
formattedDate: {
type: String,
default: "N/A",
},
thanatopractitionerId: {
type: [Number, String],
required: true,
},
});
const formatDate = (dateString) => {
if (!dateString) return "N/A";
const date = new Date(dateString);
return date.toLocaleDateString("fr-FR");
};
</script>
<style scoped>
.card-profile-card {
border: 0;
box-shadow: 0 0 2rem 0 rgba(136, 152, 170, 0.15);
border-radius: 0.5rem;
}
.text-xs {
font-size: 0.75rem;
}
.text-sm {
font-size: 0.875rem;
}
.font-weight-bold {
font-weight: 600;
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<div class="card-body text-center">
<!-- Client Avatar -->
<ClientAvatar
:avatar-url="avatarUrl"
:initials="initials"
:alt="thanatopractitionerName"
:editable="true"
@edit="$emit('edit-avatar')"
/>
<!-- Client Name -->
<h5 class="font-weight-bolder mb-0">
{{ thanatopractitionerName }}
</h5>
<p class="text-sm text-secondary mb-3">
{{ thanatopractitionerType }}
</p>
<!-- Quick Stats -->
<div class="row text-center mt-3">
<div class="col-6 border-end">
<h6 class="text-sm font-weight-bolder mb-0">
<i
class="fas"
:class="
isActive
? 'fa-check-circle text-success'
: 'fa-times-circle text-danger'
"
></i>
</h6>
<p class="text-xs text-secondary mb-0">Statut</p>
</div>
<div class="col-6">
<h6 class="text-sm font-weight-bolder mb-0">
{{ diplomaNumber || 'N/A' }}
</h6>
<p class="text-xs text-secondary mb-0">Diplôme</p>
</div>
</div>
</div>
</template>
<script setup>
import ClientAvatar from "@/components/atoms/client/ClientAvatar.vue";
import { defineProps, defineEmits } from "vue";
defineProps({
avatarUrl: {
type: String,
default: null,
},
initials: {
type: String,
required: true,
},
thanatopractitionerName: {
type: String,
required: true,
},
thanatopractitionerType: {
type: String,
default: "Thanatopractitioner",
},
isActive: {
type: Boolean,
default: true,
},
diplomaNumber: {
type: String,
default: null,
},
});
defineEmits(["edit-avatar"]);
</script>

View File

@ -0,0 +1,31 @@
<template>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<p class="font-weight-bold mb-0">Activité du Thanatopractitioner</p>
</div>
</div>
<div class="card-body">
<div class="text-center py-5">
<i class="fas fa-chart-line fa-3x text-muted mb-3"></i>
<h6 class="text-muted">Fonctionnalité à venir</h6>
<p class="text-muted">L'activité sera bientôt disponible.</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
thanatopractitionerId: {
type: [Number, String],
required: true,
},
});
</script>

View File

@ -0,0 +1,31 @@
<template>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<p class="font-weight-bold mb-0">Agenda du Thanatopractitioner</p>
</div>
</div>
<div class="card-body">
<div class="text-center py-5">
<i class="fas fa-calendar-alt fa-3x text-muted mb-3"></i>
<h6 class="text-muted">Fonctionnalité à venir</h6>
<p class="text-muted">L'agenda sera bientôt disponible.</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
thanatopractitionerId: {
type: [Number, String],
required: true,
},
});
</script>

View File

@ -0,0 +1,51 @@
<template>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center justify-content-between">
<p class="font-weight-bold mb-0">
Documents du Thanatopractitioner
</p>
<soft-button
type="button"
class="btn bg-gradient-primary btn-sm"
@click="handleAddDocument"
>
<i class="fas fa-plus me-2"></i>Ajouter
</soft-button>
</div>
</div>
<div class="card-body">
<div class="text-center py-5">
<i class="fas fa-folder fa-3x text-muted mb-3"></i>
<h6 class="text-muted">Fonctionnalité à venir</h6>
<p class="text-muted">
La gestion des documents sera bientôt disponible.
</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from "vue";
import SoftButton from "@/components/SoftButton.vue";
defineProps({
thanatopractitioner: {
type: Object,
required: true,
},
thanatopractitionerId: {
type: [Number, String],
required: true,
},
});
const handleAddDocument = () => {
console.log("Add document functionality to be implemented");
};
</script>

View File

@ -0,0 +1,188 @@
<template>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<p class="font-weight-bold mb-0">
Informations du Thanatopractitioner
</p>
</div>
</div>
<div class="card-body">
<form novalidate @submit.prevent="handleSubmit">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="diploma_number" class="form-label">
Numéro de Diplôme
</label>
<soft-input
id="diploma_number"
v-model="form.diploma_number"
type="text"
placeholder="Entrez le numéro de diplôme"
/>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="diploma_date" class="form-label">
Date du Diplôme
</label>
<soft-input
id="diploma_date"
v-model="form.diploma_date"
type="date"
/>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="authorization_number" class="form-label">
Numéro d'Autorisation
</label>
<soft-input
id="authorization_number"
v-model="form.authorization_number"
type="text"
placeholder="Entrez le numéro d'autorisation"
/>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="active" class="form-label">Statut</label>
<select
id="active"
v-model="form.active"
class="form-control"
>
<option :value="true">Actif</option>
<option :value="false">Inactif</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="authorization_issue_date" class="form-label">
Date d'Émission
</label>
<soft-input
id="authorization_issue_date"
v-model="form.authorization_issue_date"
type="date"
/>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="authorization_expiry_date" class="form-label">
Date d'Expiration
</label>
<soft-input
id="authorization_expiry_date"
v-model="form.authorization_expiry_date"
type="date"
/>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="form-group">
<label for="notes" class="form-label">Notes</label>
<textarea
id="notes"
v-model="form.notes"
class="form-control"
rows="3"
placeholder="Notes additionnelles..."
></textarea>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 d-flex justify-content-end">
<soft-button
type="button"
class="btn btn-light me-3"
@click="resetForm"
>
Annuler
</soft-button>
<soft-button type="submit" class="btn bg-gradient-primary">
Mettre à jour
</soft-button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, defineProps, defineEmits } from "vue";
import SoftInput from "@/components/SoftInput.vue";
import SoftButton from "@/components/SoftButton.vue";
const props = defineProps({
thanatopractitioner: {
type: Object,
required: true,
},
});
const emit = defineEmits(["thanatopractitioner-updated"]);
// Form data
const form = reactive({
diploma_number: props.thanatopractitioner.diploma_number || "",
diploma_date: props.thanatopractitioner.diploma_date || "",
authorization_number: props.thanatopractitioner.authorization_number || "",
authorization_issue_date:
props.thanatopractitioner.authorization_issue_date || "",
authorization_expiry_date:
props.thanatopractitioner.authorization_expiry_date || "",
notes: props.thanatopractitioner.notes || "",
active: props.thanatopractitioner.active || false,
});
const handleSubmit = () => {
emit("thanatopractitioner-updated", { ...form });
};
const resetForm = () => {
form.diploma_number = props.thanatopractitioner.diploma_number || "";
form.diploma_date = props.thanatopractitioner.diploma_date || "";
form.authorization_number =
props.thanatopractitioner.authorization_number || "";
form.authorization_issue_date =
props.thanopractitioner.authorization_issue_date || "";
form.authorization_expiry_date =
props.thanatopractitioner.authorization_expiry_date || "";
form.notes = props.thanatopractitioner.notes || "";
form.active = props.thanatopractitioner.active || false;
};
</script>
<style scoped>
.form-label {
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<p class="font-weight-bold mb-0">Notes du Thanatopractitioner</p>
</div>
</div>
<div class="card-body">
<div v-if="notes" class="notes-content">
<p class="text-dark">{{ notes }}</p>
</div>
<div v-else class="text-center py-5">
<i class="fas fa-sticky-note fa-3x text-muted mb-3"></i>
<h6 class="text-muted">Aucune note</h6>
<p class="text-muted">
Aucune note n'a été ajoutée pour ce thanatopractitioner.
</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
notes: {
type: String,
default: null,
},
});
</script>
<style scoped>
.notes-content {
background-color: #f8f9fa;
border-radius: 0.5rem;
padding: 1rem;
border-left: 4px solid #007bff;
}
.text-dark {
color: #495057 !important;
line-height: 1.6;
margin: 0;
}
</style>

View File

@ -0,0 +1,242 @@
<template>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-header pb-0">
<div class="d-flex align-items-center">
<p class="font-weight-bold mb-0">Aperçu du Thanatopractitioner</p>
</div>
</div>
<div class="card-body">
<div class="row">
<!-- Employee Information -->
<div class="col-lg-6">
<h6 class="font-weight-bold mb-3">Informations Personnelles</h6>
<div class="info-item mb-3">
<label class="text-sm text-muted">Nom Complet</label>
<div class="font-weight-bold">
{{ getFullName() }}
</div>
</div>
<div class="info-item mb-3">
<label class="text-sm text-muted">Email</label>
<div class="font-weight-bold">
{{ thanatopractitioner.employee?.email || "N/A" }}
</div>
</div>
<div class="info-item mb-3">
<label class="text-sm text-muted">Téléphone</label>
<div class="font-weight-bold">
{{ thanatopractitioner.employee?.phone || "N/A" }}
</div>
</div>
<div class="info-item mb-3">
<label class="text-sm text-muted">Poste</label>
<div class="font-weight-bold">
{{
thanatopractitioner.employee?.job_title ||
"Thanatopractitioner"
}}
</div>
</div>
</div>
<!-- Professional Information -->
<div class="col-lg-6">
<h6 class="font-weight-bold mb-3">
Informations Professionnelles
</h6>
<div class="info-item mb-3">
<label class="text-sm text-muted">Numéro de Diplôme</label>
<div class="font-weight-bold">
{{ thanatopractitioner.diploma_number || "N/A" }}
</div>
</div>
<div class="info-item mb-3">
<label class="text-sm text-muted">Date du Diplôme</label>
<div class="font-weight-bold">
{{ formatDate(thanatopractitioner.diploma_date) }}
</div>
</div>
<div class="info-item mb-3">
<label class="text-sm text-muted">Numéro d'Autorisation</label>
<div class="font-weight-bold">
{{ thanatopractitioner.authorization_number || "N/A" }}
</div>
</div>
<div class="info-item mb-3">
<label class="text-sm text-muted"
>Validité de l'Autorisation</label
>
<div class="font-weight-bold">
<span
:class="[
'badge',
isAuthorizationValid() ? 'bg-success' : 'bg-danger',
'badge-sm',
]"
>
{{ formatAuthorizationStatus() }}
</span>
</div>
</div>
</div>
</div>
<!-- Status and Additional Information -->
<div class="row mt-4">
<div class="col-12">
<h6 class="font-weight-bold mb-3">Statut et Informations</h6>
<div class="row">
<div class="col-md-4">
<div class="info-item mb-3">
<label class="text-sm text-muted">Statut</label>
<div>
<span
:class="[
'badge',
thanatopractitioner.active
? 'bg-success'
: 'bg-danger',
'badge-sm',
]"
>
{{ thanatopractitioner.active ? "Actif" : "Inactif" }}
</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="info-item mb-3">
<label class="text-sm text-muted">Date de Création</label>
<div class="font-weight-bold">
{{ formattedDate }}
</div>
</div>
</div>
<div class="col-md-4">
<div class="info-item mb-3">
<label class="text-sm text-muted">ID</label>
<div class="font-weight-bold">
#{{ thanatopractitioner.id }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Notes Section -->
<div v-if="thanatopractitioner.notes" class="row mt-4">
<div class="col-12">
<h6 class="font-weight-bold mb-3">Notes</h6>
<div class="notes-section">
<p class="text-dark">{{ thanatopractitioner.notes }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, computed } from "vue";
const props = defineProps({
thanatopractitioner: {
type: Object,
required: true,
},
formattedDate: {
type: String,
default: "N/A",
},
thanatopractitionerId: {
type: [Number, String],
required: true,
},
});
const getFullName = () => {
return props.thanatopractitioner.employee?.full_name || "N/A";
};
const formatDate = (dateString) => {
if (!dateString) return "N/A";
const date = new Date(dateString);
return date.toLocaleDateString("fr-FR");
};
const isAuthorizationValid = () => {
if (!props.thanatopractitioner.authorization_expiry_date) {
return false;
}
const validityDate = new Date(
props.thanatopractitioner.authorization_expiry_date
);
const today = new Date();
return validityDate > today;
};
const formatAuthorizationStatus = () => {
if (!props.thanatopractitioner.authorization_expiry_date) {
return "Aucune autorisation";
}
const expiryDate = new Date(
props.thanatopractitioner.authorization_expiry_date
);
const today = new Date();
const diffTime = expiryDate.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays < 0) {
return "Expirée";
} else if (diffDays <= 30) {
return `Expire dans ${diffDays} jour${diffDays > 1 ? "s" : ""}`;
} else {
return `Valide (${diffDays} jours)`;
}
};
</script>
<style scoped>
.info-item {
margin-bottom: 1rem;
}
.info-item label {
display: block;
margin-bottom: 0.25rem;
font-weight: 600;
color: #6c757d;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.font-weight-bold {
font-weight: 600;
color: #495057;
}
.notes-section {
background-color: #f8f9fa;
border-radius: 0.5rem;
padding: 1rem;
border-left: 4px solid #007bff;
}
.badge-sm {
font-size: 0.7rem;
padding: 0.3rem 0.6rem;
}
.text-dark {
color: #495057 !important;
line-height: 1.6;
margin: 0;
}
</style>

View File

@ -0,0 +1,146 @@
<template>
<div class="card-body">
<div class="row">
<div class="col-auto">
<div class="avatar avatar-xl position-relative">
<soft-avatar
:img="avatarUrl"
size="xxl"
class="border-radius-lg shadow-sm"
:alt="thanatopractitionerName"
circular
>
<template #fallback>
<div class="avatar-circle">
{{ initials }}
</div>
</template>
</soft-avatar>
<button
class="btn btn-sm btn-icon-only bg-gradient-primary position-absolute bottom-0 end-0 mb-n2 me-n2"
type="button"
@click="$emit('edit-avatar')"
>
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="col-auto my-auto">
<div class="h-100">
<h5 class="mb-1 text-primary font-weight-bold">
{{ thanatopractitionerName }}
</h5>
<p class="mb-0 text-xs text-secondary font-weight-bold text-sm">
{{ thanatopractitionerType }}
</p>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="d-flex align-items-center">
<div class="me-2">
<div
class="badge"
:class="{
'bg-success': isActive,
'bg-danger': !isActive,
}"
>
<i
:class="{
'fas fa-check-circle me-1': isActive,
'fas fa-times-circle me-1': !isActive,
}"
></i>
{{ isActive ? "Actif" : "Inactif" }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import SoftAvatar from "@/components/SoftAvatar.vue";
import { defineProps, defineEmits } from "vue";
defineProps({
avatarUrl: {
type: String,
default: null,
},
initials: {
type: String,
required: true,
},
thanatopractitionerName: {
type: String,
required: true,
},
thanatopractitionerType: {
type: String,
default: "Thanatopractitioner",
},
isActive: {
type: Boolean,
default: true,
},
diplomaNumber: {
type: String,
default: null,
},
authorizationNumber: {
type: String,
default: null,
},
authorizationStatus: {
type: Object,
default: () => ({ status: "unknown", text: "Statut inconnu" }),
},
});
defineEmits(["edit-avatar"]);
</script>
<style scoped>
.avatar-circle {
width: 100%;
height: 100%;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 1.2rem;
}
.info-item {
border-left: 3px solid #e9ecef;
padding-left: 0.75rem;
}
.card-body {
padding: 1.5rem;
}
.btn-icon-only {
width: 2rem;
height: 2rem;
padding: 0.25rem;
}
.position-absolute.bottom-0.end-0 {
right: -0.5rem;
bottom: -0.5rem;
}
.position-absolute.bottom-0.end-0.mb-n2.me-n2 {
margin-bottom: -0.5rem;
margin-right: -0.5rem;
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<ul class="nav nav-pills flex-column">
<TabNavigationItem
icon="fas fa-eye"
label="Aperçu"
:is-active="activeTab === 'overview'"
@click="$emit('change-tab', 'overview')"
/>
<TabNavigationItem
icon="fas fa-info-circle"
label="Informations"
:is-active="activeTab === 'info'"
@click="$emit('change-tab', 'info')"
/>
<TabNavigationItem
icon="fas fa-info-circle"
label="Activités récentes"
:is-active="activeTab === 'activity'"
@click="$emit('change-tab', 'activity')"
/>
<TabNavigationItem
icon="fas fa-file-alt"
label="Documents"
:is-active="activeTab === 'documents'"
:badge="documentsCount > 0 ? documentsCount : null"
@click="$emit('change-tab', 'documents')"
/>
<TabNavigationItem
icon="fas fa-map-marker-alt"
label="Adresse"
:is-active="activeTab === 'address'"
@click="$emit('change-tab', 'address')"
/>
<TabNavigationItem
icon="fas fa-sticky-note"
label="Notes"
:is-active="activeTab === 'notes'"
@click="$emit('change-tab', 'notes')"
/>
<TabNavigationItem
icon="fas fa-calendar-alt"
label="Agenda"
:is-active="activeTab === 'agenda'"
@click="$emit('change-tab', 'agenda')"
/>
</ul>
</template>
<script setup>
import TabNavigationItem from "@/components/atoms/client/TabNavigationItem.vue";
import { defineProps, defineEmits } from "vue";
defineProps({
activeTab: {
type: String,
required: true,
},
documentsCount: {
type: Number,
default: 0,
},
});
defineEmits(["change-tab"]);
</script>

View File

@ -0,0 +1,20 @@
<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="thanatopractitioner-detail-sidebar" />
<slot name="file-input" />
</div>
<div class="col-lg-9 mt-lg-0 mt-4">
<slot name="thanatopractitioner-detail-content" />
</div>
</div>
</div>
</template>
<script setup>
// Template only component
</script>

View File

@ -1,24 +1,45 @@
<template>
<div class="container-fluid py-4">
<div class="d-sm-flex justify-content-between">
<div>
<slot name="thanatopractitioner-new-action"></slot>
</div>
<div class="d-flex">
<div class="dropdown d-inline">
<slot name="select-filter"></slot>
</div>
<slot name="thanatopractitioner-other-action"></slot>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card mt-4">
<slot name="thanatopractitioner-table"></slot>
<div class="card">
<div class="card-header pb-0">
<div class="d-lg-flex">
<div>
<h6 class="mb-0 font-weight-bolder text-lg">
Thanatopracteurs
</h6>
<p class="text-sm mb-0">
<i class="fa fa-check text-info" aria-hidden="true"></i>
Liste des thanatopracteurs
</p>
</div>
<div class="ms-auto my-auto mt-lg-0 mt-4">
<div class="ms-auto my-auto">
<slot name="thanatopractitioner-new-action" />
</div>
</div>
</div>
</div>
<div class="card-body px-0 pb-0">
<div class="row px-4 pb-3">
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<slot name="select-filter" />
</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3 ms-auto">
<slot name="thanatopractitioner-other-action" />
</div>
</div>
<div class="table-responsive">
<slot name="thanatopractitioner-table" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script></script>
<script setup>
// Template only component
</script>

View File

@ -0,0 +1,20 @@
<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="thanatopractitioner-detail-sidebar" />
<slot name="file-input" />
</div>
<div class="col-lg-9 mt-lg-0 mt-4">
<slot name="thanatopractitioner-detail-content" />
</div>
</div>
</div>
</template>
<script setup>
// Template only component
</script>

View File

@ -0,0 +1,600 @@
<template>
<div class="messenger-configurator">
<!-- Floating Action Button -->
<div class="fab-container" @click="toggle">
<div class="fab">
<i class="fas fa-robot"></i>
</div>
</div>
<!-- Messenger Configuration Panel -->
<div v-if="isOpen" class="messenger-panel">
<!-- Header -->
<div class="messenger-header">
<div class="bot-avatar">
<i class="fas fa-robot"></i>
</div>
<div class="bot-info">
<h4>AI Assistant</h4>
<p>En ligne</p>
</div>
<div class="header-actions">
<button class="btn-icon" @click="toggle">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<!-- Chat Area -->
<div class="chat-container">
<!-- Bot Welcome Message -->
<div class="message bot-message">
<div class="message-avatar">
<i class="fas fa-robot"></i>
</div>
<div class="message-content">
<p>
Bonjour ! Je suis votre assistant IA. Comment puis-je vous aider
aujourd'hui ?
</p>
<span class="message-time">Maintenant</span>
</div>
</div>
<!-- Configuration Options as Messages -->
<div class="message bot-message">
<div class="message-avatar">
<i class="fas fa-robot"></i>
</div>
<div class="message-content">
<p>Personnalisons mon comportement selon vos préférences :</p>
<!-- Personality Selection -->
<div class="option-group">
<h6>Personnalité</h6>
<div class="option-buttons">
<button
class="option-btn"
:class="{ active: personality === 'professional' }"
@click="setPersonality('professional')"
>
<i class="fas fa-suitcase"></i>
<span>Professionnel</span>
</button>
<button
class="option-btn"
:class="{ active: personality === 'friendly' }"
@click="setPersonality('friendly')"
>
<i class="fas fa-smile"></i>
<span>Amical</span>
</button>
<button
class="option-btn"
:class="{ active: personality === 'technical' }"
@click="setPersonality('technical')"
>
<i class="fas fa-code"></i>
<span>Technique</span>
</button>
</div>
</div>
<!-- Language Selection -->
<div class="option-group">
<h6>Langue</h6>
<div class="option-buttons">
<button
class="option-btn"
:class="{ active: language === 'fr' }"
@click="setLanguage('fr')"
>
<i class="fas fa-flag"></i>
<span>Français</span>
</button>
<button
class="option-btn"
:class="{ active: language === 'en' }"
@click="setLanguage('en')"
>
<i class="fas fa-flag-usa"></i>
<span>English</span>
</button>
</div>
</div>
<!-- Response Length -->
<div class="option-group">
<h6>Longueur des réponses</h6>
<div class="option-buttons">
<button
class="option-btn"
:class="{ active: responseLength === 'short' }"
@click="setResponseLength('short')"
>
<i class="fas fa-comment-alt"></i>
<span>Court</span>
</button>
<button
class="option-btn"
:class="{ active: responseLength === 'medium' }"
@click="setResponseLength('medium')"
>
<i class="fas fa-comments"></i>
<span>Moyen</span>
</button>
<button
class="option-btn"
:class="{ active: responseLength === 'detailed' }"
@click="setResponseLength('detailed')"
>
<i class="fas fa-file-alt"></i>
<span>Détaillé</span>
</button>
</div>
</div>
<span class="message-time">Maintenant</span>
</div>
</div>
<!-- User Selection Confirmation -->
<div v-if="hasSelections" class="message user-message">
<div class="message-content">
<p>J'ai sélectionné :</p>
<ul>
<li v-if="personality">Personnalité: {{ getPersonalityText }}</li>
<li v-if="language">Langue: {{ getLanguageText }}</li>
<li v-if="responseLength">
Longueur: {{ getResponseLengthText }}
</li>
</ul>
<span class="message-time">Maintenant</span>
</div>
<div class="message-avatar user-avatar">
<i class="fas fa-user"></i>
</div>
</div>
<!-- Bot Confirmation Message -->
<div v-if="hasSelections" class="message bot-message">
<div class="message-avatar">
<i class="fas fa-robot"></i>
</div>
<div class="message-content">
<p>
Parfait ! J'ai pris note de vos préférences. Souhaitez-vous
enregistrer cette configuration ?
</p>
<div class="confirmation-actions">
<button class="btn-primary" @click="saveConfiguration">
<i class="fas fa-save"></i>
Enregistrer
</button>
<button class="btn-secondary" @click="resetToDefault">
<i class="fas fa-redo"></i>
Réinitialiser
</button>
</div>
<span class="message-time">Maintenant</span>
</div>
</div>
</div>
<!-- Input Area -->
<div class="input-container">
<div class="input-wrapper">
<input
type="text"
placeholder="Tapez votre message..."
class="message-input"
v-model="userMessage"
@keyup.enter="sendMessage"
/>
<button class="send-btn" @click="sendMessage">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "MessengerConfigurator",
props: {
toggle: {
type: Function,
default: null,
},
},
data() {
return {
isOpen: false,
personality: "professional",
language: "fr",
responseLength: "medium",
userMessage: "",
hasSelections: false,
};
},
computed: {
getPersonalityText() {
const personalities = {
professional: "Professionnel",
friendly: "Amical",
technical: "Technique",
};
return personalities[this.personality];
},
getLanguageText() {
return this.language === "fr" ? "Français" : "English";
},
getResponseLengthText() {
const lengths = {
short: "Court",
medium: "Moyen",
detailed: "Détaillé",
};
return lengths[this.responseLength];
},
},
methods: {
toggle() {
this.isOpen = !this.isOpen;
if (this.toggle) {
this.toggle();
}
},
setPersonality(type) {
this.personality = type;
this.hasSelections = true;
},
setLanguage(lang) {
this.language = lang;
this.hasSelections = true;
},
setResponseLength(length) {
this.responseLength = length;
this.hasSelections = true;
},
saveConfiguration() {
// Save configuration logic
this.$store.dispatch("saveAIAssistantConfig", {
personality: this.personality,
language: this.language,
responseLength: this.responseLength,
});
// Show success message
alert("Configuration enregistrée avec succès !");
},
resetToDefault() {
this.personality = "professional";
this.language = "fr";
this.responseLength = "medium";
this.hasSelections = false;
},
sendMessage() {
if (this.userMessage.trim()) {
// Handle user message
console.log("User message:", this.userMessage);
this.userMessage = "";
}
},
},
};
</script>
<style scoped>
.messenger-configurator {
position: relative;
z-index: 9999;
}
/* Floating Action Button */
.fab-container {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 1000;
}
.fab {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
cursor: pointer;
transition: all 0.3s ease;
}
.fab:hover {
transform: scale(1.1);
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
}
/* Messenger Panel */
.messenger-panel {
position: fixed;
bottom: 100px;
right: 24px;
width: 380px;
height: 600px;
background: white;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 1000;
}
/* Header */
.messenger-header {
display: flex;
align-items: center;
padding: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.bot-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.bot-info {
flex: 1;
}
.bot-info h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.bot-info p {
margin: 0;
font-size: 12px;
opacity: 0.8;
}
.header-actions {
display: flex;
}
.btn-icon {
background: none;
border: none;
color: white;
font-size: 16px;
cursor: pointer;
padding: 4px;
border-radius: 4px;
}
.btn-icon:hover {
background: rgba(255, 255, 255, 0.1);
}
/* Chat Container */
.chat-container {
flex: 1;
padding: 16px;
overflow-y: auto;
background: #f8f9fa;
}
/* Messages */
.message {
display: flex;
margin-bottom: 16px;
}
.bot-message {
justify-content: flex-start;
}
.user-message {
justify-content: flex-end;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 8px;
flex-shrink: 0;
}
.bot-message .message-avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.user-avatar {
background: #e9ecef;
color: #495057;
}
.message-content {
max-width: 70%;
background: white;
border-radius: 18px;
padding: 12px 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.bot-message .message-content {
border-top-left-radius: 4px;
}
.user-message .message-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-top-right-radius: 4px;
}
.message-content p {
margin: 0 0 8px 0;
font-size: 14px;
line-height: 1.4;
}
.message-time {
font-size: 11px;
opacity: 0.6;
}
/* Option Groups */
.option-group {
margin: 16px 0;
}
.option-group h6 {
margin: 0 0 8px 0;
font-size: 13px;
font-weight: 600;
color: #495057;
}
.option-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.option-btn {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px 12px;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
color: #495057;
}
.option-btn:hover {
background: #e9ecef;
}
.option-btn.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-color: #667eea;
}
.option-btn i {
font-size: 16px;
margin-bottom: 4px;
}
/* Confirmation Actions */
.confirmation-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.btn-primary,
.btn-secondary {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
border: none;
border-radius: 20px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-primary:hover,
.btn-secondary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Input Area */
.input-container {
padding: 16px;
background: white;
border-top: 1px solid #e9ecef;
}
.input-wrapper {
display: flex;
align-items: center;
background: #f8f9fa;
border-radius: 24px;
padding: 8px 16px;
}
.message-input {
flex: 1;
border: none;
background: transparent;
outline: none;
font-size: 14px;
}
.send-btn {
background: none;
border: none;
color: #667eea;
cursor: pointer;
padding: 4px;
border-radius: 50%;
}
.send-btn:hover {
background: #e9ecef;
}
/* Responsive */
@media (max-width: 480px) {
.messenger-panel {
width: 100vw;
height: 100vh;
bottom: 0;
right: 0;
border-radius: 0;
}
.fab-container {
bottom: 16px;
right: 16px;
}
}
</style>

View File

@ -1,65 +1,5 @@
<template>
<footer class="py-3 footer">
<div class="container-fluid">
<div class="row align-items-center justify-content-lg-between">
<div class="mb-4 col-lg-6 mb-lg-0">
<div
class="text-sm text-center copyright text-muted"
:class="this.$store.state.isRTL ? 'text-lg-end' : 'text-lg-start'"
>
©
{{ new Date().getFullYear() }}, made with
<i class="fa fa-heart"></i> by
<a
href="https://www.creative-tim.com"
class="font-weight-bold"
target="_blank"
>Creative Tim</a
>
for a better web.
</div>
</div>
<div class="col-lg-6">
<ul
class="nav nav-footer justify-content-center justify-content-lg-end"
>
<li class="nav-item">
<a
href="https://www.creative-tim.com"
class="nav-link text-muted"
target="_blank"
>Creative Tim</a
>
</li>
<li class="nav-item">
<a
href="https://www.creative-tim.com/presentation"
class="nav-link text-muted"
target="_blank"
>About Us</a
>
</li>
<li class="nav-item">
<a
href="https://www.creative-tim.com/blog"
class="nav-link text-muted"
target="_blank"
>Blog</a
>
</li>
<li class="nav-item">
<a
href="https://www.creative-tim.com/license"
class="nav-link pe-0 text-muted"
target="_blank"
>License</a
>
</li>
</ul>
</div>
</div>
</div>
</footer>
<footer class="py-3 footer"></footer>
</template>
<script>

View File

@ -87,9 +87,11 @@
<a
class="p-0 nav-link"
:class="textWhite ? textWhite : 'text-body'"
@click="toggleConfigurator"
@click="toggleMessenger"
>
<i class="cursor-pointer fa fa-cog fixed-plugin-button-nav"></i>
<i
class="cursor-pointer fa fa-comment fixed-plugin-button-nav"
></i>
</a>
</li>
<li
@ -227,11 +229,13 @@
import Breadcrumbs from "../Breadcrumbs.vue";
import { mapMutations, mapActions, mapState } from "vuex";
import { useAuthStore } from "@/stores/auth";
import BotMessageConfigurator from "@/examples/BotMessageConfigurator.vue";
export default {
name: "Navbar",
components: {
Breadcrumbs,
BotMessageConfigurator,
},
props: {
minNav: {
@ -246,6 +250,7 @@ export default {
data() {
return {
showMenu: false,
showMessenger: false,
};
},
computed: {
@ -270,6 +275,10 @@ export default {
this.navbarMinimize();
},
toggleMessenger() {
this.showMessenger = !this.showMessenger;
},
async handleLogout() {
try {
const authStore = useAuthStore();

View File

@ -561,14 +561,14 @@ const routes = [
component: () =>
import("@/views/pages/Thanatopractitioners/AddThanatopractitioner.vue"),
},
// {
// path: "/employes/thanatopracteurs/:id",
// name: "Thanatopractitioner details",
// component: () =>
// import(
// "@/views/pages/Thanatopractitioners/ThanatopractitionerDetails.vue"
// ),
// },
{
path: "/employes/thanatopracteurs/:id",
name: "Thanatopractitioner details",
component: () =>
import(
"@/views/pages/Thanatopractitioners/ThanatopractitionerDetails.vue"
),
},
// Paramétrage
{
path: "/parametrage/droits",

View File

@ -12,6 +12,14 @@ export interface Thanatopractitioner {
active: boolean;
created_at: string;
updated_at: string;
// Additional computed/mapped fields
full_name?: string;
first_name?: string;
last_name?: string;
email?: string | null;
job_title?: string | null;
// Relations
employee?: {
id: number;

View File

@ -1,14 +1,11 @@
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import ThanatopractitionerService from "@/services/thanatopractitioner";
import type {
import ThanatopractitionerService, {
Thanatopractitioner,
CreateThanatopractitionerPayload,
UpdateThanatopractitionerPayload,
ThanatopractitionerListResponse,
NestedThanatopractitionerListResponse,
} from "@/services/thanatopractitioner";
import { useNotificationStore } from "./notification";
export const useThanatopractitionerStore = defineStore(
"thanatopractitioner",
@ -17,373 +14,361 @@ export const useThanatopractitionerStore = defineStore(
const thanatopractitioners = ref<Thanatopractitioner[]>([]);
const currentThanatopractitioner = ref<Thanatopractitioner | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);
const searchResults = ref<Thanatopractitioner[]>([]);
// Pagination state
const pagination = ref({
current_page: 1,
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
});
const isLoading = ref(false);
const currentPage = ref(1);
const perPage = ref(10);
const total = ref(0);
const lastPage = ref(1);
const from = ref(0);
const to = ref(0);
const search = ref("");
const activeFilter = ref<boolean | undefined>(undefined);
// Getters
const allThanatopractitioners = computed(() => thanatopractitioners.value);
const activeThanatopractitioners = computed(() =>
thanatopractitioners.value.filter(
(thanatopractitioner: Thanatopractitioner) => thanatopractitioner.active
)
);
const inactiveThanatopractitioners = computed(() =>
thanatopractitioners.value.filter(
(thanatopractitioner: Thanatopractitioner) =>
!thanatopractitioner.active
)
);
const isLoading = computed(() => loading.value);
const hasError = computed(() => error.value !== null);
const getError = computed(() => error.value);
const getThanatopractitionerById = computed(() => (id: number) =>
thanatopractitioners.value.find(
(thanatopractitioner: Thanatopractitioner) =>
thanatopractitioner.id === id
)
);
const getPagination = computed(() => pagination.value);
const getPagination = computed(() => ({
current_page: currentPage.value,
last_page: lastPage.value,
per_page: perPage.value,
total: total.value,
from: from.value,
to: to.value,
}));
const totalPages = computed(() => lastPage.value);
const hasNextPage = computed(() => currentPage.value < lastPage.value);
const hasPreviousPage = computed(() => currentPage.value > 1);
const isEmpty = computed(() => thanatopractitioners.value.length === 0);
// Actions
const setLoading = (isLoading: boolean) => {
loading.value = isLoading;
};
const setError = (err: string | null) => {
error.value = err;
};
const clearError = () => {
error.value = null;
};
const setThanatopractitioners = (
newThanatopractitioners: Thanatopractitioner[]
const fetchThanatopractitioners = async (
params: {
page?: number;
per_page?: number;
search?: string;
active?: boolean;
} = {}
) => {
thanatopractitioners.value = newThanatopractitioners;
};
const setCurrentThanatopractitioner = (
thanatopractitioner: Thanatopractitioner | null
) => {
currentThanatopractitioner.value = thanatopractitioner;
};
const setSearchThanatopractitioner = (
searchThanatopractitioner: Thanatopractitioner[]
) => {
searchResults.value = searchThanatopractitioner;
};
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,
from: meta.from || 0,
to: meta.to || 0,
};
}
};
/**
* Fetch all thanatopractitioners with optional pagination and filters
*/
const fetchThanatopractitioners = async (params?: {
page?: number;
per_page?: number;
search?: string;
active?: boolean;
sort_by?: string;
sort_direction?: string;
}) => {
setLoading(true);
setError(null);
try {
loading.value = true;
console.log("Fetching thanatopractitioners with params:", {
page: params.page || 1,
per_page: params.per_page || perPage.value,
search: params.search || "",
active: params.active,
});
const response = await ThanatopractitionerService.getAllThanatopractitioners(
params
{
page: params.page || 1,
per_page: params.per_page || perPage.value,
search: params.search || "",
active: params.active,
}
);
setThanatopractitioners(response.data.data);
setPagination(response.data.pagination);
console.log("Full response:", JSON.stringify(response, null, 2));
// Safely handle the response with more robust type handling
const data = response.data?.data || response.data || [];
const pagination = response.data?.pagination || {
current_page: 1,
last_page: 1,
total: data.length,
from: 0,
to: data.length,
};
// Map data to include employee information with safe fallbacks
thanatopractitioners.value = data.map((item: Thanatopractitioner) => ({
...item,
...(item.employee || {}),
full_name: item.employee?.full_name || "N/A",
job_title: item.employee?.job_title || "N/A",
first_name: item.employee?.first_name || "N/A",
last_name: item.employee?.last_name || "N/A",
email: item.employee?.email || "N/A",
}));
// Update pagination values
currentPage.value = pagination.current_page;
lastPage.value = pagination.last_page;
total.value = pagination.total;
from.value = pagination.from;
to.value = pagination.to;
search.value = params.search || "";
activeFilter.value = params.active;
return response;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to fetch thanatopractitioners";
setError(errorMessage);
throw err;
} catch (error) {
console.error("Error fetching thanatopractitioners:", error);
throw error;
} finally {
setLoading(false);
loading.value = false;
}
};
/**
* Fetch a single thanatopractitioner by ID
*/
const fetchThanatopractitioner = async (id: number) => {
setLoading(true);
setError(null);
try {
isLoading.value = true;
const response = await ThanatopractitionerService.getThanatopractitioner(
id
);
setCurrentThanatopractitioner(response.data);
return response.data;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to fetch thanatopractitioner";
setError(errorMessage);
throw err;
// Safely handle the response with additional mapping
const data = response.data;
currentThanatopractitioner.value = {
...data,
...(data.employee || {}),
full_name: data.employee?.full_name || "N/A",
job_title: data.employee?.job_title || "N/A",
first_name: data.employee?.first_name || "N/A",
last_name: data.employee?.last_name || "N/A",
email: data.employee?.email || "N/A",
};
return currentThanatopractitioner.value;
} catch (error) {
console.error("Error fetching thanatopractitioner:", error);
throw error;
} finally {
setLoading(false);
isLoading.value = false;
}
};
// Add method for details view
const fetchThanatopractitionerById = async (id: number) => {
try {
const response = await ThanatopractitionerService.getThanatopractitioner(
Number(id)
);
// Safely handle the response with additional mapping
const data = response.data;
return {
...data,
...(data.employee || {}),
full_name: data.employee?.full_name || "N/A",
job_title: data.employee?.job_title || "N/A",
first_name: data.employee?.first_name || "N/A",
last_name: data.employee?.last_name || "N/A",
email: data.employee?.email || "N/A",
};
} catch (error) {
console.error("Error fetching thanatopractitioner by ID:", error);
throw error;
}
};
// Add method for documents (placeholder)
const fetchThanatopractitionerDocuments = async (id: number) => {
try {
// TODO: Implement actual document fetching logic
return [];
} catch (error) {
console.error("Error fetching thanatopractitioner documents:", error);
return [];
}
};
// Add method for adding documents (placeholder)
const addThanatopractitionerDocument = async (documentData: any) => {
try {
// TODO: Implement actual document addition logic
console.log("Adding document:", documentData);
} catch (error) {
console.error("Error adding thanatopractitioner document:", error);
throw error;
}
};
// Add method for updating documents (placeholder)
const updateThanatopractitionerDocument = async (documentData: any) => {
try {
// TODO: Implement actual document update logic
console.log("Updating document:", documentData);
} catch (error) {
console.error("Error updating thanatopractitioner document:", error);
throw error;
}
};
/**
* Create a new thanatopractitioner
*/
const createThanatopractitioner = async (
payload: CreateThanatopractitionerPayload
) => {
setLoading(true);
setError(null);
try {
loading.value = true;
const response = await ThanatopractitionerService.createThanatopractitioner(
payload
);
// Add the new thanatopractitioner to the list
thanatopractitioners.value.push(response.data);
setCurrentThanatopractitioner(response.data);
return response.data;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to create thanatopractitioner";
setError(errorMessage);
throw err;
// Add the new thanatopractitioner to the list with safe mapping
const newThanatopractitioner = {
...response.data,
...(response.data.employee || {}),
full_name: response.data.employee?.full_name || "N/A",
job_title: response.data.employee?.job_title || "N/A",
first_name: response.data.employee?.first_name || "N/A",
last_name: response.data.employee?.last_name || "N/A",
email: response.data.employee?.email || "N/A",
};
thanatopractitioners.value.unshift(newThanatopractitioner);
total.value += 1;
return newThanatopractitioner;
} catch (error) {
console.error("Error creating thanatopractitioner:", error);
throw error;
} finally {
setLoading(false);
loading.value = false;
}
};
/**
* Update an existing thanatopractitioner
*/
const updateThanatopractitioner = async (
payload: UpdateThanatopractitionerPayload
) => {
setLoading(true);
setError(null);
try {
console.log(payload);
loading.value = true;
const response = await ThanatopractitionerService.updateThanatopractitioner(
payload
);
const updatedThanatopractitioner = response.data;
// Safely map the updated thanatopractitioner
const updatedThanatopractitioner = {
...response.data,
...(response.data.employee || {}),
full_name: response.data.employee?.full_name || "N/A",
job_title: response.data.employee?.job_title || "N/A",
first_name: response.data.employee?.first_name || "N/A",
last_name: response.data.employee?.last_name || "N/A",
email: response.data.employee?.email || "N/A",
};
// Update in the thanatopractitioners list
const index = thanatopractitioners.value.findIndex(
(thanatopractitioner: Thanatopractitioner) =>
thanatopractitioner.id === updatedThanatopractitioner.id
(t) => t.id === payload.id
);
if (index !== -1) {
thanatopractitioners.value[index] = updatedThanatopractitioner;
}
// Update current thanatopractitioner if it's the one being edited
if (
currentThanatopractitioner.value &&
currentThanatopractitioner.value.id === updatedThanatopractitioner.id
) {
setCurrentThanatopractitioner(updatedThanatopractitioner);
// Update current thanatopractitioner if it's the same one
if (currentThanatopractitioner.value?.id === payload.id) {
currentThanatopractitioner.value = updatedThanatopractitioner;
}
return updatedThanatopractitioner;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to update thanatopractitioner";
setError(errorMessage);
throw err;
} catch (error) {
console.error("Error updating thanatopractitioner:", error);
throw error;
} finally {
setLoading(false);
loading.value = false;
}
};
/**
* Delete a thanatopractitioner
*/
const deleteThanatopractitioner = async (id: number) => {
setLoading(true);
setError(null);
try {
const response = await ThanatopractitionerService.deleteThanatopractitioner(
id
);
loading.value = true;
await ThanatopractitionerService.deleteThanatopractitioner(id);
// Remove from the thanatopractitioners list
thanatopractitioners.value = thanatopractitioners.value.filter(
(thanatopractitioner: Thanatopractitioner) =>
thanatopractitioner.id !== id
(t) => t.id !== id
);
total.value -= 1;
// Clear current thanatopractitioner if it's the one being deleted
if (
currentThanatopractitioner.value &&
currentThanatopractitioner.value.id === id
) {
setCurrentThanatopractitioner(null);
// Clear current thanatopractitioner if it's the deleted one
if (currentThanatopractitioner.value?.id === id) {
currentThanatopractitioner.value = null;
}
return response;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to delete thanatopractitioner";
setError(errorMessage);
throw err;
} catch (error) {
console.error("Error deleting thanatopractitioner:", error);
throw error;
} finally {
setLoading(false);
loading.value = false;
}
};
/**
* Search thanatopractitioners
*/
const searchThanatopractitioners = async (query: string) => {
setLoading(true);
error.value = null;
try {
const results = await ThanatopractitionerService.searchThanatopractitioners(
query
);
setSearchThanatopractitioner(results);
return results;
} catch (err) {
error.value = "Erreur lors de la recherche des thanatopractitioners";
console.error("Error searching thanatopractitioners:", err);
setSearchThanatopractitioner([]);
throw err;
} finally {
setLoading(false);
}
};
/**
* Toggle thanatopractitioner active status
*/
const toggleThanatopractitionerStatus = async (
id: number,
isActive: boolean
) => {
setLoading(true);
setError(null);
try {
const response = await ThanatopractitionerService.toggleThanatopractitionerStatus(
id,
isActive
);
const updatedThanatopractitioner = response.data;
// Safely map the updated thanatopractitioner
const updatedThanatopractitioner = {
...response.data,
...(response.data.employee || {}),
full_name: response.data.employee?.full_name || "N/A",
job_title: response.data.employee?.job_title || "N/A",
first_name: response.data.employee?.first_name || "N/A",
last_name: response.data.employee?.last_name || "N/A",
email: response.data.employee?.email || "N/A",
};
// Update in the thanatopractitioners list
const index = thanatopractitioners.value.findIndex(
(thanatopractitioner: Thanatopractitioner) =>
thanatopractitioner.id === id
);
const index = thanatopractitioners.value.findIndex((t) => t.id === id);
if (index !== -1) {
thanatopractitioners.value[index] = updatedThanatopractitioner;
}
// Update current thanatopractitioner if it's the one being toggled
if (
currentThanatopractitioner.value &&
currentThanatopractitioner.value.id === id
) {
setCurrentThanatopractitioner(updatedThanatopractitioner);
// Update current thanatopractitioner if it's the same one
if (currentThanatopractitioner.value?.id === id) {
currentThanatopractitioner.value = updatedThanatopractitioner;
}
return updatedThanatopractitioner;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to toggle thanatopractitioner status";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
} catch (error) {
console.error("Error toggling thanatopractitioner status:", error);
throw error;
}
};
/**
* Get thanatopractitioner statistics
*/
const fetchStatistics = async () => {
setLoading(true);
setError(null);
const searchThanatopractitioners = async (query: string) => {
try {
loading.value = true;
const results = await ThanatopractitionerService.searchThanatopractitioners(
query
);
return results;
} catch (error) {
console.error("Error searching thanatopractitioners:", error);
throw error;
} finally {
loading.value = false;
}
};
const getStatistics = async () => {
try {
const response = await ThanatopractitionerService.getStatistics();
return response.data;
} catch (err: any) {
const errorMessage =
err.response?.data?.message ||
err.message ||
"Failed to fetch statistics";
setError(errorMessage);
throw err;
} finally {
setLoading(false);
} catch (error) {
console.error("Error fetching thanatopractitioner statistics:", error);
throw error;
}
};
/**
* Clear current thanatopractitioner
*/
const clearCurrentThanatopractitioner = () => {
setCurrentThanatopractitioner(null);
const setCurrentPage = (page: number) => {
currentPage.value = page;
};
/**
* Clear all state
*/
const clearStore = () => {
thanatopractitioners.value = [];
const setPerPage = (newPerPage: number) => {
perPage.value = newPerPage;
currentPage.value = 1; // Reset to first page
};
const clearCurrentThanatopractitioner = () => {
currentThanatopractitioner.value = null;
error.value = null;
pagination.value = {
current_page: 1,
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
};
};
const resetFilters = () => {
search.value = "";
activeFilter.value = undefined;
currentPage.value = 1;
};
return {
@ -391,31 +376,42 @@ export const useThanatopractitionerStore = defineStore(
thanatopractitioners,
currentThanatopractitioner,
loading,
error,
searchResults,
isLoading,
currentPage,
perPage,
total,
lastPage,
from,
to,
search,
activeFilter,
// Getters
allThanatopractitioners,
activeThanatopractitioners,
inactiveThanatopractitioners,
isLoading,
hasError,
getError,
getThanatopractitionerById,
getPagination,
totalPages,
hasNextPage,
hasPreviousPage,
isEmpty,
// Actions
fetchThanatopractitioners,
fetchThanatopractitioner,
fetchThanatopractitionerById,
fetchThanatopractitionerDocuments,
addThanatopractitionerDocument,
updateThanatopractitionerDocument,
createThanatopractitioner,
updateThanatopractitioner,
deleteThanatopractitioner,
searchThanatopractitioners,
toggleThanatopractitionerStatus,
fetchStatistics,
searchThanatopractitioners,
getStatistics,
setCurrentPage,
setPerPage,
clearCurrentThanatopractitioner,
clearStore,
clearError,
resetFilters,
};
}
);
export default useThanatopractitionerStore;

View File

@ -0,0 +1,89 @@
<template>
<div>
<ThanatopractitionerDetailPresentation
v-if="thanatopractitioner"
:thanatopractitioner="thanatopractitioner"
:documents="documents"
:is-loading="isLoading"
:file-input="fileInput"
@updateThanatopractitioner="updateThanatopractitioner"
@add-new-document="addDocument"
@updating-document="updateDocument"
/>
<div v-else-if="isLoading" class="text-center p-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Chargement...</span>
</div>
</div>
<div v-else class="text-center p-5">
<p>Thanatopraticien non trouvé</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { useThanatopractitionerStore } from "@/stores/thanatopractitionerStore";
import ThanatopractitionerDetailPresentation from "@/components/Organism/Thanatopractitioner/ThanatopractitionerDetailPresentation.vue";
const route = useRoute();
const thanatopractitionerStore = useThanatopractitionerStore();
const thanatopractitioner = ref(null);
const documents = ref([]);
const isLoading = ref(true);
const fileInput = ref(null);
onMounted(async () => {
try {
const id = route.params.id;
thanatopractitioner.value = await thanatopractitionerStore.fetchThanatopractitionerById(
id
);
documents.value = await thanatopractitionerStore.fetchThanatopractitionerDocuments(
id
);
} catch (error) {
console.error("Error fetching thanatopractitioner details:", error);
} finally {
isLoading.value = false;
}
});
const updateThanatopractitioner = async (updateData) => {
try {
await thanatopractitionerStore.updateThanatopractitioner(updateData);
// Optionally refresh the data
thanatopractitioner.value = await thanatopractitionerStore.fetchThanatopractitionerById(
updateData.id
);
} catch (error) {
console.error("Error updating thanatopractitioner:", error);
}
};
const addDocument = async (documentData) => {
try {
await thanatopractitionerStore.addThanatopractitionerDocument(documentData);
documents.value = await thanatopractitionerStore.fetchThanatopractitionerDocuments(
thanatopractitioner.value.id
);
} catch (error) {
console.error("Error adding document:", error);
}
};
const updateDocument = async (documentData) => {
try {
await thanatopractitionerStore.updateThanatopractitionerDocument(
documentData
);
documents.value = await thanatopractitionerStore.fetchThanatopractitionerDocuments(
thanatopractitioner.value.id
);
} catch (error) {
console.error("Error updating document:", error);
}
};
</script>

View File

@ -1,8 +1,8 @@
<template>
<thanatopractitioner-presentation
:thanatopractitioner-data="thanatopractitionerStore.thanatopractitioners"
:loading-data="thanatopractitionerStore.loading"
:pagination="thanatopractitionerStore.getPagination"
:thanatopractitioner-data="thanatopractitioners"
:loading-data="loading"
:pagination="pagination"
@push-details="goDetails"
@delete-thanatopractitioner="confirmDeleteThanatopractitioner"
@change-page="changePage"
@ -25,7 +25,7 @@
</template>
<script setup>
import { reactive, onMounted } from "vue";
import { ref, reactive, onMounted } from "vue";
import ThanatopractitionerPresentation from "@/components/Organism/Thanatopractitioner/ThanatopractitionerPresentation.vue";
import ConfirmModal from "@/components/molecules/common/ConfirmModal.vue";
import { useThanatopractitionerStore } from "@/stores/thanatopractitionerStore";
@ -36,6 +36,15 @@ const thanatopractitionerStore = useThanatopractitionerStore();
const notificationStore = useNotificationStore();
const router = useRouter();
const thanatopractitioners = ref([]);
const loading = ref(false);
const pagination = ref({
current_page: 1,
last_page: 1,
per_page: 10,
total: 0,
});
// Confirm modal state
const confirmModal = reactive({
isVisible: false,
@ -52,94 +61,91 @@ const confirmModal = reactive({
});
onMounted(async () => {
await thanatopractitionerStore.fetchThanatopractitioners();
await fetchThanatopractitioners();
});
const fetchThanatopractitioners = async (
params = { page: 1, per_page: 10 }
) => {
try {
loading.value = true;
const response = await thanatopractitionerStore.fetchThanatopractitioners(
params
);
// Update data based on the new API response structure
thanatopractitioners.value = response.data.data.map((item) => ({
...item.employee,
...item,
full_name: item.employee.full_name,
job_title: item.employee.job_title,
}));
// Update pagination
pagination.value = {
current_page: response.pagination.current_page,
last_page: response.pagination.last_page,
per_page: response.pagination.per_page,
total: response.pagination.total,
};
} catch (error) {
console.error("Error fetching thanatopractitioners:", error);
notificationStore.error(
"Erreur de chargement",
"Une erreur est survenue lors du chargement des thanatopractitioners."
);
} finally {
loading.value = false;
}
};
const goDetails = (id) => {
router.push({
name: "Thanatopractitioner details",
params: {
id: id,
},
params: { id: id },
});
};
const confirmDeleteThanatopractitioner = (thanatopractitionerId) => {
console.log(
"confirmDeleteThanatopractitioner called with ID:",
thanatopractitionerId
);
console.log("Thanatopractitioner ID type:", typeof thanatopractitionerId);
console.log(
"Store thanatopractitioners:",
thanatopractitionerStore.thanatopractitioners
);
console.log(
"Store thanatopractitioners length:",
thanatopractitionerStore.thanatopractitioners.length
);
const thanatopractitioner = thanatopractitionerStore.thanatopractitioners.find(
const thanatopractitioner = thanatopractitioners.value.find(
(prac) => prac.id === thanatopractitionerId
);
console.log("Found thanatopractitioner:", thanatopractitioner);
if (thanatopractitioner) {
console.log("Showing confirmation modal");
confirmModal.isVisible = true;
confirmModal.thanatopractitionerId = thanatopractitionerId;
confirmModal.thanatopractitionerName = `${thanatopractitioner.first_name} ${thanatopractitioner.last_name}`;
confirmModal.thanatopractitionerName = thanatopractitioner.full_name;
confirmModal.message = `Êtes-vous sûr de vouloir supprimer le thanatopractitioner "${confirmModal.thanatopractitionerName}" ?`;
} else {
console.log("No thanatopractitioner found - trying to parse as number");
const numericId = parseInt(thanatopractitionerId, 10);
console.log("Trying with numeric ID:", numericId);
const thanatopractitionerByNumber = thanatopractitionerStore.thanatopractitioners.find(
(prac) => prac.id === numericId
);
console.log("Found by numeric ID:", thanatopractitionerByNumber);
if (thanatopractitionerByNumber) {
console.log("Showing confirmation modal with numeric ID");
confirmModal.isVisible = true;
confirmModal.thanatopractitionerId = numericId;
confirmModal.thanatopractitionerName = `${thanatopractitionerByNumber.first_name} ${thanatopractitionerByNumber.last_name}`;
confirmModal.message = `Êtes-vous sûr de vouloir supprimer le thanatopractitioner "${confirmModal.thanatopractitionerName}" ?`;
} else {
console.log(
"Still no thanatopractitioner found - displaying all thanatopractitioner IDs for comparison"
);
thanatopractitionerStore.thanatopractitioners.forEach((prac, index) => {
console.log(
`Thanatopractitioner ${index}: ID=${
prac.id
} (${typeof prac.id}), Name=${prac.first_name} ${prac.last_name}`
);
});
}
}
};
const handleConfirmDelete = async () => {
const thanatopractitionerId = confirmModal.thanatopractitionerId;
const thanatopractitionerName = confirmModal.thanatopractitionerName;
console.log("Test");
try {
confirmModal.isLoading = true;
await thanatopractitionerStore.deleteThanatopractitioner(
thanatopractitionerId
);
notificationStore.success(
"Thanatopractitioner supprimé",
`Le thanatopractitioner ${thanatopractitionerName} a été supprimé avec succès.`
);
closeConfirmModal();
// Refresh the list after deletion
await fetchThanatopractitioners({
page: pagination.value.current_page,
per_page: pagination.value.per_page,
});
} catch (error) {
console.error("Error deleting thanatopractitioner:", error);
notificationStore.error(
"Erreur de suppression",
"Une erreur est survenue lors de la suppression du thanatopractitioner."
);
} finally {
closeConfirmModal();
}
};
@ -156,12 +162,10 @@ const closeConfirmModal = () => {
};
const changePage = async (page) => {
console.log("changePage called in Thanatopractitioners.vue with page:", page);
try {
console.log("Fetching thanatopractitioners with page:", page);
await thanatopractitionerStore.fetchThanatopractitioners({
await fetchThanatopractitioners({
page,
per_page: 10,
per_page: pagination.value.per_page,
});
} catch (error) {
console.error("Error changing page:", error);