<template>
<PageHeader :propData="dataToPass" />
<div class="row">
<div class="col-xl-12">
<div class="card custom-card">
<div class="card-header">
<div class="card-title">
<div class="input-group">
<input type="text" v-model="searchValue" class="form-control bg-
light border-0" placeholder="Rechercher" aria-describedby="button-addon2">
<button @click="searchConges" aria-label="button" class="btn btn-
light" type="button" id="button-addon2">
<i class="ri-search-line text-muted"></i>
</button>
</div>
</div>
<div class="ml-auto">
<div class="btn-group mr-4">
<button type="button" class="btn btn-outline-secondary rounded-
pill"
@click="printPage">
<img src="/print.svg" alt="Print Icon" class="svg-icon me-2
align-middle" />
</button>
<button type="button" class="btn btn-outline-secondary rounded-
pill"
@click="downloadPDF">
<img src="/pdf1.svg" alt="PDF Icon" class="svg-icon me-2 align-
middle" />
</button>
<button type="button" class="btn btn-outline-secondary rounded-
pill"
@click="downloadCSV">
<img src="/csv1.svg" alt="Csv Icon" class="svg-icon me-2
align-middle" />
</button>
<button type="button" class="btn btn-outline-secondary rounded-
pill"
@click="downloadExcel">
<img src="/excel.svg" alt="Excel Icon" class="excel-icon
me-2 align-middle" />
</button>
</div>
</div>
</div>
<div class="card custom-card">
<div class="card-body">
<div class="table-responsive">
<table class="table text-nowrap table-bordered">
<thead>
<tr>
<th>
<div class="d-flex align-items-center">
<span>#</span>
<i
class="ri-sort-asc ml-auto"
:class="sortColumn === 'index' ? (sortOrder === 'asc' ?
'ri-sort-desc' : 'ri-sort-asc') : 'ri-sort-asc'"
@click="sortColumnToggle('index')"
></i> </div>
<input v-if="showSearch.index" v-model="columnSearch.index"
@input="filterConge" type="text" class="form-control mt-2">
</th>
<th>
<div class="d-flex align-items-center">
<span>Sigle </span>
<i class="ri-search-line text-success ml-auto"
@click="toggleSearch('code_type_dossier')"></i>
<i
:class="sortColumn === 'code_type_dossier' ? (sortOrder
=== 'asc' ? 'ri-sort-desc' : 'ri-sort-asc') : 'ri-sort-asc'"
@click="sortColumnToggle('code_type_dossier')"
></i>
</div>
<input v-if="showSearch.code_type_dossier" v-
model="columnSearch.code_type_dossier" @input="filterConge" type="text"
class="form-control mt-2">
</th>
<th>
<div class="d-flex align-items-center">
<span>Nature dossier </span>
<i class="ri-search-line text-success ml-auto"
@click="toggleSearch('nature_dossier')"></i>
<i
:class="sortColumn === 'nature_dossier' ? (sortOrder
=== 'asc' ? 'ri-sort-desc' : 'ri-sort-asc') : 'ri-sort-asc'"
@click="sortColumnToggle('nature_dossier')"
></i>
</div>
<input v-if="showSearch.nature_dossier" v-
model="columnSearch.nature_dossier" @input="filterConge" type="text" class="form-
control mt-2">
</th>
<th>
<div class="d-flex align-items-center">
<span>Type dossier </span>
<i class="ri-search-line text-success ml-auto"
@click="toggleSearch('libelle')"></i>
<i
:class="sortColumn === 'libelle' ? (sortOrder === 'asc'
? 'ri-sort-desc' : 'ri-sort-asc') : 'ri-sort-asc'"
@click="sortColumnToggle('libelle')"
></i>
</div>
<input v-if="showSearch.libelle" v-
model="columnSearch.libelle" @input="filterConge" type="text" class="form-control
mt-2">
</th>
<th style="width: 100px;">
<div class="d-flex align-items-center">
<span>Pieces </span>
<i class="ri-search-line text-success ml-auto"
@click="toggleSearch('pieces1')"></i>
<i
:class="sortColumn === 'pieces1' ? (sortOrder === 'asc'
? 'ri-sort-desc' : 'ri-sort-asc') : 'ri-sort-asc'"
@click="sortColumnToggle('pieces1')"
></i>
</div>
<input v-if="showSearch.pieces1" v-
model="columnSearch.pieces1" @input="filterConge" type="text" class="form-control
mt-2">
</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in dossiers" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.code_type_dossier }}</td>
<td>{{ item.nature_dossier ? item.nature_dossier.libelle :
'N/A' }}</td>
<td>{{ item.libelle }}</td>
<td>
<!-- Loop through associated pieces and display them -->
<ul>
<li v-for="piece in
item.associatedPieces" :key="piece.id">{{ piece.libelle }}</li>
</ul>
</td>
<td>
<button class="btn btn-link"
@click="openAssignPiecesModal(item.id)" data-bs-target="#create-task" data-bs-
toggle="modal">
<i class="bi bi-plus-circle" title="Liste des
pièces"></i>
</button>
</td>
</tr>
<tr v-if="dossiers.length === 0">
<td colspan="7" class="text-center">
<h4 class="card">
<i class="bx bx-data"></i> Aucune donnée
</h4>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade" id="create-task" tabindex="-1" aria-
hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-body px-4">
<div class="table-responsive">
<table class="table text-nowrap">
<thead>
<tr>
<th></th>
<th>#</th>
<th>
<div class="d-flex align-items-center">
<span>Pièces</span>
<i class="ri-search-line text-success ml-auto"
@click="toggleSearch('piece_name')"></i>
<i :class="sortColumn === 'piece_name' ? (sortOrder
=== 'asc' ? 'ri-sort-desc' : 'ri-sort-asc') : 'ri-sort-asc'"
@click="sortColumnToggle('piece_name')"></i>
</div>
<input v-if="showSearch.piece_name" v-
model="columnSearch.piece_name" @input="filterConge" type="text" class="form-
control mt-2">
</th>
<th>Affecté</th>
</tr>
</thead>
<tbody>
<tr v-for="(piece, index) in pieces" :key="piece.id"
class="product-list">
<td class="task-checkbox">
<input
@click="toggleAssignment(index)"
v-model="piece.assigned"
ref="checkOption"
class="form-check-input"
type="checkbox"
:checked="piece.assigned"
aria-label="..."
/>
</td>
<td>{{ piece.id }}</td>
<td>{{ piece.libelle }}</td>
<td>
<div :class="['btn', 'btn-sm', piece.assigned ? 'btn-
light' : 'btn-danger-light']">
{{ piece.assigned ? 'oui' : 'non' }}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning" data-bs-
dismiss="modal" aria-label="Close">Retour</button>
<button type="button" class="btn btn-primary"
@click="assignPiecesToDossier(dossierTypeId)">Affecter pièce</button>
</div>
</div>
</div>
</div>
<!-- Pagination controls -->
<nav aria-label="Page navigation">
<ul class="pagination mb-0">
<li class="page-item" :class="{ disabled: currentPage === 1 }">
<a class="page-link" href="javascript:void(0);"
@click="prevPage">Précédent</a>
</li>
<li class="page-item" v-for="page in
totalPages" :key="page" :class="{ active: currentPage === page }">
<a class="page-link" href="javascript:void(0);"
@click="changePage(page)">{{ page }}</a>
</li>
<li class="page-item" :class="{ disabled: currentPage ===
totalPages }">
<a class="page-link" href="javascript:void(0);"
@click="nextPage">Suivant</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</template>
<script>
import PageHeader from "@/components/common/pageheader.vue";
import axios from "axios";
import Swal from "sweetalert2";
import Papa from "papaparse";
import * as XLSX from "xlsx";
import jsPDF from "jspdf";
import "jspdf-autotable";
export default {
name: 'liste dossier',
components: {
PageHeader,
},
data() {
return {
pieces: [], // Tableau des pièces récupérées de l'API
selectedPieceIds: [], // Tableau des IDs des pièces sélectionnées
dossierTypeId: null,
conges: [],
dossiers: [],
agentOptions: [],
code_type_dossier: {},
dataToPass: {
current: "Affectation type dossier pièce ",
list: ['Affectation type dossier pièce', 'Parametre']
},
searchValue: "",
currentPage: 1,
itemsPerPage: 10,
showSearch: {
index: false,
libelle: false,
code_type_dossier: false,
nature_dossier: false,
pieces1: false,
piece_name: false,
duree: false
},
columnSearch: {},
sortColumn: "",
sortOrder: "asc",
};
},
computed: {
filteredConges() {
return this.conges.filter(conge => {
const agentName = this.getAgentName(conge.type_dossier_id).toLowerCase();
return (
(!this.columnSearch.index ||
conge.index.toString().includes(this.columnSearch.index)) &&
(!this.columnSearch.code_type_dossier ||
agentName.includes(this.columnSearch.code_type_dossier.toLowerCase())) &&
(!this.columnSearch.libelle ||
conge.libelle.toLowerCase().includes(this.columnSearch.libelle.toLowerCase())) &&
(!this.columnSearch.nature_dossier ||
conge.nature_dossier.includes(this.columnSearch.nature_dossier)) &&
(!this.columnSearch.pieces1 ||
conge.pieces1.toLowerCase().includes(this.columnSearch.pieces1.toLowerCase())) &&
(!this.columnSearch.duree ||
conge.duree.toLowerCase().includes(this.columnSearch.duree.toLowerCase()))
);
});
},
sortedConges() {
const congeWithIndex = this.filteredConges.map((conge, index) => {
return { ...conge, index: index + 1 };
});
const sortedConges = congeWithIndex.slice().sort((a, b) => {
let modifier = 1;
if (this.sortOrder === "desc") modifier = -1;
if (this.sortColumn) {
let valueA, valueB;
if (this.sortColumn === 'code_type_dossier') {
valueA = this.getAgentName(a.type_dossier_id).toLowerCase();
valueB = this.getAgentName(b.type_dossier_id).toLowerCase();
} else {
valueA = a[this.sortColumn];
valueB = b[this.sortColumn];
}
if (valueA < valueB) return -1 * modifier;
if (valueA > valueB) return 1 * modifier;
}
return 0;
});
return sortedConges.map((conge, index) => {
return { ...conge, index: index + 1 };
});
},
paginatedConges() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.sortedConges.slice(start, end);
},
totalPages() {
return Math.ceil(this.sortedConges.length / this.itemsPerPage);
}
},
async mounted() {
await this.fetchDossiers();
await this.fetchPieces();
await this.getConges();
},
methods: {
toggleAssignment(index) {
// Inverse l'état de la case cochée
this.pieces[index].assigned = !this.pieces[index].assigned;
},
selectAllProducts() {
// Permet de tout sélectionner ou désélectionner
const allSelected = this.pieces.every(piece => piece.assigned);
this.pieces.forEach(piece => piece.assigned = !allSelected);
},
async fetchPieces() {
try {
const response = await fetch('http://127.0.0.1:8000/api/piece-a-fournir');
const data = await response.json();
this.pieces = data; // Stocker les pièces dans le tableau
} catch (error) {
console.error("Erreur lors de la récupération des pièces:", error);
}
},
openAssignPiecesModal(dossierTypeId) {
this.dossierTypeId = dossierTypeId; // Assigner l'ID du type de dossier
sélectionné
this.selectedPieceIds = []; // Réinitialiser la sélection des pièces
this.fetchPieces(); // Charger les pièces
$('#create-task').modal('show'); // Afficher le modal
},
// Méthode pour gérer la sélection/désélection des pièces
toggleAssignment(index) {
const piece = this.pieces[index];
piece.assigned = !piece.assigned; // Inverser l'état de l'affectation
if (piece.assigned) {
this.selectedPieceIds.push(piece.id); // Ajouter l'ID de la pièce
sélectionnée
} else {
// Retirer l'ID de la pièce désélectionnée
const indexToRemove = this.selectedPieceIds.indexOf(piece.id);
if (indexToRemove > -1) {
this.selectedPieceIds.splice(indexToRemove, 1);
}
}
},
async assignPiecesToDossier() {
try {
if (this.selectedPieceIds.length === 0) {
alert('Veuillez sélectionner au moins une pièce à affecter.');
return;
}
const response = await fetch(`http://127.0.0.1:8000/api/type-dossiers/$
{this.dossierTypeId}/attach-to-pieces`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
piece_a_fournir_ids: this.selectedPieceIds,
}),
});
if (response.ok) {
alert('Les pièces ont été affectées avec succès au type de dossier.');
$('#create-task').modal('hide');
this.fetchPieces();
} else {
alert("Erreur lors de l'affectation des pièces.");
}
} catch (error) {
console.error("Erreur lors de l'affectation des pièces:", error);
}
},
async fetchDossiers() {
try {
const response = await
axios.get('http://127.0.0.1:8000/api/typedossier-piece/liste/');
this.dossiers = response.data.map(dossier => {
return {
...dossier,
associatedPieces: dossier.piece_a_fournirs, // Only the pieces related to
this dossier type
nature_dossier: dossier.nature_dossier // Make sure nature_dossier is
included
};
});
} catch (error) {
console.error('Error fetching dossiers:', error);
}
},
downloadExcel() {
const worksheet = XLSX.utils.json_to_sheet(this.conges);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'conges');
XLSX.writeFile(workbook, `conges_${this.getCurrentDateTime()}.xlsx`);
},
downloadPDF() {
try {
const doc = new jsPDF();
const currentDateTime = this.getCurrentDateTime();
doc.setFontSize(20);
doc.setTextColor('#157e6f');
const title = 'CABINET VAD';
const titleWidth = doc.getStringUnitWidth(title) * 20 /
doc.internal.scaleFactor;
doc.text(title, (doc.internal.pageSize.width - titleWidth) / 2, 10);
doc.setFontSize(12);
doc.setTextColor('#000000');
const subtitle = 'Listes des conges';
const subtitleWidth = doc.getStringUnitWidth(subtitle) * 12 /
doc.internal.scaleFactor;
doc.text(subtitle, (doc.internal.pageSize.width - subtitleWidth) / 2,
17);
doc.setFontSize(10);
doc.text(currentDateTime, 10, doc.internal.pageSize.height - 10);
const footerText = 'Développé par Gescano';
const footerWidth = doc.getStringUnitWidth(footerText) * 10 /
doc.internal.scaleFactor;
doc.text(footerText, doc.internal.pageSize.width - footerWidth - 10,
doc.internal.pageSize.height - 10);
doc.autoTable({
head: [['#', 'Agent', 'Type', 'Date debut', 'Date fin', 'Durée']],
body: this.conges.map((conge, index) => [
index + 1,
this.getAgentName(conge.type_dossier_id),
conge.type,
conge._dossier,
conge.pieces1,
conge.duree
]),
startY: 25,
theme: 'striped',
headStyles: { fillColor: '#157e6f' }
});
doc.save(`conges_${currentDateTime.replace(/ /g, '_').replace(/:/g,
'-')}.pdf`);
} catch (error) {
console.error('Erreur lors de l\'exportation PDF:', error);
}
},
printPage() {
const printContent = document.createElement('div');
const table = document.querySelector('.table-responsive').cloneNode(true);
const rows = table.querySelectorAll('tr');
rows.forEach(row => {
const lastCell = row.lastElementChild;
if (lastCell) {
row.removeChild(lastCell);
}
});
const currentDateTime = this.getCurrentDateTime();
const header = `
<div style="text-align: center; font-size: 20px; color: #157e6f; margin-
bottom: 20px;">
CABINET VAD
</div>
<div style="text-align: center; font-size: 12px; color: #000000; margin-
bottom: 20px;">
Liste des congés
</div>
`;
const footer = `
<div style="position: fixed; bottom: 10px; left: 10px; font-size: 10px;">
${currentDateTime}
</div>
<div style="position: fixed; bottom: 10px; right: 10px; font-size:
10px;">
Développé par Gescano
</div>
`;
const tableStyle = `
<style>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
}
th {
background-color: #f4f4f4;
color: #333;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #e9e9e9;
}
</style>
`;
printContent.innerHTML = header + tableStyle + table.outerHTML + footer;
const printWindow = window.open('', '', 'width=800,height=600');
printWindow.document.write('<html><head><title>Impression</title>');
printWindow.document.write('</head><body>');
printWindow.document.write(printContent.innerHTML);
printWindow.document.write('</body></html>');
printWindow.document.close();
printWindow.print();
},
toggleSearch(column) {
this.showSearch[column] = !this.showSearch[column];
if (!this.showSearch[column]) {
this.columnSearch[column] = "";
}
},
sortColumnToggle(column) {
if (this.sortColumn === column) {
this.sortOrder = this.sortOrder === "asc" ? "desc" : "asc";
} else {
this.sortColumn = column;
this.sortOrder = "asc";
}
},
changePage(page) {
this.currentPage = page;
},
nextPage() {
if (this.currentPage < this.totalPages) {
this.currentPage++;
}
},
prevPage() {
if (this.currentPage > 1) {
this.currentPage--;
}
},
getCurrentDateTime() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
}
};
</script>
<style scoped>
/* Ajoutez vos styles CSS spécifiques si nécessaire */
</style>