Activity TimeGrid WIP

This commit is contained in:
NaNoMelo 2024-10-15 18:33:25 +02:00
parent 5e9ffe4d9e
commit f09827339f
4 changed files with 151 additions and 27 deletions

View File

@ -1,51 +1,130 @@
import { paginated } from "#core:utils/api"; import { paginated } from "#core:utils/api";
import { exportToHtml } from "#core:utils/globals"; import { exportToHtml } from "#core:utils/globals";
import { Chart } from "chart.js/auto";
import { import {
type PermanencyFetchPermananciesData, type PermanencyFetchPermananciesData,
type PermanencySchema,
permanencyFetchPermanancies, permanencyFetchPermanancies,
} from "#openapi"; } from "#openapi";
import { Calendar } from "@fullcalendar/core";
import timeGridPlugin from "@fullcalendar/timegrid";
interface ActivityChartConfig { interface ActivityChartConfig {
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
startDate: Date; startDate: Date;
counterId: number; counterId: number;
} }
// Get permanancies from the last week using the API interface OpeningTime {
start: Date;
end: Date;
}
exportToHtml("loadChart", loadChart); exportToHtml("loadChart", loadChart);
async function loadChart(options: ActivityChartConfig) { async function loadChart(options: ActivityChartConfig) {
const permanancies = paginated(permanencyFetchPermanancies, { const permanancies = await paginated(permanencyFetchPermanancies, {
query: { query: {
counter: [options.counterId], counter: [options.counterId],
// biome-ignore lint/style/useNamingConvention: backend API uses snake_case // biome-ignore lint/style/useNamingConvention: backend API uses snake_case
start_date: options.startDate.toISOString(), start_date: options.startDate.toString(),
}, },
} as PermanencyFetchPermananciesData).then((data) => { } as PermanencyFetchPermananciesData);
console.log(data);
}); const events = getEvents(permanancies);
const chart = new Chart(options.canvas, {
type: "bar", const calendar = new Calendar(options.canvas, {
data: { plugins: [timeGridPlugin],
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], initialView: "timeGridWeek",
datasets: [ locale: "fr",
{ slotLabelFormat: {
label: "# of Votes", hour: "2-digit",
data: [12, 19, 3, 5, 2, 3], minute: "2-digit",
borderWidth: 1, hour12: false,
},
],
}, },
options: { dayHeaderFormat: {
scales: { weekday: "long",
y: { },
beginAtZero: true, firstDay: 1,
}, views: {
timeGrid: {
allDaySlot: false,
}, },
}, },
scrollTime: "09:00:00",
headerToolbar: {
left: "",
center: "",
right: "",
},
//weekends: false,
events: events,
nowIndicator: true,
//slotDuration: "00:15:00",
height: 600,
}); });
calendar.render();
} }
console.log("Hello from graph-index.ts"); function getOpeningTimes(rawPermanancies: PermanencySchema[]) {
const permanancies = rawPermanancies
.map(convertPermanancyToOpeningTime)
.sort((a, b) => a.start.getTime() - b.start.getTime());
const openingTimes: OpeningTime[] = [];
for (const permanancy of permanancies) {
// if there are no opening times, add the first one
if (openingTimes.length === 0) {
openingTimes.push(permanancy);
} else {
const lastPermanancy = openingTimes[openingTimes.length - 1];
if (
// if the new permanancy starts before the 15 minutes following the end of the last one, merge them
new Date(permanancy.start).setMinutes(permanancy.start.getMinutes() - 15) <
lastPermanancy.end.getTime()
) {
lastPermanancy.end = new Date(
Math.max(lastPermanancy.end.getTime(), permanancy.end.getTime()),
);
} else {
openingTimes.push(permanancy);
}
}
}
return openingTimes;
}
function convertPermanancyToOpeningTime(permanancy: PermanencySchema): OpeningTime {
return {
start: new Date(permanancy.start),
end: permanancy.end ? new Date(permanancy.end) : new Date(),
};
}
function getEvents(permanancies: PermanencySchema[]) {
const openingTimes = getOpeningTimes(permanancies);
const events = [];
for (const openingTime of openingTimes) {
const lastMonday: Date = new Date();
lastMonday.setDate(new Date().getDate() - ((new Date().getDay() - 1) % 7));
lastMonday.setHours(0, 0, 0);
// if permanancies took place before monday (last week), display them in lightblue as part of the current week
if (openingTime.end < lastMonday) {
events.push({
start: new Date(openingTime.start).setDate(openingTime.start.getDate() + 7),
end: new Date(openingTime.end).setDate(openingTime.end.getDate() + 7),
backgroundColor: "lightblue",
});
} else {
events.push({
start: openingTime.start,
end: openingTime.end,
backgroundColor: "green",
});
}
}
//const openingTimesByDay = splitByDay(openingTimes);
return events;
}

View File

@ -5,7 +5,7 @@
{% trans counter_name=counter %}{{ counter_name }} activity{% endtrans %} {% trans counter_name=counter %}{{ counter_name }} activity{% endtrans %}
{% endblock %} {% endblock %}
{% block additionnal_js %} {% block additional_js %}
<script defer src="{{ static('webpack/graph-index.ts') }}"></script> <script defer src="{{ static('webpack/graph-index.ts') }}"></script>
{% endblock %} {% endblock %}
@ -27,7 +27,8 @@
{% endif %} {% endif %}
</ul> </ul>
<h4>{% trans %}Last Week Activity {% endtrans %}</h4> <h4>{% trans %}Last Week Activity {% endtrans %}</h4>
<canvas id="activityChart" width="400" height="200"></canvas> <div id="activityGraph" width="400" height="200"></div>
<br/>
{% endif %} {% endif %}
<h5>{% trans %}Legend{% endtrans %}</h5> <h5>{% trans %}Legend{% endtrans %}</h5>
@ -48,7 +49,7 @@
<script> <script>
window.addEventListener("DOMContentLoaded", () => { window.addEventListener("DOMContentLoaded", () => {
loadChart({ loadChart({
canvas: document.getElementById("activityChart"), canvas: document.getElementById("activityGraph"),
startDate: new Date().setDate(new Date().getDate() - 7), startDate: new Date().setDate(new Date().getDate() - 7),
counterId: {{ counter.id }}, counterId: {{ counter.id }},
}); });

42
package-lock.json generated
View File

@ -10,6 +10,8 @@
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/timegrid": "^6.1.15",
"@hey-api/client-fetch": "^0.4.0", "@hey-api/client-fetch": "^0.4.0",
"@sentry/browser": "^8.34.0", "@sentry/browser": "^8.34.0",
"@zip.js/zip.js": "^2.7.52", "@zip.js/zip.js": "^2.7.52",
@ -1930,6 +1932,36 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@fullcalendar/core": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
"integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==",
"license": "MIT",
"dependencies": {
"preact": "~10.12.1"
}
},
"node_modules/@fullcalendar/daygrid": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz",
"integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.15"
}
},
"node_modules/@fullcalendar/timegrid": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz",
"integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==",
"license": "MIT",
"dependencies": {
"@fullcalendar/daygrid": "~6.1.15"
},
"peerDependencies": {
"@fullcalendar/core": "~6.1.15"
}
},
"node_modules/@hey-api/client-fetch": { "node_modules/@hey-api/client-fetch": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.4.0.tgz", "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.4.0.tgz",
@ -5872,6 +5904,16 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true "dev": true
}, },
"node_modules/preact": {
"version": "10.12.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -43,6 +43,8 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/timegrid": "^6.1.15",
"@hey-api/client-fetch": "^0.4.0", "@hey-api/client-fetch": "^0.4.0",
"@sentry/browser": "^8.34.0", "@sentry/browser": "^8.34.0",
"@zip.js/zip.js": "^2.7.52", "@zip.js/zip.js": "^2.7.52",