pagiantion defunt liste

This commit is contained in:
kevin 2026-04-29 09:15:58 +03:00
parent 67b15ab337
commit 9951ed0ee6
6 changed files with 294 additions and 32 deletions

View File

@ -20,18 +20,30 @@ class InterventionSeeder extends Seeder
$products = Product::query()
->whereHas('category', fn ($query) => $query->where('intervention', true))
->get();
$deceasedCollection = Deceased::query()->get();
$practitioners = Thanatopractitioner::query()->get();
$clients = Client::query()->limit(12)->get();
$creatorId = User::query()->value('id');
$types = ['thanatopraxie', 'toilette_mortuaire', 'exhumation', 'retrait_pacemaker', 'retrait_bijoux', 'autre'];
$statuses = ['demande', 'planifie', 'en_cours', 'termine', 'annule'];
if ($products->isEmpty() || $deceasedCollection->isEmpty() || $clients->isEmpty()) {
if ($products->isEmpty() || $clients->isEmpty()) {
return;
}
foreach ($clients as $index => $client) {
$deceased = Deceased::updateOrCreate(
[
'last_name' => sprintf('Défunt Client %d', $client->id),
'first_name' => 'Dossier',
],
[
'birth_date' => now()->subYears(55 + $index)->subDays($index)->format('Y-m-d'),
'death_date' => now()->subDays($index + 1)->format('Y-m-d'),
'place_of_death' => $client->billing_city ?: 'Antananarivo',
'notes' => sprintf('Défunt de démonstration lié au client #%d pour les interventions seedées.', $client->id),
]
);
$location = ClientLocation::updateOrCreate(
[
'client_id' => $client->id,
@ -54,7 +66,7 @@ class InterventionSeeder extends Seeder
'type' => $types[$index % count($types)],
],
[
'deceased_id' => optional($deceasedCollection->get($index % $deceasedCollection->count()))->id,
'deceased_id' => $deceased->id,
'order_giver' => $faker->name,
'location_id' => $location->id,
'product_id' => optional($products->get($index % $products->count()))->id,

View File

@ -4,13 +4,58 @@
<add-button text="Ajouter" @click="add" />
</template>
<template #select-filter>
<div class="defunt-toolbar-controls">
<filter-table />
<div class="defunt-search-box ms-2">
<soft-input
id="deceased-list-search"
:model-value="search"
placeholder="Rechercher par nom du défunt"
icon="fas fa-search"
icon-dir="left"
@update:model-value="emit('search-change', $event)"
/>
</div>
</div>
</template>
<template #defunt-other-action>
<table-action />
</template>
<template #header-pagination>
<div
v-if="pagination && pagination.last_page > 1"
class="d-flex justify-content-center"
>
<soft-pagination color="success" size="sm">
<soft-pagination-item
prev
:disabled="pagination.current_page <= 1"
@click="changePage(pagination.current_page - 1)"
/>
<soft-pagination-item
v-for="page in visiblePages"
:key="page"
:label="page.toString()"
:active="pagination.current_page === page"
@click="changePage(page)"
/>
<soft-pagination-item
next
:disabled="pagination.current_page >= pagination.last_page"
@click="changePage(pagination.current_page + 1)"
/>
</soft-pagination>
</div>
</template>
<template #defunt-table>
<defunts-list :defunts="defunts" />
<defunts-list
:defunts="defunts"
:loading="loading"
:pagination="pagination"
@page-change="emit('page-change', $event)"
/>
</template>
</defunts-template>
</template>
@ -20,19 +65,110 @@ 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 DefuntsList from "@/components/molecules/Defunts/DefuntsList.vue";
import { defineProps } from "vue";
import SoftInput from "@/components/SoftInput.vue";
import SoftPagination from "@/components/SoftPagination.vue";
import SoftPaginationItem from "@/components/SoftPaginationItem.vue";
import { computed, defineEmits, defineProps } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
defineProps({
const emit = defineEmits(["page-change", "search-change"]);
const props = defineProps({
defunts: {
type: Array,
required: true,
},
loading: {
type: Boolean,
default: false,
},
pagination: {
type: Object,
default: () => ({
current_page: 1,
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
}),
},
search: {
type: String,
default: "",
},
});
const add = () => {
router.push({ name: "Add Defunts" });
};
const changePage = (page) => {
if (typeof page !== "number") return;
if (page < 1 || page > (props.pagination?.last_page || 1)) return;
if (page === props.pagination?.current_page) return;
emit("page-change", page);
};
const visiblePages = computed(() => {
if (!props.pagination) return [];
const currentPage = props.pagination.current_page || 1;
const lastPage = props.pagination.last_page || 1;
if (lastPage <= 7) {
return Array.from({ length: lastPage }, (_, index) => index + 1);
}
const pages = [1];
let start = Math.max(2, currentPage - 1);
let end = Math.min(lastPage - 1, currentPage + 1);
if (currentPage < 4) {
start = 2;
end = 4;
}
if (currentPage > lastPage - 3) {
start = lastPage - 3;
end = lastPage - 1;
}
for (let page = start; page <= end; page++) {
pages.push(page);
}
if (!pages.includes(lastPage)) {
pages.push(lastPage);
}
return [...new Set(pages)].filter(
(page) => typeof page === "number" && page >= 1 && page <= lastPage
);
});
</script>
<style scoped>
.defunt-toolbar-controls {
display: flex;
align-items: center;
}
.defunt-search-box {
min-width: 280px;
}
@media (max-width: 991.98px) {
.defunt-toolbar-controls {
width: 100%;
flex-wrap: wrap;
gap: 0.75rem;
}
.defunt-search-box {
min-width: 100%;
}
}
</style>

View File

@ -1,6 +1,13 @@
<template>
<div class="row">
<div v-if="!defunts || defunts.length === 0" class="empty-state">
<div>
<div v-if="loading" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Chargement...</span>
</div>
<p class="mt-2 mb-0">Chargement des défunts...</p>
</div>
<div v-else-if="!defunts || defunts.length === 0" class="empty-state">
<div class="empty-message">
<i class="fas fa-inbox"></i>
<h3>Aucun défunt trouvé</h3>
@ -10,6 +17,9 @@
</soft-button>
</div>
</div>
<template v-else>
<div class="row">
<div
v-for="(defunt, index) in defunts"
:key="index"
@ -28,6 +38,18 @@
</div>
</div>
<div
v-if="(pagination?.last_page || 1) > 1"
class="d-flex justify-content-end align-items-center mt-3 px-1 flex-wrap gap-3"
>
<div class="text-xs text-secondary font-weight-bold">
Affichage de {{ safeFrom }} à {{ safeTo }} sur
{{ pagination.total || defunts.length }} défunts
</div>
</div>
</template>
</div>
<!-- Intervention Add Modal -->
<div v-if="showModal" class="modal-overlay" @click="closeInterventionModal">
<div class="modal-container" @click.stop>
@ -81,8 +103,7 @@
</template>
<script setup>
import DefuntCard from "@/components/atoms/Defunts/DefuntCard.vue";
import { ref } from "vue";
import { defineProps } from "vue";
import { computed, defineProps, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import SoftButton from "@/components/SoftButton.vue";
import InterventationAddModal from "@/components/molecules/Interventions/InterventationAddModal.vue";
@ -90,7 +111,6 @@ import { useInterventionStore } from "@/stores/interventionStore";
import { useDeceasedStore } from "@/stores/deceasedStore";
import { useClientStore } from "@/stores/clientStore";
import { useNotificationStore } from "@/stores/notification";
import { onMounted } from "vue";
// Router
const router = useRouter();
@ -177,11 +197,58 @@ const handleCreateIntervention = async (form) => {
}
};
defineProps({
const props = defineProps({
defunts: {
type: Array,
required: true,
},
loading: {
type: Boolean,
default: false,
},
pagination: {
type: Object,
default: () => ({
current_page: 1,
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
}),
},
});
const safeFrom = computed(() => {
if (props.pagination?.from) {
return props.pagination.from;
}
if (!props.pagination?.total || props.defunts.length === 0) {
return 0;
}
return (
((Number(props.pagination.current_page) || 1) - 1) *
(Number(props.pagination.per_page) || 10) +
1
);
});
const safeTo = computed(() => {
if (props.pagination?.to) {
return props.pagination.to;
}
if (!props.pagination?.total || props.defunts.length === 0) {
return 0;
}
return Math.min(
(Number(props.pagination.current_page) || 1) *
(Number(props.pagination.per_page) || 10),
Number(props.pagination.total) || 0
);
});
// Modal management functions

View File

@ -13,6 +13,9 @@
</div>
<div class="row">
<div class="col-12">
<div class="mt-4">
<slot name="header-pagination"></slot>
</div>
<div class="mt-4">
<slot name="defunt-table"></slot>
</div>

View File

@ -21,6 +21,8 @@ export const useDeceasedStore = defineStore("deceased", () => {
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
});
// Getters
@ -103,6 +105,8 @@ export const useDeceasedStore = defineStore("deceased", () => {
last_page: meta.last_page || 1,
per_page: meta.per_page || 10,
total: meta.total || 0,
from: meta.from || 0,
to: meta.to || 0,
};
}
};
@ -308,6 +312,8 @@ export const useDeceasedStore = defineStore("deceased", () => {
last_page: 1,
per_page: 10,
total: 0,
from: 0,
to: 0,
};
};

View File

@ -1,14 +1,52 @@
<template>
<defunt-presentation :defunts="deceasedStore.deceased" />
<defunt-presentation
:defunts="deceasedStore.deceased"
:loading="deceasedStore.loading"
:pagination="deceasedStore.getPagination"
:search="search"
@page-change="changePage"
@search-change="updateSearch"
/>
</template>
<script setup>
import DefuntPresentation from "@/components/Organism/Defunts/DefuntPresentation.vue";
import { useDeceasedStore } from "@/stores/deceasedStore";
import { onMounted } from "vue";
import { onMounted, ref } from "vue";
const deceasedStore = useDeceasedStore();
const search = ref("");
let searchDebounceTimeout = null;
const DEFAULT_PER_PAGE = 10;
onMounted(async () => {
await deceasedStore.fetchDeceased();
await deceasedStore.fetchDeceased({
page: 1,
per_page: DEFAULT_PER_PAGE,
search: search.value.trim(),
});
});
const changePage = async (page) => {
await deceasedStore.fetchDeceased({
page,
per_page: deceasedStore.getPagination.per_page || DEFAULT_PER_PAGE,
search: search.value.trim(),
});
};
const updateSearch = (value) => {
search.value = value;
if (searchDebounceTimeout) {
window.clearTimeout(searchDebounceTimeout);
}
searchDebounceTimeout = window.setTimeout(async () => {
await deceasedStore.fetchDeceased({
page: 1,
per_page: deceasedStore.getPagination.per_page || DEFAULT_PER_PAGE,
search: search.value.trim(),
});
}, 300);
};
</script>