put activities in an ajax call

This commit is contained in:
Charles 2025-12-15 13:52:54 +01:00
parent 9af81b1d2c
commit b5d56f1d85
7 changed files with 142 additions and 59 deletions

View File

@ -4,12 +4,26 @@ import {TabulatorFull as Tabulator} from 'tabulator-tables';
import {eyeIconLink, TABULATOR_FR_LANG} from "../js/global.js"; import {eyeIconLink, TABULATOR_FR_LANG} from "../js/global.js";
export default class extends Controller { export default class extends Controller {
static values = {aws: String}; static values = {aws: String,
id: String,
activities: Boolean,
table: Boolean,
};
static targets = ["activityList", "emptyMessage"]
connect() { connect() {
if(this.activitiesValue){
this.loadActivities();
setInterval(() => {
this.loadActivities();
}, 5000); // Refresh every 60 seconds
}
if (this.tableValue){
this.table(); this.table();
} }
}
table(){ table(){
const table = new Tabulator("#tabulator-org", { const table = new Tabulator("#tabulator-org", {
// Register locales here // Register locales here
@ -82,4 +96,59 @@ export default class extends Controller {
}], }],
}); });
} }
async loadActivities() {
try {
// 1. Fetch the data using the ID from values
const response = await fetch(`/actions/organization/${this.idValue}/activities-ajax`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const activities = await response.json();
// 2. Render
this.renderActivities(activities);
} catch (error) {
console.error('Error fetching activities:', error);
this.activityListTarget.innerHTML = `<div class="text-danger">Erreur lors du chargement.</div>`;
}
}
renderActivities(activities) {
// Clear the loading spinner
this.activityListTarget.innerHTML = '';
if (activities.length === 0) {
// Show empty message
this.activityListTarget.innerHTML = this.emptyMessageTarget.innerHTML;
return;
}
// Loop through JSON and build HTML
const html = activities.map(activity => {
return `
<div class="card shadow-sm mb-3 border-0 bg-white rounded-end"
style="border-left: 6px solid ${activity.color} !important;">
<div class="card-header bg-transparent border-0 pb-0 pt-3">
<h6 class="text-muted text-uppercase fw-bold mb-0" style="font-size: 0.85rem;">
${activity.date}
</h6>
</div>
<div class="card-body pt-2 pb-4">
<div class="card-text fs-5 lh-sm">
<span class="fw-bold text-dark">${activity.userName}</span>
<div class="text-secondary mt-1">${activity.actionType}</div>
</div>
</div>
</div>
`;
}).join('');
this.activityListTarget.innerHTML = html;
}
} }

View File

@ -0,0 +1,34 @@
<?php
namespace App\Controller;
use App\Entity\Actions;
use App\Entity\Organizations;
use App\Service\ActionService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
#[Route(path: '/actions', name: 'actions_')]
class ActionController extends AbstractController
{
public function __construct(
private EntityManagerInterface $entityManager,
private ActionService $actionService
) {
}
#[Route('/organization/{id}/activities-ajax', name: 'app_organization_activities_ajax', methods: ['GET'])]
public function fetchActivitiesAjax(Organizations $organization): JsonResponse
{
$actions = $this->entityManager->getRepository(Actions::class)->findBy(
['Organization' => $organization],
['date' => 'DESC'],
15
);
$formattedActivities = $this->actionService->formatActivities($actions);
return new JsonResponse($formattedActivities);
}
}

View File

@ -40,11 +40,11 @@ readonly class ActionService
{ {
return array_map(function (Actions $activity) { return array_map(function (Actions $activity) {
return [ return [
'date' => $activity->getDate(), 'date' => $activity->getDate()->format('d/m/Y H:i'),
'actionType' => $activity->getActionType(), 'actionType' => $activity->getActionType(),
'users' => $activity->getUsers(), 'userName' => $activity->getUsers()->getName(),
'organization' => $activity->getOrganization(), // 'organization' => $activity->getOrganization(),
'description' => $activity->getDescription(), // 'description' => $activity->getDescription(),
'color' => $this->getActivityColor($activity->getDate()) 'color' => $this->getActivityColor($activity->getDate())
]; ];
}, $activities); }, $activities);

View File

@ -1,25 +0,0 @@
{% block body %}
<div class="card border-0">
<div class="card-header d-flex justify-content-between align-items-center border-0">
<h3>{{ title }}</h3>
</div>
<div class="card-body">
{% if activities|length == 0 %}
<p>Aucune activité récente.</p>
{% else %}
{% set sortedActivities = activities|sort((a, b) => a.date <=> b.date)|reverse %}
<ul class="list-group gap-2">
{% for activity in sortedActivities%}
{% include 'user/organization/userActivity.html.twig' with {
activityTime: activity.date,
action: activity.actionType,
userName: activity.users.name,
color: activity.color
} %}
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock %}

View File

@ -35,6 +35,7 @@
{% else %} {% else %}
<div id="tabulator-org" data-controller="organization" <div id="tabulator-org" data-controller="organization"
data-organization-table-value="true"
data-organization-data-value="{{ organizationsData|json_encode(constant("JSON_UNESCAPED_UNICODE"))|e("html_attr") }}" data-organization-data-value="{{ organizationsData|json_encode(constant("JSON_UNESCAPED_UNICODE"))|e("html_attr") }}"
data-organization-aws-value="{{ aws_url }}"></div> data-organization-aws-value="{{ aws_url }}"></div>

View File

@ -39,9 +39,9 @@
</div> </div>
</div> </div>
{# single row so that activity and users tabs are next to each other#} {# single row so that activity and users tabs are next to each other #}
<div class="row"> <div class="row">
{# User tables #} {# User tables #}
<div class="col-9"> <div class="col-9">
<div class="row mb-3 d-flex gap-2 "> <div class="row mb-3 d-flex gap-2 ">
<div class="col mb-3 card no-header-bg"> <div class="col mb-3 card no-header-bg">
@ -49,7 +49,8 @@
<h2> <h2>
Nouveaux utilisateurs Nouveaux utilisateurs
</h2> </h2>
<a href="{{ path('user_new', {'organizationId': organization.id}) }}" class="btn btn-primary">Ajouter un utilisateur</a> <a href="{{ path('user_new', {'organizationId': organization.id}) }}"
class="btn btn-primary">Ajouter un utilisateur</a>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="tabulator-userListSmall" data-controller="user" <div id="tabulator-userListSmall" data-controller="user"
@ -93,7 +94,7 @@
</div> </div>
{# APPLICATION ROW #} {# APPLICATION ROW #}
{# TODO: Weird gap not going away#} {# TODO: Weird gap not going away #}
<div class="row mb-3 "> <div class="row mb-3 ">
{% for application in applications %} {% for application in applications %}
<div class="col-6 mb-3"> <div class="col-6 mb-3">
@ -104,16 +105,36 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{# Activities col#} {# Activities col #}
<div class="col-3 m-auto"> <div class="col-3 m-auto">
{% include 'organization/activity.html.twig' with { <div class="card border-0"
title: 'Activités récentes', data-controller="organization"
empty_message: 'Aucune activité récente.' data-organization-activities-value = "true"
} %} data-organization-id-value="{{ organization.id }}">
<div class="card-header d-flex justify-content-between align-items-center border-0">
<h3>Activité récente</h3>
<button class="btn btn-sm btn-outline-secondary" data-action="organization#loadActivities">
<i class="fas fa-sync"></i> Rafraîchir
</button>
</div> </div>
<div class="card-body bg-light">
<div class="d-flex flex-column" data-organization-target="activityList">
<div class="text-center text-muted p-5">
<span class="spinner-border" aria-hidden="true"></span>
</div> </div>
{# Ne pas enlever le 2ème /div#} </div>
{# Empty state #}
<div class="d-none" data-organization-target="emptyMessage">
<div class="alert alert-light text-center shadow-sm">Aucune activité récente.</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -1,17 +0,0 @@
{% block body %}
<div class="card border">
<div class="card-header d-flex align-items-center border-0">
<div class="row align-items-center">
<h4 class="mb-0">
<span style="display:inline-block; width:16px; height:16px; border-radius:50%; background:{{ color }}; margin-right:10px;"></span>
{{ activityTime|ago }}</h4>
</div>
</div>
<div class="card-body">
<p>{{ userName }} - {{ action }}</p>
</div>
</div>
{% endblock %}