thanato
This commit is contained in:
parent
8d1d65e27b
commit
bbf60fb380
426
thanas
426
thanas
@ -1,370 +1,70 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, computed } from "vue";
|
||||
import EmployeeService from "@/services/employee";
|
||||
<template>
|
||||
<thanatopractitioner-template>
|
||||
<template #thanatopractitioner-new-action>
|
||||
<add-button text="Ajouter" @click="goToAdd" />
|
||||
</template>
|
||||
<template #select-filter>
|
||||
<filter-table />
|
||||
</template>
|
||||
<template #thanatopractitioner-other-action>
|
||||
<table-action />
|
||||
</template>
|
||||
<template #thanatopractitioner-table>
|
||||
<thanatopractitioner-table
|
||||
:data="thanatopractitionerData"
|
||||
:loading="loadingData"
|
||||
:pagination="pagination"
|
||||
@view="goToDetails"
|
||||
@delete="deleteThanatopractitioner"
|
||||
@change-page="$emit('change-page', $event)"
|
||||
/>
|
||||
</template>
|
||||
</thanatopractitioner-template>
|
||||
</template>
|
||||
<script setup>
|
||||
import ThanatopractitionerTemplate from "@/components/templates/CRM/ThanatopractitionerTemplate.vue";
|
||||
import ThanatopractitionerTable from "@/components/molecules/Thanatopractitioners/ThanatopractitionerTable.vue";
|
||||
import addButton from "@/components/molecules/new-button/addButton.vue";
|
||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import type {
|
||||
Employee,
|
||||
CreateEmployeePayload,
|
||||
UpdateEmployeePayload,
|
||||
EmployeeListResponse,
|
||||
} from "@/services/employee";
|
||||
const router = useRouter();
|
||||
|
||||
export const useEmployeeStore = defineStore("employee", () => {
|
||||
// State
|
||||
const employees = ref<Employee[]>([]);
|
||||
const currentEmployee = ref<Employee | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const searchResults = ref<Employee[]>([]);
|
||||
const emit = defineEmits(["pushDetails", "deleteThanatopractitioner", "changePage"]);
|
||||
|
||||
// Pagination state
|
||||
const pagination = ref({
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
from: 0,
|
||||
to: 0,
|
||||
});
|
||||
|
||||
// Getters
|
||||
const allEmployees = computed(() => employees.value);
|
||||
const activeEmployees = computed(() =>
|
||||
employees.value.filter((employee) => employee.is_active)
|
||||
);
|
||||
const inactiveEmployees = computed(() =>
|
||||
employees.value.filter((employee) => !employee.is_active)
|
||||
);
|
||||
const isLoading = computed(() => loading.value);
|
||||
const hasError = computed(() => error.value !== null);
|
||||
const getError = computed(() => error.value);
|
||||
const getEmployeeById = computed(
|
||||
() => (id: number) => employees.value.find((employee) => employee.id === id)
|
||||
);
|
||||
const getPagination = computed(() => pagination.value);
|
||||
|
||||
// Actions
|
||||
const setLoading = (isLoading: boolean) => {
|
||||
loading.value = isLoading;
|
||||
};
|
||||
|
||||
const setError = (err: string | null) => {
|
||||
error.value = err;
|
||||
};
|
||||
|
||||
const clearError = () => {
|
||||
error.value = null;
|
||||
};
|
||||
|
||||
const setEmployees = (newEmployees: Employee[]) => {
|
||||
employees.value = newEmployees;
|
||||
};
|
||||
|
||||
const setCurrentEmployee = (employee: Employee | null) => {
|
||||
currentEmployee.value = employee;
|
||||
};
|
||||
|
||||
const setSearchEmployee = (searchEmployee: Employee[]) => {
|
||||
searchResults.value = searchEmployee;
|
||||
};
|
||||
|
||||
const setPagination = (meta: any) => {
|
||||
if (meta) {
|
||||
pagination.value = {
|
||||
current_page: meta.current_page || 1,
|
||||
last_page: meta.last_page || 1,
|
||||
per_page: meta.per_page || 10,
|
||||
total: meta.total || 0,
|
||||
from: meta.from || 0,
|
||||
to: meta.to || 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all employees with optional pagination and filters
|
||||
*/
|
||||
const fetchEmployees = async (params?: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
active?: boolean;
|
||||
sort_by?: string;
|
||||
sort_direction?: string;
|
||||
}) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.getAllEmployees(params);
|
||||
setEmployees(response.data);
|
||||
setPagination(response.pagination);
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch employees";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch a single employee by ID
|
||||
*/
|
||||
const fetchEmployee = async (id: number) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.getEmployee(id);
|
||||
setCurrentEmployee(response.data);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new employee
|
||||
*/
|
||||
const createEmployee = async (payload: CreateEmployeePayload) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.createEmployee(payload);
|
||||
// Add the new employee to the list
|
||||
employees.value.push(response.data);
|
||||
setCurrentEmployee(response.data);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to create employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update an existing employee
|
||||
*/
|
||||
const updateEmployee = async (payload: UpdateEmployeePayload) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log(payload);
|
||||
const response = await EmployeeService.updateEmployee(payload);
|
||||
const updatedEmployee = response.data;
|
||||
|
||||
// Update in the employees list
|
||||
const index = employees.value.findIndex(
|
||||
(employee) => employee.id === updatedEmployee.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
employees.value[index] = updatedEmployee;
|
||||
}
|
||||
|
||||
// Update current employee if it's the one being edited
|
||||
if (
|
||||
currentEmployee.value &&
|
||||
currentEmployee.value.id === updatedEmployee.id
|
||||
) {
|
||||
setCurrentEmployee(updatedEmployee);
|
||||
}
|
||||
|
||||
return updatedEmployee;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to update employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete an employee
|
||||
*/
|
||||
const deleteEmployee = async (id: number) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.deleteEmployee(id);
|
||||
|
||||
// Remove from the employees list
|
||||
employees.value = employees.value.filter(
|
||||
(employee) => employee.id !== id
|
||||
);
|
||||
|
||||
// Clear current employee if it's the one being deleted
|
||||
if (currentEmployee.value && currentEmployee.value.id === id) {
|
||||
setCurrentEmployee(null);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to delete employee";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Search employees
|
||||
*/
|
||||
const searchEmployees = async (query: string) => {
|
||||
setLoading(true);
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const results = await EmployeeService.searchEmployees(query);
|
||||
setSearchEmployee(results);
|
||||
return results;
|
||||
} catch (err) {
|
||||
error.value = "Erreur lors de la recherche des employés";
|
||||
console.error("Error searching employees:", err);
|
||||
setSearchEmployee([]);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle employee active status
|
||||
*/
|
||||
const toggleEmployeeStatus = async (id: number, isActive: boolean) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.toggleEmployeeStatus(id, isActive);
|
||||
const updatedEmployee = response.data;
|
||||
|
||||
// Update in the employees list
|
||||
const index = employees.value.findIndex((employee) => employee.id === id);
|
||||
if (index !== -1) {
|
||||
employees.value[index] = updatedEmployee;
|
||||
}
|
||||
|
||||
// Update current employee if it's the one being toggled
|
||||
if (currentEmployee.value && currentEmployee.value.id === id) {
|
||||
setCurrentEmployee(updatedEmployee);
|
||||
}
|
||||
|
||||
return updatedEmployee;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to toggle employee status";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get employee statistics
|
||||
*/
|
||||
const fetchStatistics = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await EmployeeService.getStatistics();
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch statistics";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear current employee
|
||||
*/
|
||||
const clearCurrentEmployee = () => {
|
||||
setCurrentEmployee(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all state
|
||||
*/
|
||||
const clearStore = () => {
|
||||
employees.value = [];
|
||||
currentEmployee.value = null;
|
||||
error.value = null;
|
||||
pagination.value = {
|
||||
defineProps({
|
||||
thanatopractitionerData: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
loadingData: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
from: 0,
|
||||
to: 0,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
employees,
|
||||
currentEmployee,
|
||||
loading,
|
||||
error,
|
||||
searchResults,
|
||||
|
||||
// Getters
|
||||
allEmployees,
|
||||
activeEmployees,
|
||||
inactiveEmployees,
|
||||
isLoading,
|
||||
hasError,
|
||||
getError,
|
||||
getEmployeeById,
|
||||
getPagination,
|
||||
|
||||
// Actions
|
||||
fetchEmployees,
|
||||
fetchEmployee,
|
||||
createEmployee,
|
||||
updateEmployee,
|
||||
deleteEmployee,
|
||||
searchEmployees,
|
||||
toggleEmployeeStatus,
|
||||
fetchStatistics,
|
||||
clearCurrentEmployee,
|
||||
clearStore,
|
||||
clearError,
|
||||
};
|
||||
last_page: 1,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const goToAdd = () => {
|
||||
router.push({
|
||||
name: "Creation thanatopractitioner",
|
||||
});
|
||||
};
|
||||
|
||||
const goToDetails = (thanatopractitioner) => {
|
||||
emit("pushDetails", thanatopractitioner);
|
||||
};
|
||||
|
||||
const deleteThanatopractitioner = (thanatopractitioner) => {
|
||||
emit("deleteThanatopractitioner", thanatopractitioner);
|
||||
};
|
||||
</script>
|
||||
|
||||
187
thanasoft
187
thanasoft
@ -1,141 +1,60 @@
|
||||
<template>
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center">
|
||||
<p class="mb-0 font-weight-bold text-lg">Gestion des Employés</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive p-0">
|
||||
<!-- Filter and Action Bar -->
|
||||
<div
|
||||
class="d-flex justify-content-between align-items-center mb-4 px-4"
|
||||
>
|
||||
<div class="d-flex align-items-center">
|
||||
<slot name="select-filter">
|
||||
<filter-table />
|
||||
</slot>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<slot name="employee-other-action">
|
||||
<table-action />
|
||||
</slot>
|
||||
<slot name="employee-new-action">
|
||||
<add-button text="Ajouter" @click="goToEmployee" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="employee-content">
|
||||
<slot name="employee-table">
|
||||
<!-- Default table slot - will be overridden by specific implementations -->
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<TabNavigationItem
|
||||
icon="fas fa-eye"
|
||||
label="Aperçu"
|
||||
:is-active="activeTab === 'overview'"
|
||||
spacing=""
|
||||
@click="$emit('change-tab', 'overview')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-info-circle"
|
||||
label="Informations"
|
||||
:is-active="activeTab === 'info'"
|
||||
@click="$emit('change-tab', 'info')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-calendar"
|
||||
label="Agenda"
|
||||
:is-active="activeTab === 'agenda'"
|
||||
@click="$emit('change-tab', 'agenda')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-info-circle"
|
||||
label="Activités récentes"
|
||||
:is-active="activeTab === 'activity'"
|
||||
@click="$emit('change-tab', 'activity')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-folder"
|
||||
label="Documents"
|
||||
:is-active="activeTab === 'documents'"
|
||||
:badge="documentsCount > 0 ? documentsCount : null"
|
||||
@click="$emit('change-tab', 'documents')"
|
||||
/>
|
||||
<TabNavigationItem
|
||||
icon="fas fa-sticky-note"
|
||||
label="Notes"
|
||||
:is-active="activeTab === 'notes'"
|
||||
@click="$emit('change-tab', 'notes')"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||
import addButton from "@/components/molecules/new-button/addButton.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import TabNavigationItem from "@/components/atoms/client/TabNavigationItem.vue";
|
||||
|
||||
const router = useRouter();
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
defineProps({
|
||||
activeTab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
documentsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const goToEmployee = () => {
|
||||
router.push({
|
||||
name: "Creation employé",
|
||||
});
|
||||
};
|
||||
defineEmits(["change-tab"]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container-fluid {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.employee-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.align-items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-content-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.font-weight-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #5e72e4 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container-fluid {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.d-flex.gap-2 {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -206,7 +206,7 @@ class ThanatopractitionerController extends Controller
|
||||
public function show(string $id): ThanatopractitionerResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->find($id);
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->findById((int) $id);
|
||||
|
||||
if (!$thanatopractitioner) {
|
||||
return response()->json([
|
||||
|
||||
@ -26,7 +26,8 @@
|
||||
/>
|
||||
<router-view />
|
||||
<app-footer v-show="showFooter" />
|
||||
<configurator
|
||||
|
||||
<BotMessageConfigurator
|
||||
:toggle="toggleConfigurator"
|
||||
:class="[showConfig ? 'show' : '', hideConfigButton ? 'd-none' : '']"
|
||||
/>
|
||||
@ -35,7 +36,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import Sidenav from "./examples/Sidenav";
|
||||
import Configurator from "@/examples/Configurator.vue";
|
||||
import BotMessageConfigurator from "./examples/BotMessageConfigurator.vue";
|
||||
import Navbar from "@/examples/Navbars/Navbar.vue";
|
||||
import AppFooter from "@/examples/Footer.vue";
|
||||
import NotificationContainer from "@/components/NotificationContainer.vue";
|
||||
@ -44,10 +45,10 @@ export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Sidenav,
|
||||
Configurator,
|
||||
Navbar,
|
||||
AppFooter,
|
||||
NotificationContainer,
|
||||
BotMessageConfigurator,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
||||
@ -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>
|
||||
<thanatopractitioner-template>
|
||||
<template #thanatopractitioner-new-action>
|
||||
<add-button text="Ajouter" @click="goToThanatopractitioner" />
|
||||
<add-button text="Ajouter" @click="goToAdd" />
|
||||
</template>
|
||||
<template #select-filter>
|
||||
<filter-table />
|
||||
@ -16,16 +16,15 @@
|
||||
:pagination="pagination"
|
||||
@view="goToDetails"
|
||||
@delete="deleteThanatopractitioner"
|
||||
@changePage="handleChangePage"
|
||||
@change-page="$emit('change-page', $event)"
|
||||
/>
|
||||
</template>
|
||||
</thanatopractitioner-template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ThanatopractitionerTemplate from "@/components/templates/CRM/ThanatopractitionerTemplate.vue";
|
||||
import ThanatopractitionerTable from "@/components/molecules/Thanatopractitioners/ThanatopractitionerTable.vue";
|
||||
import AddButton from "@/components/molecules/new-button/addButton.vue";
|
||||
import addButton from "@/components/molecules/new-button/addButton.vue";
|
||||
import FilterTable from "@/components/molecules/Tables/FilterTable.vue";
|
||||
import TableAction from "@/components/molecules/Tables/TableAction.vue";
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
@ -39,7 +38,7 @@ const emit = defineEmits([
|
||||
"changePage",
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
thanatopractitionerData: {
|
||||
type: Array,
|
||||
default: [],
|
||||
@ -50,43 +49,26 @@ const props = defineProps({
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: null,
|
||||
default: () => ({
|
||||
current_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
last_page: 1,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const goToThanatopractitioner = () => {
|
||||
const goToAdd = () => {
|
||||
router.push({
|
||||
name: "Creation thanatopractitioner",
|
||||
});
|
||||
};
|
||||
|
||||
const goToDetails = (thanatopractitionerId) => {
|
||||
emit("pushDetails", thanatopractitionerId);
|
||||
const goToDetails = (thanatopractitioner) => {
|
||||
emit("pushDetails", thanatopractitioner);
|
||||
};
|
||||
|
||||
const deleteThanatopractitioner = (thanatopractitionerId) => {
|
||||
console.log(
|
||||
"deleteThanatopractitioner called in ThanatopractitionerPresentation with ID:",
|
||||
thanatopractitionerId
|
||||
);
|
||||
emit("deleteThanatopractitioner", thanatopractitionerId);
|
||||
};
|
||||
|
||||
const handleChangePage = (page) => {
|
||||
console.log(
|
||||
"handleChangePage called in ThanatopractitionerPresentation with page:",
|
||||
page
|
||||
);
|
||||
if (page >= 1 && page <= (props.pagination?.last_page || 1)) {
|
||||
console.log(
|
||||
"Emitting changePage event from ThanatopractitionerPresentation:",
|
||||
page
|
||||
);
|
||||
emit("changePage", page);
|
||||
}
|
||||
const deleteThanatopractitioner = (thanatopractitioner) => {
|
||||
emit("deleteThanatopractitioner", thanatopractitioner);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Component-specific styles */
|
||||
</style>
|
||||
|
||||
@ -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) -->
|
||||
<td class="font-weight-bold">
|
||||
<div class="d-flex align-items-center">
|
||||
<soft-avatar
|
||||
:img="getRandomAvatar()"
|
||||
size="xs"
|
||||
class="me-2"
|
||||
alt="user image"
|
||||
circular
|
||||
/>
|
||||
<div>
|
||||
<span>{{
|
||||
thanatopractitioner.employee?.full_name ||
|
||||
@ -273,7 +266,7 @@
|
||||
>
|
||||
<div class="text-sm text-muted">
|
||||
Affichage de {{ pagination.from }} à {{ pagination.to }} sur
|
||||
{{ pagination.total }} thanatopractitioners
|
||||
{{ pagination.total }} thanatopracteurs
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<!-- Previous Button -->
|
||||
@ -325,9 +318,9 @@
|
||||
<div class="empty-icon">
|
||||
<i class="fas fa-user-md fa-3x text-muted"></i>
|
||||
</div>
|
||||
<h5 class="empty-title">Aucun thanatopractitioner trouvé</h5>
|
||||
<h5 class="empty-title">Aucun thanatopracteur trouvé</h5>
|
||||
<p class="empty-text text-muted">
|
||||
Aucun thanatopractitioner à afficher pour le moment.
|
||||
Aucun thanatopracteur à afficher pour le moment.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -378,12 +371,6 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
// Methods
|
||||
const getRandomAvatar = () => {
|
||||
const randomIndex = Math.floor(Math.random() * avatarImages.length);
|
||||
return avatarImages[randomIndex];
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return "N/A";
|
||||
const date = new Date(dateString);
|
||||
@ -399,23 +386,16 @@ const isAuthorizationValid = (expiryDate) => {
|
||||
|
||||
// Direct button handlers
|
||||
const handleView = (thanatopractitionerId) => {
|
||||
console.log("Direct view button clicked for ID:", thanatopractitionerId);
|
||||
emit("view", thanatopractitionerId);
|
||||
};
|
||||
|
||||
const handleDelete = (thanatopractitionerId) => {
|
||||
console.log("Direct delete button clicked for ID:", thanatopractitionerId);
|
||||
emit("delete", thanatopractitionerId);
|
||||
};
|
||||
|
||||
// Pagination methods
|
||||
const changePage = (page) => {
|
||||
console.log("changePage called in ThanatopractitionerTable with page:", page);
|
||||
if (page >= 1 && page <= props.pagination.last_page) {
|
||||
console.log(
|
||||
"Emitting changePage event from ThanatopractitionerTable:",
|
||||
page
|
||||
);
|
||||
emit("changePage", page);
|
||||
}
|
||||
};
|
||||
@ -458,32 +438,6 @@ const getVisiblePages = () => {
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
// Watch for data changes
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
if (!props.loading) {
|
||||
console.log("ThanatopractitionerTable: Data changed");
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
// Clean up any event listeners if needed
|
||||
});
|
||||
|
||||
// Initialize data
|
||||
onMounted(() => {
|
||||
if (!props.loading && props.data.length > 0) {
|
||||
console.log(
|
||||
"ThanatopractitionerTable: Component mounted with",
|
||||
props.data.length,
|
||||
"thanatopractitioners"
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
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>
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-sm-flex justify-content-between">
|
||||
<div>
|
||||
<slot name="thanatopractitioner-new-action"></slot>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="dropdown d-inline">
|
||||
<slot name="select-filter"></slot>
|
||||
</div>
|
||||
<slot name="thanatopractitioner-other-action"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card mt-4">
|
||||
<slot name="thanatopractitioner-table"></slot>
|
||||
<div class="card">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-lg-flex">
|
||||
<div>
|
||||
<h6 class="mb-0 font-weight-bolder text-lg">
|
||||
Thanatopracteurs
|
||||
</h6>
|
||||
<p class="text-sm mb-0">
|
||||
<i class="fa fa-check text-info" aria-hidden="true"></i>
|
||||
Liste des thanatopracteurs
|
||||
</p>
|
||||
</div>
|
||||
<div class="ms-auto my-auto mt-lg-0 mt-4">
|
||||
<div class="ms-auto my-auto">
|
||||
<slot name="thanatopractitioner-new-action" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pb-0">
|
||||
<div class="row px-4 pb-3">
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
|
||||
<slot name="select-filter" />
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3 ms-auto">
|
||||
<slot name="thanatopractitioner-other-action" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<slot name="thanatopractitioner-table" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script></script>
|
||||
<script setup>
|
||||
// Template only component
|
||||
</script>
|
||||
|
||||
@ -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>
|
||||
<footer class="py-3 footer">
|
||||
<div class="container-fluid">
|
||||
<div class="row align-items-center justify-content-lg-between">
|
||||
<div class="mb-4 col-lg-6 mb-lg-0">
|
||||
<div
|
||||
class="text-sm text-center copyright text-muted"
|
||||
:class="this.$store.state.isRTL ? 'text-lg-end' : 'text-lg-start'"
|
||||
>
|
||||
©
|
||||
{{ new Date().getFullYear() }}, made with
|
||||
<i class="fa fa-heart"></i> by
|
||||
<a
|
||||
href="https://www.creative-tim.com"
|
||||
class="font-weight-bold"
|
||||
target="_blank"
|
||||
>Creative Tim</a
|
||||
>
|
||||
for a better web.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<ul
|
||||
class="nav nav-footer justify-content-center justify-content-lg-end"
|
||||
>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="https://www.creative-tim.com"
|
||||
class="nav-link text-muted"
|
||||
target="_blank"
|
||||
>Creative Tim</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="https://www.creative-tim.com/presentation"
|
||||
class="nav-link text-muted"
|
||||
target="_blank"
|
||||
>About Us</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="https://www.creative-tim.com/blog"
|
||||
class="nav-link text-muted"
|
||||
target="_blank"
|
||||
>Blog</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="https://www.creative-tim.com/license"
|
||||
class="nav-link pe-0 text-muted"
|
||||
target="_blank"
|
||||
>License</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<footer class="py-3 footer"></footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@ -87,9 +87,11 @@
|
||||
<a
|
||||
class="p-0 nav-link"
|
||||
:class="textWhite ? textWhite : 'text-body'"
|
||||
@click="toggleConfigurator"
|
||||
@click="toggleMessenger"
|
||||
>
|
||||
<i class="cursor-pointer fa fa-cog fixed-plugin-button-nav"></i>
|
||||
<i
|
||||
class="cursor-pointer fa fa-comment fixed-plugin-button-nav"
|
||||
></i>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
@ -227,11 +229,13 @@
|
||||
import Breadcrumbs from "../Breadcrumbs.vue";
|
||||
import { mapMutations, mapActions, mapState } from "vuex";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import BotMessageConfigurator from "@/examples/BotMessageConfigurator.vue";
|
||||
|
||||
export default {
|
||||
name: "Navbar",
|
||||
components: {
|
||||
Breadcrumbs,
|
||||
BotMessageConfigurator,
|
||||
},
|
||||
props: {
|
||||
minNav: {
|
||||
@ -246,6 +250,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
showMenu: false,
|
||||
showMessenger: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -270,6 +275,10 @@ export default {
|
||||
this.navbarMinimize();
|
||||
},
|
||||
|
||||
toggleMessenger() {
|
||||
this.showMessenger = !this.showMessenger;
|
||||
},
|
||||
|
||||
async handleLogout() {
|
||||
try {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
@ -561,14 +561,14 @@ const routes = [
|
||||
component: () =>
|
||||
import("@/views/pages/Thanatopractitioners/AddThanatopractitioner.vue"),
|
||||
},
|
||||
// {
|
||||
// path: "/employes/thanatopracteurs/:id",
|
||||
// name: "Thanatopractitioner details",
|
||||
// component: () =>
|
||||
// import(
|
||||
// "@/views/pages/Thanatopractitioners/ThanatopractitionerDetails.vue"
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
path: "/employes/thanatopracteurs/:id",
|
||||
name: "Thanatopractitioner details",
|
||||
component: () =>
|
||||
import(
|
||||
"@/views/pages/Thanatopractitioners/ThanatopractitionerDetails.vue"
|
||||
),
|
||||
},
|
||||
// Paramétrage
|
||||
{
|
||||
path: "/parametrage/droits",
|
||||
|
||||
@ -12,6 +12,14 @@ export interface Thanatopractitioner {
|
||||
active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
||||
// Additional computed/mapped fields
|
||||
full_name?: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
email?: string | null;
|
||||
job_title?: string | null;
|
||||
|
||||
// Relations
|
||||
employee?: {
|
||||
id: number;
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref, computed } from "vue";
|
||||
import ThanatopractitionerService from "@/services/thanatopractitioner";
|
||||
|
||||
import type {
|
||||
import ThanatopractitionerService, {
|
||||
Thanatopractitioner,
|
||||
CreateThanatopractitionerPayload,
|
||||
UpdateThanatopractitionerPayload,
|
||||
ThanatopractitionerListResponse,
|
||||
NestedThanatopractitionerListResponse,
|
||||
} from "@/services/thanatopractitioner";
|
||||
import { useNotificationStore } from "./notification";
|
||||
|
||||
export const useThanatopractitionerStore = defineStore(
|
||||
"thanatopractitioner",
|
||||
@ -17,373 +14,361 @@ export const useThanatopractitionerStore = defineStore(
|
||||
const thanatopractitioners = ref<Thanatopractitioner[]>([]);
|
||||
const currentThanatopractitioner = ref<Thanatopractitioner | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const searchResults = ref<Thanatopractitioner[]>([]);
|
||||
|
||||
// Pagination state
|
||||
const pagination = ref({
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
from: 0,
|
||||
to: 0,
|
||||
});
|
||||
const isLoading = ref(false);
|
||||
const currentPage = ref(1);
|
||||
const perPage = ref(10);
|
||||
const total = ref(0);
|
||||
const lastPage = ref(1);
|
||||
const from = ref(0);
|
||||
const to = ref(0);
|
||||
const search = ref("");
|
||||
const activeFilter = ref<boolean | undefined>(undefined);
|
||||
|
||||
// Getters
|
||||
const allThanatopractitioners = computed(() => thanatopractitioners.value);
|
||||
const activeThanatopractitioners = computed(() =>
|
||||
thanatopractitioners.value.filter(
|
||||
(thanatopractitioner: Thanatopractitioner) => thanatopractitioner.active
|
||||
)
|
||||
);
|
||||
const inactiveThanatopractitioners = computed(() =>
|
||||
thanatopractitioners.value.filter(
|
||||
(thanatopractitioner: Thanatopractitioner) =>
|
||||
!thanatopractitioner.active
|
||||
)
|
||||
);
|
||||
const isLoading = computed(() => loading.value);
|
||||
const hasError = computed(() => error.value !== null);
|
||||
const getError = computed(() => error.value);
|
||||
const getThanatopractitionerById = computed(() => (id: number) =>
|
||||
thanatopractitioners.value.find(
|
||||
(thanatopractitioner: Thanatopractitioner) =>
|
||||
thanatopractitioner.id === id
|
||||
)
|
||||
);
|
||||
const getPagination = computed(() => pagination.value);
|
||||
const getPagination = computed(() => ({
|
||||
current_page: currentPage.value,
|
||||
last_page: lastPage.value,
|
||||
per_page: perPage.value,
|
||||
total: total.value,
|
||||
from: from.value,
|
||||
to: to.value,
|
||||
}));
|
||||
const totalPages = computed(() => lastPage.value);
|
||||
const hasNextPage = computed(() => currentPage.value < lastPage.value);
|
||||
const hasPreviousPage = computed(() => currentPage.value > 1);
|
||||
const isEmpty = computed(() => thanatopractitioners.value.length === 0);
|
||||
|
||||
// Actions
|
||||
const setLoading = (isLoading: boolean) => {
|
||||
loading.value = isLoading;
|
||||
};
|
||||
|
||||
const setError = (err: string | null) => {
|
||||
error.value = err;
|
||||
};
|
||||
|
||||
const clearError = () => {
|
||||
error.value = null;
|
||||
};
|
||||
|
||||
const setThanatopractitioners = (
|
||||
newThanatopractitioners: Thanatopractitioner[]
|
||||
const fetchThanatopractitioners = async (
|
||||
params: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
active?: boolean;
|
||||
} = {}
|
||||
) => {
|
||||
thanatopractitioners.value = newThanatopractitioners;
|
||||
};
|
||||
|
||||
const setCurrentThanatopractitioner = (
|
||||
thanatopractitioner: Thanatopractitioner | null
|
||||
) => {
|
||||
currentThanatopractitioner.value = thanatopractitioner;
|
||||
};
|
||||
|
||||
const setSearchThanatopractitioner = (
|
||||
searchThanatopractitioner: Thanatopractitioner[]
|
||||
) => {
|
||||
searchResults.value = searchThanatopractitioner;
|
||||
};
|
||||
|
||||
const setPagination = (meta: any) => {
|
||||
if (meta) {
|
||||
pagination.value = {
|
||||
current_page: meta.current_page || 1,
|
||||
last_page: meta.last_page || 1,
|
||||
per_page: meta.per_page || 10,
|
||||
total: meta.total || 0,
|
||||
from: meta.from || 0,
|
||||
to: meta.to || 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all thanatopractitioners with optional pagination and filters
|
||||
*/
|
||||
const fetchThanatopractitioners = async (params?: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
active?: boolean;
|
||||
sort_by?: string;
|
||||
sort_direction?: string;
|
||||
}) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
console.log("Fetching thanatopractitioners with params:", {
|
||||
page: params.page || 1,
|
||||
per_page: params.per_page || perPage.value,
|
||||
search: params.search || "",
|
||||
active: params.active,
|
||||
});
|
||||
|
||||
const response = await ThanatopractitionerService.getAllThanatopractitioners(
|
||||
params
|
||||
{
|
||||
page: params.page || 1,
|
||||
per_page: params.per_page || perPage.value,
|
||||
search: params.search || "",
|
||||
active: params.active,
|
||||
}
|
||||
);
|
||||
setThanatopractitioners(response.data.data);
|
||||
setPagination(response.data.pagination);
|
||||
console.log("Full response:", JSON.stringify(response, null, 2));
|
||||
|
||||
// Safely handle the response with more robust type handling
|
||||
const data = response.data?.data || response.data || [];
|
||||
const pagination = response.data?.pagination || {
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
total: data.length,
|
||||
from: 0,
|
||||
to: data.length,
|
||||
};
|
||||
|
||||
// Map data to include employee information with safe fallbacks
|
||||
thanatopractitioners.value = data.map((item: Thanatopractitioner) => ({
|
||||
...item,
|
||||
...(item.employee || {}),
|
||||
full_name: item.employee?.full_name || "N/A",
|
||||
job_title: item.employee?.job_title || "N/A",
|
||||
first_name: item.employee?.first_name || "N/A",
|
||||
last_name: item.employee?.last_name || "N/A",
|
||||
email: item.employee?.email || "N/A",
|
||||
}));
|
||||
|
||||
// Update pagination values
|
||||
currentPage.value = pagination.current_page;
|
||||
lastPage.value = pagination.last_page;
|
||||
total.value = pagination.total;
|
||||
from.value = pagination.from;
|
||||
to.value = pagination.to;
|
||||
|
||||
search.value = params.search || "";
|
||||
activeFilter.value = params.active;
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch thanatopractitioners";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} catch (error) {
|
||||
console.error("Error fetching thanatopractitioners:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch a single thanatopractitioner by ID
|
||||
*/
|
||||
const fetchThanatopractitioner = async (id: number) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
const response = await ThanatopractitionerService.getThanatopractitioner(
|
||||
id
|
||||
);
|
||||
setCurrentThanatopractitioner(response.data);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch thanatopractitioner";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
|
||||
// Safely handle the response with additional mapping
|
||||
const data = response.data;
|
||||
currentThanatopractitioner.value = {
|
||||
...data,
|
||||
...(data.employee || {}),
|
||||
full_name: data.employee?.full_name || "N/A",
|
||||
job_title: data.employee?.job_title || "N/A",
|
||||
first_name: data.employee?.first_name || "N/A",
|
||||
last_name: data.employee?.last_name || "N/A",
|
||||
email: data.employee?.email || "N/A",
|
||||
};
|
||||
|
||||
return currentThanatopractitioner.value;
|
||||
} catch (error) {
|
||||
console.error("Error fetching thanatopractitioner:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Add method for details view
|
||||
const fetchThanatopractitionerById = async (id: number) => {
|
||||
try {
|
||||
const response = await ThanatopractitionerService.getThanatopractitioner(
|
||||
Number(id)
|
||||
);
|
||||
|
||||
// Safely handle the response with additional mapping
|
||||
const data = response.data;
|
||||
return {
|
||||
...data,
|
||||
...(data.employee || {}),
|
||||
full_name: data.employee?.full_name || "N/A",
|
||||
job_title: data.employee?.job_title || "N/A",
|
||||
first_name: data.employee?.first_name || "N/A",
|
||||
last_name: data.employee?.last_name || "N/A",
|
||||
email: data.employee?.email || "N/A",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching thanatopractitioner by ID:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Add method for documents (placeholder)
|
||||
const fetchThanatopractitionerDocuments = async (id: number) => {
|
||||
try {
|
||||
// TODO: Implement actual document fetching logic
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching thanatopractitioner documents:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Add method for adding documents (placeholder)
|
||||
const addThanatopractitionerDocument = async (documentData: any) => {
|
||||
try {
|
||||
// TODO: Implement actual document addition logic
|
||||
console.log("Adding document:", documentData);
|
||||
} catch (error) {
|
||||
console.error("Error adding thanatopractitioner document:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Add method for updating documents (placeholder)
|
||||
const updateThanatopractitionerDocument = async (documentData: any) => {
|
||||
try {
|
||||
// TODO: Implement actual document update logic
|
||||
console.log("Updating document:", documentData);
|
||||
} catch (error) {
|
||||
console.error("Error updating thanatopractitioner document:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new thanatopractitioner
|
||||
*/
|
||||
const createThanatopractitioner = async (
|
||||
payload: CreateThanatopractitionerPayload
|
||||
) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await ThanatopractitionerService.createThanatopractitioner(
|
||||
payload
|
||||
);
|
||||
// Add the new thanatopractitioner to the list
|
||||
thanatopractitioners.value.push(response.data);
|
||||
setCurrentThanatopractitioner(response.data);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to create thanatopractitioner";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
|
||||
// Add the new thanatopractitioner to the list with safe mapping
|
||||
const newThanatopractitioner = {
|
||||
...response.data,
|
||||
...(response.data.employee || {}),
|
||||
full_name: response.data.employee?.full_name || "N/A",
|
||||
job_title: response.data.employee?.job_title || "N/A",
|
||||
first_name: response.data.employee?.first_name || "N/A",
|
||||
last_name: response.data.employee?.last_name || "N/A",
|
||||
email: response.data.employee?.email || "N/A",
|
||||
};
|
||||
|
||||
thanatopractitioners.value.unshift(newThanatopractitioner);
|
||||
total.value += 1;
|
||||
|
||||
return newThanatopractitioner;
|
||||
} catch (error) {
|
||||
console.error("Error creating thanatopractitioner:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update an existing thanatopractitioner
|
||||
*/
|
||||
const updateThanatopractitioner = async (
|
||||
payload: UpdateThanatopractitionerPayload
|
||||
) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log(payload);
|
||||
loading.value = true;
|
||||
const response = await ThanatopractitionerService.updateThanatopractitioner(
|
||||
payload
|
||||
);
|
||||
const updatedThanatopractitioner = response.data;
|
||||
|
||||
// Safely map the updated thanatopractitioner
|
||||
const updatedThanatopractitioner = {
|
||||
...response.data,
|
||||
...(response.data.employee || {}),
|
||||
full_name: response.data.employee?.full_name || "N/A",
|
||||
job_title: response.data.employee?.job_title || "N/A",
|
||||
first_name: response.data.employee?.first_name || "N/A",
|
||||
last_name: response.data.employee?.last_name || "N/A",
|
||||
email: response.data.employee?.email || "N/A",
|
||||
};
|
||||
|
||||
// Update in the thanatopractitioners list
|
||||
const index = thanatopractitioners.value.findIndex(
|
||||
(thanatopractitioner: Thanatopractitioner) =>
|
||||
thanatopractitioner.id === updatedThanatopractitioner.id
|
||||
(t) => t.id === payload.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
thanatopractitioners.value[index] = updatedThanatopractitioner;
|
||||
}
|
||||
|
||||
// Update current thanatopractitioner if it's the one being edited
|
||||
if (
|
||||
currentThanatopractitioner.value &&
|
||||
currentThanatopractitioner.value.id === updatedThanatopractitioner.id
|
||||
) {
|
||||
setCurrentThanatopractitioner(updatedThanatopractitioner);
|
||||
// Update current thanatopractitioner if it's the same one
|
||||
if (currentThanatopractitioner.value?.id === payload.id) {
|
||||
currentThanatopractitioner.value = updatedThanatopractitioner;
|
||||
}
|
||||
|
||||
return updatedThanatopractitioner;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to update thanatopractitioner";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} catch (error) {
|
||||
console.error("Error updating thanatopractitioner:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a thanatopractitioner
|
||||
*/
|
||||
const deleteThanatopractitioner = async (id: number) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await ThanatopractitionerService.deleteThanatopractitioner(
|
||||
id
|
||||
);
|
||||
loading.value = true;
|
||||
await ThanatopractitionerService.deleteThanatopractitioner(id);
|
||||
|
||||
// Remove from the thanatopractitioners list
|
||||
thanatopractitioners.value = thanatopractitioners.value.filter(
|
||||
(thanatopractitioner: Thanatopractitioner) =>
|
||||
thanatopractitioner.id !== id
|
||||
(t) => t.id !== id
|
||||
);
|
||||
total.value -= 1;
|
||||
|
||||
// Clear current thanatopractitioner if it's the one being deleted
|
||||
if (
|
||||
currentThanatopractitioner.value &&
|
||||
currentThanatopractitioner.value.id === id
|
||||
) {
|
||||
setCurrentThanatopractitioner(null);
|
||||
// Clear current thanatopractitioner if it's the deleted one
|
||||
if (currentThanatopractitioner.value?.id === id) {
|
||||
currentThanatopractitioner.value = null;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to delete thanatopractitioner";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} catch (error) {
|
||||
console.error("Error deleting thanatopractitioner:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Search thanatopractitioners
|
||||
*/
|
||||
const searchThanatopractitioners = async (query: string) => {
|
||||
setLoading(true);
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const results = await ThanatopractitionerService.searchThanatopractitioners(
|
||||
query
|
||||
);
|
||||
setSearchThanatopractitioner(results);
|
||||
return results;
|
||||
} catch (err) {
|
||||
error.value = "Erreur lors de la recherche des thanatopractitioners";
|
||||
console.error("Error searching thanatopractitioners:", err);
|
||||
setSearchThanatopractitioner([]);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle thanatopractitioner active status
|
||||
*/
|
||||
const toggleThanatopractitionerStatus = async (
|
||||
id: number,
|
||||
isActive: boolean
|
||||
) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await ThanatopractitionerService.toggleThanatopractitionerStatus(
|
||||
id,
|
||||
isActive
|
||||
);
|
||||
const updatedThanatopractitioner = response.data;
|
||||
|
||||
// Safely map the updated thanatopractitioner
|
||||
const updatedThanatopractitioner = {
|
||||
...response.data,
|
||||
...(response.data.employee || {}),
|
||||
full_name: response.data.employee?.full_name || "N/A",
|
||||
job_title: response.data.employee?.job_title || "N/A",
|
||||
first_name: response.data.employee?.first_name || "N/A",
|
||||
last_name: response.data.employee?.last_name || "N/A",
|
||||
email: response.data.employee?.email || "N/A",
|
||||
};
|
||||
|
||||
// Update in the thanatopractitioners list
|
||||
const index = thanatopractitioners.value.findIndex(
|
||||
(thanatopractitioner: Thanatopractitioner) =>
|
||||
thanatopractitioner.id === id
|
||||
);
|
||||
const index = thanatopractitioners.value.findIndex((t) => t.id === id);
|
||||
if (index !== -1) {
|
||||
thanatopractitioners.value[index] = updatedThanatopractitioner;
|
||||
}
|
||||
|
||||
// Update current thanatopractitioner if it's the one being toggled
|
||||
if (
|
||||
currentThanatopractitioner.value &&
|
||||
currentThanatopractitioner.value.id === id
|
||||
) {
|
||||
setCurrentThanatopractitioner(updatedThanatopractitioner);
|
||||
// Update current thanatopractitioner if it's the same one
|
||||
if (currentThanatopractitioner.value?.id === id) {
|
||||
currentThanatopractitioner.value = updatedThanatopractitioner;
|
||||
}
|
||||
|
||||
return updatedThanatopractitioner;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to toggle thanatopractitioner status";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Error toggling thanatopractitioner status:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get thanatopractitioner statistics
|
||||
*/
|
||||
const fetchStatistics = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const searchThanatopractitioners = async (query: string) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const results = await ThanatopractitionerService.searchThanatopractitioners(
|
||||
query
|
||||
);
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error("Error searching thanatopractitioners:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatistics = async () => {
|
||||
try {
|
||||
const response = await ThanatopractitionerService.getStatistics();
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err.response?.data?.message ||
|
||||
err.message ||
|
||||
"Failed to fetch statistics";
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Error fetching thanatopractitioner statistics:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear current thanatopractitioner
|
||||
*/
|
||||
const clearCurrentThanatopractitioner = () => {
|
||||
setCurrentThanatopractitioner(null);
|
||||
const setCurrentPage = (page: number) => {
|
||||
currentPage.value = page;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all state
|
||||
*/
|
||||
const clearStore = () => {
|
||||
thanatopractitioners.value = [];
|
||||
const setPerPage = (newPerPage: number) => {
|
||||
perPage.value = newPerPage;
|
||||
currentPage.value = 1; // Reset to first page
|
||||
};
|
||||
|
||||
const clearCurrentThanatopractitioner = () => {
|
||||
currentThanatopractitioner.value = null;
|
||||
error.value = null;
|
||||
pagination.value = {
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
from: 0,
|
||||
to: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
search.value = "";
|
||||
activeFilter.value = undefined;
|
||||
currentPage.value = 1;
|
||||
};
|
||||
|
||||
return {
|
||||
@ -391,31 +376,42 @@ export const useThanatopractitionerStore = defineStore(
|
||||
thanatopractitioners,
|
||||
currentThanatopractitioner,
|
||||
loading,
|
||||
error,
|
||||
searchResults,
|
||||
isLoading,
|
||||
currentPage,
|
||||
perPage,
|
||||
total,
|
||||
lastPage,
|
||||
from,
|
||||
to,
|
||||
search,
|
||||
activeFilter,
|
||||
|
||||
// Getters
|
||||
allThanatopractitioners,
|
||||
activeThanatopractitioners,
|
||||
inactiveThanatopractitioners,
|
||||
isLoading,
|
||||
hasError,
|
||||
getError,
|
||||
getThanatopractitionerById,
|
||||
getPagination,
|
||||
totalPages,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
isEmpty,
|
||||
|
||||
// Actions
|
||||
fetchThanatopractitioners,
|
||||
fetchThanatopractitioner,
|
||||
fetchThanatopractitionerById,
|
||||
fetchThanatopractitionerDocuments,
|
||||
addThanatopractitionerDocument,
|
||||
updateThanatopractitionerDocument,
|
||||
createThanatopractitioner,
|
||||
updateThanatopractitioner,
|
||||
deleteThanatopractitioner,
|
||||
searchThanatopractitioners,
|
||||
toggleThanatopractitionerStatus,
|
||||
fetchStatistics,
|
||||
searchThanatopractitioners,
|
||||
getStatistics,
|
||||
setCurrentPage,
|
||||
setPerPage,
|
||||
clearCurrentThanatopractitioner,
|
||||
clearStore,
|
||||
clearError,
|
||||
resetFilters,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default useThanatopractitionerStore;
|
||||
|
||||
@ -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>
|
||||
<thanatopractitioner-presentation
|
||||
:thanatopractitioner-data="thanatopractitionerStore.thanatopractitioners"
|
||||
:loading-data="thanatopractitionerStore.loading"
|
||||
:pagination="thanatopractitionerStore.getPagination"
|
||||
:thanatopractitioner-data="thanatopractitioners"
|
||||
:loading-data="loading"
|
||||
:pagination="pagination"
|
||||
@push-details="goDetails"
|
||||
@delete-thanatopractitioner="confirmDeleteThanatopractitioner"
|
||||
@change-page="changePage"
|
||||
@ -25,7 +25,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, onMounted } from "vue";
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import ThanatopractitionerPresentation from "@/components/Organism/Thanatopractitioner/ThanatopractitionerPresentation.vue";
|
||||
import ConfirmModal from "@/components/molecules/common/ConfirmModal.vue";
|
||||
import { useThanatopractitionerStore } from "@/stores/thanatopractitionerStore";
|
||||
@ -36,6 +36,15 @@ const thanatopractitionerStore = useThanatopractitionerStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
const router = useRouter();
|
||||
|
||||
const thanatopractitioners = ref([]);
|
||||
const loading = ref(false);
|
||||
const pagination = ref({
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
// Confirm modal state
|
||||
const confirmModal = reactive({
|
||||
isVisible: false,
|
||||
@ -52,94 +61,91 @@ const confirmModal = reactive({
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await thanatopractitionerStore.fetchThanatopractitioners();
|
||||
await fetchThanatopractitioners();
|
||||
});
|
||||
|
||||
const fetchThanatopractitioners = async (
|
||||
params = { page: 1, per_page: 10 }
|
||||
) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await thanatopractitionerStore.fetchThanatopractitioners(
|
||||
params
|
||||
);
|
||||
|
||||
// Update data based on the new API response structure
|
||||
thanatopractitioners.value = response.data.data.map((item) => ({
|
||||
...item.employee,
|
||||
...item,
|
||||
full_name: item.employee.full_name,
|
||||
job_title: item.employee.job_title,
|
||||
}));
|
||||
|
||||
// Update pagination
|
||||
pagination.value = {
|
||||
current_page: response.pagination.current_page,
|
||||
last_page: response.pagination.last_page,
|
||||
per_page: response.pagination.per_page,
|
||||
total: response.pagination.total,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching thanatopractitioners:", error);
|
||||
notificationStore.error(
|
||||
"Erreur de chargement",
|
||||
"Une erreur est survenue lors du chargement des thanatopractitioners."
|
||||
);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const goDetails = (id) => {
|
||||
router.push({
|
||||
name: "Thanatopractitioner details",
|
||||
params: {
|
||||
id: id,
|
||||
},
|
||||
params: { id: id },
|
||||
});
|
||||
};
|
||||
|
||||
const confirmDeleteThanatopractitioner = (thanatopractitionerId) => {
|
||||
console.log(
|
||||
"confirmDeleteThanatopractitioner called with ID:",
|
||||
thanatopractitionerId
|
||||
);
|
||||
console.log("Thanatopractitioner ID type:", typeof thanatopractitionerId);
|
||||
console.log(
|
||||
"Store thanatopractitioners:",
|
||||
thanatopractitionerStore.thanatopractitioners
|
||||
);
|
||||
console.log(
|
||||
"Store thanatopractitioners length:",
|
||||
thanatopractitionerStore.thanatopractitioners.length
|
||||
);
|
||||
|
||||
const thanatopractitioner = thanatopractitionerStore.thanatopractitioners.find(
|
||||
const thanatopractitioner = thanatopractitioners.value.find(
|
||||
(prac) => prac.id === thanatopractitionerId
|
||||
);
|
||||
console.log("Found thanatopractitioner:", thanatopractitioner);
|
||||
|
||||
if (thanatopractitioner) {
|
||||
console.log("Showing confirmation modal");
|
||||
confirmModal.isVisible = true;
|
||||
confirmModal.thanatopractitionerId = thanatopractitionerId;
|
||||
confirmModal.thanatopractitionerName = `${thanatopractitioner.first_name} ${thanatopractitioner.last_name}`;
|
||||
confirmModal.thanatopractitionerName = thanatopractitioner.full_name;
|
||||
confirmModal.message = `Êtes-vous sûr de vouloir supprimer le thanatopractitioner "${confirmModal.thanatopractitionerName}" ?`;
|
||||
} else {
|
||||
console.log("No thanatopractitioner found - trying to parse as number");
|
||||
const numericId = parseInt(thanatopractitionerId, 10);
|
||||
console.log("Trying with numeric ID:", numericId);
|
||||
const thanatopractitionerByNumber = thanatopractitionerStore.thanatopractitioners.find(
|
||||
(prac) => prac.id === numericId
|
||||
);
|
||||
console.log("Found by numeric ID:", thanatopractitionerByNumber);
|
||||
|
||||
if (thanatopractitionerByNumber) {
|
||||
console.log("Showing confirmation modal with numeric ID");
|
||||
confirmModal.isVisible = true;
|
||||
confirmModal.thanatopractitionerId = numericId;
|
||||
confirmModal.thanatopractitionerName = `${thanatopractitionerByNumber.first_name} ${thanatopractitionerByNumber.last_name}`;
|
||||
confirmModal.message = `Êtes-vous sûr de vouloir supprimer le thanatopractitioner "${confirmModal.thanatopractitionerName}" ?`;
|
||||
} else {
|
||||
console.log(
|
||||
"Still no thanatopractitioner found - displaying all thanatopractitioner IDs for comparison"
|
||||
);
|
||||
thanatopractitionerStore.thanatopractitioners.forEach((prac, index) => {
|
||||
console.log(
|
||||
`Thanatopractitioner ${index}: ID=${
|
||||
prac.id
|
||||
} (${typeof prac.id}), Name=${prac.first_name} ${prac.last_name}`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
const thanatopractitionerId = confirmModal.thanatopractitionerId;
|
||||
const thanatopractitionerName = confirmModal.thanatopractitionerName;
|
||||
console.log("Test");
|
||||
|
||||
try {
|
||||
confirmModal.isLoading = true;
|
||||
await thanatopractitionerStore.deleteThanatopractitioner(
|
||||
thanatopractitionerId
|
||||
);
|
||||
|
||||
notificationStore.success(
|
||||
"Thanatopractitioner supprimé",
|
||||
`Le thanatopractitioner ${thanatopractitionerName} a été supprimé avec succès.`
|
||||
);
|
||||
closeConfirmModal();
|
||||
|
||||
// Refresh the list after deletion
|
||||
await fetchThanatopractitioners({
|
||||
page: pagination.value.current_page,
|
||||
per_page: pagination.value.per_page,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting thanatopractitioner:", error);
|
||||
notificationStore.error(
|
||||
"Erreur de suppression",
|
||||
"Une erreur est survenue lors de la suppression du thanatopractitioner."
|
||||
);
|
||||
} finally {
|
||||
closeConfirmModal();
|
||||
}
|
||||
};
|
||||
@ -156,12 +162,10 @@ const closeConfirmModal = () => {
|
||||
};
|
||||
|
||||
const changePage = async (page) => {
|
||||
console.log("changePage called in Thanatopractitioners.vue with page:", page);
|
||||
try {
|
||||
console.log("Fetching thanatopractitioners with page:", page);
|
||||
await thanatopractitionerStore.fetchThanatopractitioners({
|
||||
await fetchThanatopractitioners({
|
||||
page,
|
||||
per_page: 10,
|
||||
per_page: pagination.value.per_page,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error changing page:", error);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user