thanato
This commit is contained in:
parent
8d1d65e27b
commit
bbf60fb380
426
thanas
426
thanas
@ -1,370 +1,70 @@
|
|||||||
import { defineStore } from "pinia";
|
<template>
|
||||||
import { ref, computed } from "vue";
|
<thanatopractitioner-template>
|
||||||
import EmployeeService from "@/services/employee";
|
<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 {
|
const router = useRouter();
|
||||||
Employee,
|
|
||||||
CreateEmployeePayload,
|
|
||||||
UpdateEmployeePayload,
|
|
||||||
EmployeeListResponse,
|
|
||||||
} from "@/services/employee";
|
|
||||||
|
|
||||||
export const useEmployeeStore = defineStore("employee", () => {
|
const emit = defineEmits(["pushDetails", "deleteThanatopractitioner", "changePage"]);
|
||||||
// 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[]>([]);
|
|
||||||
|
|
||||||
// Pagination state
|
defineProps({
|
||||||
const pagination = ref({
|
thanatopractitionerData: {
|
||||||
current_page: 1,
|
type: Array,
|
||||||
last_page: 1,
|
default: [],
|
||||||
per_page: 10,
|
},
|
||||||
total: 0,
|
loadingData: {
|
||||||
from: 0,
|
type: Boolean,
|
||||||
to: 0,
|
default: false,
|
||||||
});
|
},
|
||||||
|
pagination: {
|
||||||
// Getters
|
type: Object,
|
||||||
const allEmployees = computed(() => employees.value);
|
default: () => ({
|
||||||
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 = {
|
|
||||||
current_page: 1,
|
current_page: 1,
|
||||||
last_page: 1,
|
|
||||||
per_page: 10,
|
per_page: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
from: 0,
|
last_page: 1,
|
||||||
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,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const goToAdd = () => {
|
||||||
|
router.push({
|
||||||
|
name: "Creation thanatopractitioner",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToDetails = (thanatopractitioner) => {
|
||||||
|
emit("pushDetails", thanatopractitioner);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteThanatopractitioner = (thanatopractitioner) => {
|
||||||
|
emit("deleteThanatopractitioner", thanatopractitioner);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|||||||
187
thanasoft
187
thanasoft
@ -1,141 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-fluid py-4">
|
<ul class="nav nav-pills flex-column">
|
||||||
<div class="row">
|
<TabNavigationItem
|
||||||
<div class="col-12">
|
icon="fas fa-eye"
|
||||||
<div class="card mb-4">
|
label="Aperçu"
|
||||||
<div class="card-header pb-0">
|
:is-active="activeTab === 'overview'"
|
||||||
<div class="d-flex align-items-center">
|
spacing=""
|
||||||
<p class="mb-0 font-weight-bold text-lg">Gestion des Employés</p>
|
@click="$emit('change-tab', 'overview')"
|
||||||
</div>
|
/>
|
||||||
</div>
|
<TabNavigationItem
|
||||||
<div class="card-body px-0 pt-0 pb-2">
|
icon="fas fa-info-circle"
|
||||||
<div class="table-responsive p-0">
|
label="Informations"
|
||||||
<!-- Filter and Action Bar -->
|
:is-active="activeTab === 'info'"
|
||||||
<div
|
@click="$emit('change-tab', 'info')"
|
||||||
class="d-flex justify-content-between align-items-center mb-4 px-4"
|
/>
|
||||||
>
|
<TabNavigationItem
|
||||||
<div class="d-flex align-items-center">
|
icon="fas fa-calendar"
|
||||||
<slot name="select-filter">
|
label="Agenda"
|
||||||
<filter-table />
|
:is-active="activeTab === 'agenda'"
|
||||||
</slot>
|
@click="$emit('change-tab', 'agenda')"
|
||||||
</div>
|
/>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<TabNavigationItem
|
||||||
<slot name="employee-other-action">
|
icon="fas fa-info-circle"
|
||||||
<table-action />
|
label="Activités récentes"
|
||||||
</slot>
|
:is-active="activeTab === 'activity'"
|
||||||
<slot name="employee-new-action">
|
@click="$emit('change-tab', 'activity')"
|
||||||
<add-button text="Ajouter" @click="goToEmployee" />
|
/>
|
||||||
</slot>
|
<TabNavigationItem
|
||||||
</div>
|
icon="fas fa-folder"
|
||||||
</div>
|
label="Documents"
|
||||||
|
:is-active="activeTab === 'documents'"
|
||||||
<!-- Main Content Area -->
|
:badge="documentsCount > 0 ? documentsCount : null"
|
||||||
<div class="employee-content">
|
@click="$emit('change-tab', 'documents')"
|
||||||
<slot name="employee-table">
|
/>
|
||||||
<!-- Default table slot - will be overridden by specific implementations -->
|
<TabNavigationItem
|
||||||
</slot>
|
icon="fas fa-sticky-note"
|
||||||
</div>
|
label="Notes"
|
||||||
</div>
|
:is-active="activeTab === 'notes'"
|
||||||
</div>
|
@click="$emit('change-tab', 'notes')"
|
||||||
</div>
|
/>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
import TabNavigationItem from "@/components/atoms/client/TabNavigationItem.vue";
|
||||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
|
||||||
import addButton from "@/components/molecules/new-button/addButton.vue";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
|
|
||||||
const router = useRouter();
|
import { defineProps, defineEmits } from "vue";
|
||||||
|
defineProps({
|
||||||
|
activeTab: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
documentsCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const goToEmployee = () => {
|
defineEmits(["change-tab"]);
|
||||||
router.push({
|
|
||||||
name: "Creation employé",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
</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>
|
|
||||||
|
|||||||
@ -206,7 +206,7 @@ class ThanatopractitionerController extends Controller
|
|||||||
public function show(string $id): ThanatopractitionerResource|JsonResponse
|
public function show(string $id): ThanatopractitionerResource|JsonResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$thanatopractitioner = $this->thanatopractitionerRepository->find($id);
|
$thanatopractitioner = $this->thanatopractitionerRepository->findById((int) $id);
|
||||||
|
|
||||||
if (!$thanatopractitioner) {
|
if (!$thanatopractitioner) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|||||||
@ -26,7 +26,8 @@
|
|||||||
/>
|
/>
|
||||||
<router-view />
|
<router-view />
|
||||||
<app-footer v-show="showFooter" />
|
<app-footer v-show="showFooter" />
|
||||||
<configurator
|
|
||||||
|
<BotMessageConfigurator
|
||||||
:toggle="toggleConfigurator"
|
:toggle="toggleConfigurator"
|
||||||
:class="[showConfig ? 'show' : '', hideConfigButton ? 'd-none' : '']"
|
:class="[showConfig ? 'show' : '', hideConfigButton ? 'd-none' : '']"
|
||||||
/>
|
/>
|
||||||
@ -35,7 +36,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Sidenav from "./examples/Sidenav";
|
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 Navbar from "@/examples/Navbars/Navbar.vue";
|
||||||
import AppFooter from "@/examples/Footer.vue";
|
import AppFooter from "@/examples/Footer.vue";
|
||||||
import NotificationContainer from "@/components/NotificationContainer.vue";
|
import NotificationContainer from "@/components/NotificationContainer.vue";
|
||||||
@ -44,10 +45,10 @@ export default {
|
|||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
Sidenav,
|
Sidenav,
|
||||||
Configurator,
|
|
||||||
Navbar,
|
Navbar,
|
||||||
AppFooter,
|
AppFooter,
|
||||||
NotificationContainer,
|
NotificationContainer,
|
||||||
|
BotMessageConfigurator,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<thanatopractitioner-template>
|
<thanatopractitioner-template>
|
||||||
<template #thanatopractitioner-new-action>
|
<template #thanatopractitioner-new-action>
|
||||||
<add-button text="Ajouter" @click="goToThanatopractitioner" />
|
<add-button text="Ajouter" @click="goToAdd" />
|
||||||
</template>
|
</template>
|
||||||
<template #select-filter>
|
<template #select-filter>
|
||||||
<filter-table />
|
<filter-table />
|
||||||
@ -16,16 +16,15 @@
|
|||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
@view="goToDetails"
|
@view="goToDetails"
|
||||||
@delete="deleteThanatopractitioner"
|
@delete="deleteThanatopractitioner"
|
||||||
@changePage="handleChangePage"
|
@change-page="$emit('change-page', $event)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</thanatopractitioner-template>
|
</thanatopractitioner-template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ThanatopractitionerTemplate from "@/components/templates/CRM/ThanatopractitionerTemplate.vue";
|
import ThanatopractitionerTemplate from "@/components/templates/CRM/ThanatopractitionerTemplate.vue";
|
||||||
import ThanatopractitionerTable from "@/components/molecules/Thanatopractitioners/ThanatopractitionerTable.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 FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||||
import { defineProps, defineEmits } from "vue";
|
import { defineProps, defineEmits } from "vue";
|
||||||
@ -39,7 +38,7 @@ const emit = defineEmits([
|
|||||||
"changePage",
|
"changePage",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const props = defineProps({
|
defineProps({
|
||||||
thanatopractitionerData: {
|
thanatopractitionerData: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: [],
|
default: [],
|
||||||
@ -50,43 +49,26 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
pagination: {
|
pagination: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: () => ({
|
||||||
|
current_page: 1,
|
||||||
|
per_page: 10,
|
||||||
|
total: 0,
|
||||||
|
last_page: 1,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const goToThanatopractitioner = () => {
|
const goToAdd = () => {
|
||||||
router.push({
|
router.push({
|
||||||
name: "Creation thanatopractitioner",
|
name: "Creation thanatopractitioner",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToDetails = (thanatopractitionerId) => {
|
const goToDetails = (thanatopractitioner) => {
|
||||||
emit("pushDetails", thanatopractitionerId);
|
emit("pushDetails", thanatopractitioner);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteThanatopractitioner = (thanatopractitionerId) => {
|
const deleteThanatopractitioner = (thanatopractitioner) => {
|
||||||
console.log(
|
emit("deleteThanatopractitioner", thanatopractitioner);
|
||||||
"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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* Component-specific styles */
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -115,13 +115,6 @@
|
|||||||
<!-- Name Column (from employee relation) -->
|
<!-- Name Column (from employee relation) -->
|
||||||
<td class="font-weight-bold">
|
<td class="font-weight-bold">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<soft-avatar
|
|
||||||
:img="getRandomAvatar()"
|
|
||||||
size="xs"
|
|
||||||
class="me-2"
|
|
||||||
alt="user image"
|
|
||||||
circular
|
|
||||||
/>
|
|
||||||
<div>
|
<div>
|
||||||
<span>{{
|
<span>{{
|
||||||
thanatopractitioner.employee?.full_name ||
|
thanatopractitioner.employee?.full_name ||
|
||||||
@ -273,7 +266,7 @@
|
|||||||
>
|
>
|
||||||
<div class="text-sm text-muted">
|
<div class="text-sm text-muted">
|
||||||
Affichage de {{ pagination.from }} à {{ pagination.to }} sur
|
Affichage de {{ pagination.from }} à {{ pagination.to }} sur
|
||||||
{{ pagination.total }} thanatopractitioners
|
{{ pagination.total }} thanatopracteurs
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<!-- Previous Button -->
|
<!-- Previous Button -->
|
||||||
@ -325,9 +318,9 @@
|
|||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<i class="fas fa-user-md fa-3x text-muted"></i>
|
<i class="fas fa-user-md fa-3x text-muted"></i>
|
||||||
</div>
|
</div>
|
||||||
<h5 class="empty-title">Aucun thanatopractitioner trouvé</h5>
|
<h5 class="empty-title">Aucun thanatopracteur trouvé</h5>
|
||||||
<p class="empty-text text-muted">
|
<p class="empty-text text-muted">
|
||||||
Aucun thanatopractitioner à afficher pour le moment.
|
Aucun thanatopracteur à afficher pour le moment.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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) => {
|
const formatDate = (dateString) => {
|
||||||
if (!dateString) return "N/A";
|
if (!dateString) return "N/A";
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
@ -399,23 +386,16 @@ const isAuthorizationValid = (expiryDate) => {
|
|||||||
|
|
||||||
// Direct button handlers
|
// Direct button handlers
|
||||||
const handleView = (thanatopractitionerId) => {
|
const handleView = (thanatopractitionerId) => {
|
||||||
console.log("Direct view button clicked for ID:", thanatopractitionerId);
|
|
||||||
emit("view", thanatopractitionerId);
|
emit("view", thanatopractitionerId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (thanatopractitionerId) => {
|
const handleDelete = (thanatopractitionerId) => {
|
||||||
console.log("Direct delete button clicked for ID:", thanatopractitionerId);
|
|
||||||
emit("delete", thanatopractitionerId);
|
emit("delete", thanatopractitionerId);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pagination methods
|
// Pagination methods
|
||||||
const changePage = (page) => {
|
const changePage = (page) => {
|
||||||
console.log("changePage called in ThanatopractitionerTable with page:", page);
|
|
||||||
if (page >= 1 && page <= props.pagination.last_page) {
|
if (page >= 1 && page <= props.pagination.last_page) {
|
||||||
console.log(
|
|
||||||
"Emitting changePage event from ThanatopractitionerTable:",
|
|
||||||
page
|
|
||||||
);
|
|
||||||
emit("changePage", page);
|
emit("changePage", page);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -458,32 +438,6 @@ const getVisiblePages = () => {
|
|||||||
|
|
||||||
return pages;
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
205
thanasoft-front/src/components/molecules/thanat
Normal file
205
thanasoft-front/src/components/molecules/thanat
Normal 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>
|
||||||
76
thanasoft-front/src/components/molecules/thanatopract
Normal file
76
thanasoft-front/src/components/molecules/thanatopract
Normal 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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
@ -1,24 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-fluid py-4">
|
<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="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card mt-4">
|
<div class="card">
|
||||||
<slot name="thanatopractitioner-table"></slot>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script></script>
|
<script setup>
|
||||||
|
// Template only component
|
||||||
|
</script>
|
||||||
|
|||||||
@ -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>
|
||||||
600
thanasoft-front/src/examples/BotMessageConfigurator.vue
Normal file
600
thanasoft-front/src/examples/BotMessageConfigurator.vue
Normal 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>
|
||||||
@ -1,65 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<footer class="py-3 footer">
|
<footer class="py-3 footer"></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>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -87,9 +87,11 @@
|
|||||||
<a
|
<a
|
||||||
class="p-0 nav-link"
|
class="p-0 nav-link"
|
||||||
:class="textWhite ? textWhite : 'text-body'"
|
: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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
@ -227,11 +229,13 @@
|
|||||||
import Breadcrumbs from "../Breadcrumbs.vue";
|
import Breadcrumbs from "../Breadcrumbs.vue";
|
||||||
import { mapMutations, mapActions, mapState } from "vuex";
|
import { mapMutations, mapActions, mapState } from "vuex";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import BotMessageConfigurator from "@/examples/BotMessageConfigurator.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Navbar",
|
name: "Navbar",
|
||||||
components: {
|
components: {
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
BotMessageConfigurator,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
minNav: {
|
minNav: {
|
||||||
@ -246,6 +250,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showMenu: false,
|
showMenu: false,
|
||||||
|
showMessenger: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -270,6 +275,10 @@ export default {
|
|||||||
this.navbarMinimize();
|
this.navbarMinimize();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleMessenger() {
|
||||||
|
this.showMessenger = !this.showMessenger;
|
||||||
|
},
|
||||||
|
|
||||||
async handleLogout() {
|
async handleLogout() {
|
||||||
try {
|
try {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|||||||
@ -561,14 +561,14 @@ const routes = [
|
|||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/pages/Thanatopractitioners/AddThanatopractitioner.vue"),
|
import("@/views/pages/Thanatopractitioners/AddThanatopractitioner.vue"),
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// path: "/employes/thanatopracteurs/:id",
|
path: "/employes/thanatopracteurs/:id",
|
||||||
// name: "Thanatopractitioner details",
|
name: "Thanatopractitioner details",
|
||||||
// component: () =>
|
component: () =>
|
||||||
// import(
|
import(
|
||||||
// "@/views/pages/Thanatopractitioners/ThanatopractitionerDetails.vue"
|
"@/views/pages/Thanatopractitioners/ThanatopractitionerDetails.vue"
|
||||||
// ),
|
),
|
||||||
// },
|
},
|
||||||
// Paramétrage
|
// Paramétrage
|
||||||
{
|
{
|
||||||
path: "/parametrage/droits",
|
path: "/parametrage/droits",
|
||||||
|
|||||||
@ -12,6 +12,14 @@ export interface Thanatopractitioner {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_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
|
// Relations
|
||||||
employee?: {
|
employee?: {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import ThanatopractitionerService from "@/services/thanatopractitioner";
|
import ThanatopractitionerService, {
|
||||||
|
|
||||||
import type {
|
|
||||||
Thanatopractitioner,
|
Thanatopractitioner,
|
||||||
CreateThanatopractitionerPayload,
|
CreateThanatopractitionerPayload,
|
||||||
UpdateThanatopractitionerPayload,
|
UpdateThanatopractitionerPayload,
|
||||||
ThanatopractitionerListResponse,
|
|
||||||
NestedThanatopractitionerListResponse,
|
|
||||||
} from "@/services/thanatopractitioner";
|
} from "@/services/thanatopractitioner";
|
||||||
|
import { useNotificationStore } from "./notification";
|
||||||
|
|
||||||
export const useThanatopractitionerStore = defineStore(
|
export const useThanatopractitionerStore = defineStore(
|
||||||
"thanatopractitioner",
|
"thanatopractitioner",
|
||||||
@ -17,373 +14,361 @@ export const useThanatopractitionerStore = defineStore(
|
|||||||
const thanatopractitioners = ref<Thanatopractitioner[]>([]);
|
const thanatopractitioners = ref<Thanatopractitioner[]>([]);
|
||||||
const currentThanatopractitioner = ref<Thanatopractitioner | null>(null);
|
const currentThanatopractitioner = ref<Thanatopractitioner | null>(null);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const error = ref<string | null>(null);
|
const isLoading = ref(false);
|
||||||
const searchResults = ref<Thanatopractitioner[]>([]);
|
const currentPage = ref(1);
|
||||||
|
const perPage = ref(10);
|
||||||
// Pagination state
|
const total = ref(0);
|
||||||
const pagination = ref({
|
const lastPage = ref(1);
|
||||||
current_page: 1,
|
const from = ref(0);
|
||||||
last_page: 1,
|
const to = ref(0);
|
||||||
per_page: 10,
|
const search = ref("");
|
||||||
total: 0,
|
const activeFilter = ref<boolean | undefined>(undefined);
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
const allThanatopractitioners = computed(() => thanatopractitioners.value);
|
const getPagination = computed(() => ({
|
||||||
const activeThanatopractitioners = computed(() =>
|
current_page: currentPage.value,
|
||||||
thanatopractitioners.value.filter(
|
last_page: lastPage.value,
|
||||||
(thanatopractitioner: Thanatopractitioner) => thanatopractitioner.active
|
per_page: perPage.value,
|
||||||
)
|
total: total.value,
|
||||||
);
|
from: from.value,
|
||||||
const inactiveThanatopractitioners = computed(() =>
|
to: to.value,
|
||||||
thanatopractitioners.value.filter(
|
}));
|
||||||
(thanatopractitioner: Thanatopractitioner) =>
|
const totalPages = computed(() => lastPage.value);
|
||||||
!thanatopractitioner.active
|
const hasNextPage = computed(() => currentPage.value < lastPage.value);
|
||||||
)
|
const hasPreviousPage = computed(() => currentPage.value > 1);
|
||||||
);
|
const isEmpty = computed(() => thanatopractitioners.value.length === 0);
|
||||||
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);
|
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
const setLoading = (isLoading: boolean) => {
|
const fetchThanatopractitioners = async (
|
||||||
loading.value = isLoading;
|
params: {
|
||||||
};
|
page?: number;
|
||||||
|
per_page?: number;
|
||||||
const setError = (err: string | null) => {
|
search?: string;
|
||||||
error.value = err;
|
active?: boolean;
|
||||||
};
|
} = {}
|
||||||
|
|
||||||
const clearError = () => {
|
|
||||||
error.value = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setThanatopractitioners = (
|
|
||||||
newThanatopractitioners: Thanatopractitioner[]
|
|
||||||
) => {
|
) => {
|
||||||
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 {
|
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(
|
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);
|
console.log("Full response:", JSON.stringify(response, null, 2));
|
||||||
setPagination(response.data.pagination);
|
|
||||||
|
// 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;
|
return response;
|
||||||
} catch (err: any) {
|
} catch (error) {
|
||||||
const errorMessage =
|
console.error("Error fetching thanatopractitioners:", error);
|
||||||
err.response?.data?.message ||
|
throw error;
|
||||||
err.message ||
|
|
||||||
"Failed to fetch thanatopractitioners";
|
|
||||||
setError(errorMessage);
|
|
||||||
throw err;
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch a single thanatopractitioner by ID
|
|
||||||
*/
|
|
||||||
const fetchThanatopractitioner = async (id: number) => {
|
const fetchThanatopractitioner = async (id: number) => {
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
const response = await ThanatopractitionerService.getThanatopractitioner(
|
const response = await ThanatopractitionerService.getThanatopractitioner(
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
setCurrentThanatopractitioner(response.data);
|
|
||||||
return response.data;
|
// Safely handle the response with additional mapping
|
||||||
} catch (err: any) {
|
const data = response.data;
|
||||||
const errorMessage =
|
currentThanatopractitioner.value = {
|
||||||
err.response?.data?.message ||
|
...data,
|
||||||
err.message ||
|
...(data.employee || {}),
|
||||||
"Failed to fetch thanatopractitioner";
|
full_name: data.employee?.full_name || "N/A",
|
||||||
setError(errorMessage);
|
job_title: data.employee?.job_title || "N/A",
|
||||||
throw err;
|
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 {
|
} 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 (
|
const createThanatopractitioner = async (
|
||||||
payload: CreateThanatopractitionerPayload
|
payload: CreateThanatopractitionerPayload
|
||||||
) => {
|
) => {
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
loading.value = true;
|
||||||
const response = await ThanatopractitionerService.createThanatopractitioner(
|
const response = await ThanatopractitionerService.createThanatopractitioner(
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
// Add the new thanatopractitioner to the list
|
|
||||||
thanatopractitioners.value.push(response.data);
|
// Add the new thanatopractitioner to the list with safe mapping
|
||||||
setCurrentThanatopractitioner(response.data);
|
const newThanatopractitioner = {
|
||||||
return response.data;
|
...response.data,
|
||||||
} catch (err: any) {
|
...(response.data.employee || {}),
|
||||||
const errorMessage =
|
full_name: response.data.employee?.full_name || "N/A",
|
||||||
err.response?.data?.message ||
|
job_title: response.data.employee?.job_title || "N/A",
|
||||||
err.message ||
|
first_name: response.data.employee?.first_name || "N/A",
|
||||||
"Failed to create thanatopractitioner";
|
last_name: response.data.employee?.last_name || "N/A",
|
||||||
setError(errorMessage);
|
email: response.data.employee?.email || "N/A",
|
||||||
throw err;
|
};
|
||||||
|
|
||||||
|
thanatopractitioners.value.unshift(newThanatopractitioner);
|
||||||
|
total.value += 1;
|
||||||
|
|
||||||
|
return newThanatopractitioner;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating thanatopractitioner:", error);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing thanatopractitioner
|
|
||||||
*/
|
|
||||||
const updateThanatopractitioner = async (
|
const updateThanatopractitioner = async (
|
||||||
payload: UpdateThanatopractitionerPayload
|
payload: UpdateThanatopractitionerPayload
|
||||||
) => {
|
) => {
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(payload);
|
loading.value = true;
|
||||||
const response = await ThanatopractitionerService.updateThanatopractitioner(
|
const response = await ThanatopractitionerService.updateThanatopractitioner(
|
||||||
payload
|
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
|
// Update in the thanatopractitioners list
|
||||||
const index = thanatopractitioners.value.findIndex(
|
const index = thanatopractitioners.value.findIndex(
|
||||||
(thanatopractitioner: Thanatopractitioner) =>
|
(t) => t.id === payload.id
|
||||||
thanatopractitioner.id === updatedThanatopractitioner.id
|
|
||||||
);
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
thanatopractitioners.value[index] = updatedThanatopractitioner;
|
thanatopractitioners.value[index] = updatedThanatopractitioner;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update current thanatopractitioner if it's the one being edited
|
// Update current thanatopractitioner if it's the same one
|
||||||
if (
|
if (currentThanatopractitioner.value?.id === payload.id) {
|
||||||
currentThanatopractitioner.value &&
|
currentThanatopractitioner.value = updatedThanatopractitioner;
|
||||||
currentThanatopractitioner.value.id === updatedThanatopractitioner.id
|
|
||||||
) {
|
|
||||||
setCurrentThanatopractitioner(updatedThanatopractitioner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedThanatopractitioner;
|
return updatedThanatopractitioner;
|
||||||
} catch (err: any) {
|
} catch (error) {
|
||||||
const errorMessage =
|
console.error("Error updating thanatopractitioner:", error);
|
||||||
err.response?.data?.message ||
|
throw error;
|
||||||
err.message ||
|
|
||||||
"Failed to update thanatopractitioner";
|
|
||||||
setError(errorMessage);
|
|
||||||
throw err;
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a thanatopractitioner
|
|
||||||
*/
|
|
||||||
const deleteThanatopractitioner = async (id: number) => {
|
const deleteThanatopractitioner = async (id: number) => {
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await ThanatopractitionerService.deleteThanatopractitioner(
|
loading.value = true;
|
||||||
id
|
await ThanatopractitionerService.deleteThanatopractitioner(id);
|
||||||
);
|
|
||||||
|
|
||||||
// Remove from the thanatopractitioners list
|
// Remove from the thanatopractitioners list
|
||||||
thanatopractitioners.value = thanatopractitioners.value.filter(
|
thanatopractitioners.value = thanatopractitioners.value.filter(
|
||||||
(thanatopractitioner: Thanatopractitioner) =>
|
(t) => t.id !== id
|
||||||
thanatopractitioner.id !== id
|
|
||||||
);
|
);
|
||||||
|
total.value -= 1;
|
||||||
|
|
||||||
// Clear current thanatopractitioner if it's the one being deleted
|
// Clear current thanatopractitioner if it's the deleted one
|
||||||
if (
|
if (currentThanatopractitioner.value?.id === id) {
|
||||||
currentThanatopractitioner.value &&
|
currentThanatopractitioner.value = null;
|
||||||
currentThanatopractitioner.value.id === id
|
|
||||||
) {
|
|
||||||
setCurrentThanatopractitioner(null);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
return response;
|
console.error("Error deleting thanatopractitioner:", error);
|
||||||
} catch (err: any) {
|
throw error;
|
||||||
const errorMessage =
|
|
||||||
err.response?.data?.message ||
|
|
||||||
err.message ||
|
|
||||||
"Failed to delete thanatopractitioner";
|
|
||||||
setError(errorMessage);
|
|
||||||
throw err;
|
|
||||||
} finally {
|
} 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 (
|
const toggleThanatopractitionerStatus = async (
|
||||||
id: number,
|
id: number,
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
) => {
|
) => {
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await ThanatopractitionerService.toggleThanatopractitionerStatus(
|
const response = await ThanatopractitionerService.toggleThanatopractitionerStatus(
|
||||||
id,
|
id,
|
||||||
isActive
|
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
|
// Update in the thanatopractitioners list
|
||||||
const index = thanatopractitioners.value.findIndex(
|
const index = thanatopractitioners.value.findIndex((t) => t.id === id);
|
||||||
(thanatopractitioner: Thanatopractitioner) =>
|
|
||||||
thanatopractitioner.id === id
|
|
||||||
);
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
thanatopractitioners.value[index] = updatedThanatopractitioner;
|
thanatopractitioners.value[index] = updatedThanatopractitioner;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update current thanatopractitioner if it's the one being toggled
|
// Update current thanatopractitioner if it's the same one
|
||||||
if (
|
if (currentThanatopractitioner.value?.id === id) {
|
||||||
currentThanatopractitioner.value &&
|
currentThanatopractitioner.value = updatedThanatopractitioner;
|
||||||
currentThanatopractitioner.value.id === id
|
|
||||||
) {
|
|
||||||
setCurrentThanatopractitioner(updatedThanatopractitioner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedThanatopractitioner;
|
return updatedThanatopractitioner;
|
||||||
} catch (err: any) {
|
} catch (error) {
|
||||||
const errorMessage =
|
console.error("Error toggling thanatopractitioner status:", error);
|
||||||
err.response?.data?.message ||
|
throw error;
|
||||||
err.message ||
|
|
||||||
"Failed to toggle thanatopractitioner status";
|
|
||||||
setError(errorMessage);
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const searchThanatopractitioners = async (query: string) => {
|
||||||
* Get thanatopractitioner statistics
|
try {
|
||||||
*/
|
loading.value = true;
|
||||||
const fetchStatistics = async () => {
|
const results = await ThanatopractitionerService.searchThanatopractitioners(
|
||||||
setLoading(true);
|
query
|
||||||
setError(null);
|
);
|
||||||
|
return results;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error searching thanatopractitioners:", error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatistics = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await ThanatopractitionerService.getStatistics();
|
const response = await ThanatopractitionerService.getStatistics();
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (error) {
|
||||||
const errorMessage =
|
console.error("Error fetching thanatopractitioner statistics:", error);
|
||||||
err.response?.data?.message ||
|
throw error;
|
||||||
err.message ||
|
|
||||||
"Failed to fetch statistics";
|
|
||||||
setError(errorMessage);
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const setCurrentPage = (page: number) => {
|
||||||
* Clear current thanatopractitioner
|
currentPage.value = page;
|
||||||
*/
|
|
||||||
const clearCurrentThanatopractitioner = () => {
|
|
||||||
setCurrentThanatopractitioner(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const setPerPage = (newPerPage: number) => {
|
||||||
* Clear all state
|
perPage.value = newPerPage;
|
||||||
*/
|
currentPage.value = 1; // Reset to first page
|
||||||
const clearStore = () => {
|
};
|
||||||
thanatopractitioners.value = [];
|
|
||||||
|
const clearCurrentThanatopractitioner = () => {
|
||||||
currentThanatopractitioner.value = null;
|
currentThanatopractitioner.value = null;
|
||||||
error.value = null;
|
};
|
||||||
pagination.value = {
|
|
||||||
current_page: 1,
|
const resetFilters = () => {
|
||||||
last_page: 1,
|
search.value = "";
|
||||||
per_page: 10,
|
activeFilter.value = undefined;
|
||||||
total: 0,
|
currentPage.value = 1;
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -391,31 +376,42 @@ export const useThanatopractitionerStore = defineStore(
|
|||||||
thanatopractitioners,
|
thanatopractitioners,
|
||||||
currentThanatopractitioner,
|
currentThanatopractitioner,
|
||||||
loading,
|
loading,
|
||||||
error,
|
isLoading,
|
||||||
searchResults,
|
currentPage,
|
||||||
|
perPage,
|
||||||
|
total,
|
||||||
|
lastPage,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
search,
|
||||||
|
activeFilter,
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
allThanatopractitioners,
|
|
||||||
activeThanatopractitioners,
|
|
||||||
inactiveThanatopractitioners,
|
|
||||||
isLoading,
|
|
||||||
hasError,
|
|
||||||
getError,
|
|
||||||
getThanatopractitionerById,
|
|
||||||
getPagination,
|
getPagination,
|
||||||
|
totalPages,
|
||||||
|
hasNextPage,
|
||||||
|
hasPreviousPage,
|
||||||
|
isEmpty,
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
fetchThanatopractitioners,
|
fetchThanatopractitioners,
|
||||||
fetchThanatopractitioner,
|
fetchThanatopractitioner,
|
||||||
|
fetchThanatopractitionerById,
|
||||||
|
fetchThanatopractitionerDocuments,
|
||||||
|
addThanatopractitionerDocument,
|
||||||
|
updateThanatopractitionerDocument,
|
||||||
createThanatopractitioner,
|
createThanatopractitioner,
|
||||||
updateThanatopractitioner,
|
updateThanatopractitioner,
|
||||||
deleteThanatopractitioner,
|
deleteThanatopractitioner,
|
||||||
searchThanatopractitioners,
|
|
||||||
toggleThanatopractitionerStatus,
|
toggleThanatopractitionerStatus,
|
||||||
fetchStatistics,
|
searchThanatopractitioners,
|
||||||
|
getStatistics,
|
||||||
|
setCurrentPage,
|
||||||
|
setPerPage,
|
||||||
clearCurrentThanatopractitioner,
|
clearCurrentThanatopractitioner,
|
||||||
clearStore,
|
resetFilters,
|
||||||
clearError,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export default useThanatopractitionerStore;
|
||||||
|
|||||||
@ -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>
|
||||||
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<thanatopractitioner-presentation
|
<thanatopractitioner-presentation
|
||||||
:thanatopractitioner-data="thanatopractitionerStore.thanatopractitioners"
|
:thanatopractitioner-data="thanatopractitioners"
|
||||||
:loading-data="thanatopractitionerStore.loading"
|
:loading-data="loading"
|
||||||
:pagination="thanatopractitionerStore.getPagination"
|
:pagination="pagination"
|
||||||
@push-details="goDetails"
|
@push-details="goDetails"
|
||||||
@delete-thanatopractitioner="confirmDeleteThanatopractitioner"
|
@delete-thanatopractitioner="confirmDeleteThanatopractitioner"
|
||||||
@change-page="changePage"
|
@change-page="changePage"
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, onMounted } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
import ThanatopractitionerPresentation from "@/components/Organism/Thanatopractitioner/ThanatopractitionerPresentation.vue";
|
import ThanatopractitionerPresentation from "@/components/Organism/Thanatopractitioner/ThanatopractitionerPresentation.vue";
|
||||||
import ConfirmModal from "@/components/molecules/common/ConfirmModal.vue";
|
import ConfirmModal from "@/components/molecules/common/ConfirmModal.vue";
|
||||||
import { useThanatopractitionerStore } from "@/stores/thanatopractitionerStore";
|
import { useThanatopractitionerStore } from "@/stores/thanatopractitionerStore";
|
||||||
@ -36,6 +36,15 @@ const thanatopractitionerStore = useThanatopractitionerStore();
|
|||||||
const notificationStore = useNotificationStore();
|
const notificationStore = useNotificationStore();
|
||||||
const router = useRouter();
|
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
|
// Confirm modal state
|
||||||
const confirmModal = reactive({
|
const confirmModal = reactive({
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
@ -52,94 +61,91 @@ const confirmModal = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
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) => {
|
const goDetails = (id) => {
|
||||||
router.push({
|
router.push({
|
||||||
name: "Thanatopractitioner details",
|
name: "Thanatopractitioner details",
|
||||||
params: {
|
params: { id: id },
|
||||||
id: id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDeleteThanatopractitioner = (thanatopractitionerId) => {
|
const confirmDeleteThanatopractitioner = (thanatopractitionerId) => {
|
||||||
console.log(
|
const thanatopractitioner = thanatopractitioners.value.find(
|
||||||
"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(
|
|
||||||
(prac) => prac.id === thanatopractitionerId
|
(prac) => prac.id === thanatopractitionerId
|
||||||
);
|
);
|
||||||
console.log("Found thanatopractitioner:", thanatopractitioner);
|
|
||||||
|
|
||||||
if (thanatopractitioner) {
|
if (thanatopractitioner) {
|
||||||
console.log("Showing confirmation modal");
|
|
||||||
confirmModal.isVisible = true;
|
confirmModal.isVisible = true;
|
||||||
confirmModal.thanatopractitionerId = thanatopractitionerId;
|
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}" ?`;
|
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 handleConfirmDelete = async () => {
|
||||||
const thanatopractitionerId = confirmModal.thanatopractitionerId;
|
const thanatopractitionerId = confirmModal.thanatopractitionerId;
|
||||||
const thanatopractitionerName = confirmModal.thanatopractitionerName;
|
const thanatopractitionerName = confirmModal.thanatopractitionerName;
|
||||||
console.log("Test");
|
|
||||||
try {
|
try {
|
||||||
confirmModal.isLoading = true;
|
confirmModal.isLoading = true;
|
||||||
await thanatopractitionerStore.deleteThanatopractitioner(
|
await thanatopractitionerStore.deleteThanatopractitioner(
|
||||||
thanatopractitionerId
|
thanatopractitionerId
|
||||||
);
|
);
|
||||||
|
|
||||||
notificationStore.success(
|
notificationStore.success(
|
||||||
"Thanatopractitioner supprimé",
|
"Thanatopractitioner supprimé",
|
||||||
`Le thanatopractitioner ${thanatopractitionerName} a été supprimé avec succès.`
|
`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) {
|
} catch (error) {
|
||||||
console.error("Error deleting thanatopractitioner:", error);
|
console.error("Error deleting thanatopractitioner:", error);
|
||||||
notificationStore.error(
|
notificationStore.error(
|
||||||
"Erreur de suppression",
|
"Erreur de suppression",
|
||||||
"Une erreur est survenue lors de la suppression du thanatopractitioner."
|
"Une erreur est survenue lors de la suppression du thanatopractitioner."
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
closeConfirmModal();
|
closeConfirmModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -156,12 +162,10 @@ const closeConfirmModal = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const changePage = async (page) => {
|
const changePage = async (page) => {
|
||||||
console.log("changePage called in Thanatopractitioners.vue with page:", page);
|
|
||||||
try {
|
try {
|
||||||
console.log("Fetching thanatopractitioners with page:", page);
|
await fetchThanatopractitioners({
|
||||||
await thanatopractitionerStore.fetchThanatopractitioners({
|
|
||||||
page,
|
page,
|
||||||
per_page: 10,
|
per_page: pagination.value.per_page,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error changing page:", error);
|
console.error("Error changing page:", error);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user