Ce script ajoute automatiquement une frise chronologique interactive sur la page issuehistory.pl dans Koha. Il permet de visualiser l’historique des prêts par code-barres sous forme de barres chronologiques colorées, facilitant ainsi l’analyse des périodes d’emprunt d’un document.
issuehistory.pl.//Frise chronologique historique de prêt notices
if (window.location.href.includes("issuehistory.pl")) {
const script = document.createElement('script');
script.src = '<https://d3js.org/d3.v7.min.js>';
document.head.appendChild(script);
script.onload = function() {
const style = document.createElement('style');
style.innerHTML = `
.timeline-container-wrapper {
width: 100%;
overflow-x: scroll;
margin-top: 20px;
background-color: white;
}
.timeline-chart-container {
position: relative;
width: 100%;
height: 300px;
}
.custom-tooltip {
display: none;
position: absolute;
background-color: #333;
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
z-index: 10;
max-width: 350px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
white-space: pre-wrap;
word-wrap: break-word;
}
`;
document.head.appendChild(style);
function createEventData(name, detail, startDate, endDate) {
return {
label: `${name} - ${detail}`,
startDate: startDate,
endDate: endDate,
duration: (endDate - startDate) / (1000 * 3600 * 24)
};
}
function formatDate(date) {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}/${month}/${year}`;
}
function parseDate(dateString) {
const dateParts = dateString.split(' ')[0].split('/').map(Number); // Extraire la date sans l'heure
const timeParts = dateString.split(' ')[1]?.split(':'); // Extraire l'heure, si elle existe
if (dateParts.length === 3 && timeParts?.length === 2) {
const [day, month, year] = dateParts;
const [hours, minutes] = timeParts.map(Number);
const parsedDate = new Date(year, month - 1, day, hours, minutes);
console.log("Date parsée :", dateString, "->", parsedDate);
return parsedDate;
}
console.error("Erreur de parsing de la date :", dateString);
return new Date(); // Retourner la date actuelle en cas d'erreur
}
const timelineContainer = document.createElement("div");
timelineContainer.classList.add("timeline-container-wrapper");
const searchResultsContainer = document.querySelector('.searchresults');
if (searchResultsContainer) {
searchResultsContainer.appendChild(timelineContainer);
} else {
document.body.appendChild(timelineContainer);
}
const barcodeEvents = {};
let minDate = new Date();
let maxDate = new Date(0);
const table = document.getElementById("table_issues");
const rows = table.querySelectorAll("tbody tr");
rows.forEach(row => {
const cells = row.querySelectorAll("td");
if (cells.length < 8) return;
const name = cells[0].textContent.trim();
const barcode = cells[1].textContent.trim();
const detail = cells[2].textContent.trim();
const startDate = parseDate(cells[5].textContent.trim()); // Utilisation de parseDate ici
const endDate = cells[7] ? parseDate(cells[7].textContent.trim()) : new Date(startDate);
if (startDate < minDate) minDate = startDate;
if (endDate > maxDate) maxDate = endDate;
if (!barcodeEvents[barcode]) {
barcodeEvents[barcode] = { label: barcode, events: [] };
}
barcodeEvents[barcode].events.push(createEventData(name, detail, startDate, endDate));
});
const chartData = [];
Object.keys(barcodeEvents).forEach(barcode => {
barcodeEvents[barcode].events.forEach(event => {
const startPosition = (event.startDate - minDate) / (1000 * 3600 * 24);
const duration = event.duration;
chartData.push({
label: event.label,
startDate: startPosition,
duration: duration,
barcode: barcode
});
});
});
const width = 1000;
const height = 300;
const margin = { top: 20, right: 20, bottom: 50, left: 80 }; // Augmenter la marge gauche de 40 à 80
const xScale = d3.scaleLinear()
.domain([0, (maxDate - minDate) / (1000 * 3600 * 24)])
.range([margin.left, width - margin.right]);
const yScale = d3.scaleBand()
.domain(chartData.map(d => d.barcode))
.range([margin.top, height - margin.bottom])
.padding(0.1);
const svg = d3.select(timelineContainer)
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("style", "border: 1px solid #c9c9c9;");
// Ajouter les traits verticaux pour l'axe des X
svg.selectAll(".x-axis-line")
.data(xScale.ticks(10))
.enter()
.append("line")
.attr("class", "x-axis-line")
.attr("x1", d => xScale(d))
.attr("x2", d => xScale(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom)
.attr("stroke", "#ccc")
.attr("stroke-width", 1);
svg.selectAll(".event-bar")
.data(chartData)
.enter()
.append("rect")
.attr("class", "event-bar")
.attr("x", d => xScale(d.startDate))
.attr("y", d => yScale(d.barcode))
.attr("width", d => xScale(d.startDate + d.duration) - xScale(d.startDate))
.attr("height", yScale.bandwidth())
.attr("fill", "rgba(0, 123, 255, 0.5)")
.attr("stroke", "rgba(0, 123, 255, 0.5)")
.attr("stroke-width", 1)
.on("mouseover", function(event, d) {
const tooltip = d3.select(".custom-tooltip");
tooltip.style("display", "block")
.html(`<strong>${d.label}</strong><br/>
De: ${formatDate(new Date(minDate.getTime() + d.startDate * 1000 * 3600 * 24))}<br/>
À: ${formatDate(new Date(minDate.getTime() + (d.startDate + d.duration) * 1000 * 3600 * 24))}<br/>
Durée: ${Math.round(d.duration)} jours<br/>
Code-barres: ${d.barcode}`);
})
.on("mousemove", function(event) {
const tooltip = d3.select(".custom-tooltip");
tooltip.style("top", (event.pageY + 10) + "px")
.style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function() {
d3.select(".custom-tooltip").style("display", "none");
});
const tooltip = d3.select("body").append("div").attr("class", "custom-tooltip");
svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top / 2 + 10) // Ajout de 20px pour faire descendre le texte
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("font-weight", "bold")
.style("margin", "-15px")
.text("Historique de prêt");
svg.append("g")
.selectAll(".tick")
.data(xScale.ticks(10))
.enter()
.append("text")
.attr("x", d => xScale(d))
.attr("y", height - margin.bottom + 20)
.attr("text-anchor", "middle")
.text(d => formatDate(new Date(minDate.getTime() + d * 1000 * 3600 * 24)))
.style("font-size", "12px");
svg.append("g")
.selectAll(".tick")
.data(yScale.domain())
.enter()
.append("text")
.attr("x", margin.left - 10)
.attr("y", d => yScale(d) + yScale.bandwidth() / 2)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(d => d)
.style("font-size", "12px");
};
}
issuehistory.pl.