Réalisation finale
This commit is contained in:
6
templates/membre/_delete_form.html.twig
Normal file
6
templates/membre/_delete_form.html.twig
Normal file
@@ -0,0 +1,6 @@
|
||||
<form method="post" action="{{ path('app_membre_delete', {'id': membre.id}) }}" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer ce membre ?');">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ membre.id) }}">
|
||||
<button class="btn btn-danger">
|
||||
<i class="fas fa-trash"></i> Supprimer
|
||||
</button>
|
||||
</form>
|
||||
26
templates/membre/_form.html.twig
Normal file
26
templates/membre/_form.html.twig
Normal file
@@ -0,0 +1,26 @@
|
||||
{{ form_start(form) }}
|
||||
<div class="mb-3">
|
||||
{{ form_label(form.nom) }}
|
||||
{{ form_widget(form.nom) }}
|
||||
{{ form_errors(form.nom) }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form_label(form.prenom) }}
|
||||
{{ form_widget(form.prenom) }}
|
||||
{{ form_errors(form.prenom) }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form_label(form.email) }}
|
||||
{{ form_widget(form.email) }}
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-save"></i> {{ button_label|default('Enregistrer') }}
|
||||
</button>
|
||||
<a href="{{ path('app_membre_index') }}" class="btn btn-secondary">Annuler</a>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
27
templates/membre/edit.html.twig
Normal file
27
templates/membre/edit.html.twig
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Modifier Membre - {{ membre }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Modifier Membre</h1>
|
||||
<div>
|
||||
<a href="{{ path('app_membre_show', {'id': membre.id}) }}" class="btn btn-info">
|
||||
<i class="fas fa-eye"></i> Voir
|
||||
</a>
|
||||
<a href="{{ path('app_membre_index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Retour à la liste
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{ include('membre/_form.html.twig', {'button_label': 'Enregistrer'}) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
169
templates/membre/index.html.twig
Normal file
169
templates/membre/index.html.twig
Normal file
@@ -0,0 +1,169 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Membres{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Membres</h1>
|
||||
<a href="{{ path('app_membre_new') }}" class="btn btn-success">
|
||||
<i class="fas fa-plus"></i> Nouveau Membre
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Édition inline :</strong> Cliquez sur les cellules nom, prénom ou email pour les modifier directement.
|
||||
<br><small>Les modifications sont enregistrées automatiquement. Vérifiez la console (F12) pour les logs.</small>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Nom</th>
|
||||
<th>Prénom</th>
|
||||
<th>Email</th>
|
||||
<th>Contributions</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for membre in membres %}
|
||||
<tr data-membre-id="{{ membre.id }}">
|
||||
<td>{{ membre.id }}</td>
|
||||
<td class="editable-cell"
|
||||
data-entity-type="Membre"
|
||||
data-entity-id="{{ membre.id }}"
|
||||
data-field="nom"
|
||||
title="Cliquez pour modifier">
|
||||
{{ membre.nom|default('') }}
|
||||
</td>
|
||||
<td class="editable-cell"
|
||||
data-entity-type="Membre"
|
||||
data-entity-id="{{ membre.id }}"
|
||||
data-field="prenom"
|
||||
title="Cliquez pour modifier">
|
||||
{{ membre.prenom|default('') }}
|
||||
</td>
|
||||
<td class="editable-cell"
|
||||
data-entity-type="Membre"
|
||||
data-entity-id="{{ membre.id }}"
|
||||
data-field="email"
|
||||
title="Cliquez pour modifier">
|
||||
{{ membre.email|default('') }}
|
||||
</td>
|
||||
<td>{{ membre.contributions|length }}</td>
|
||||
<td>
|
||||
<a href="{{ path('app_membre_show', {'id': membre.id}) }}" class="btn btn-sm btn-info">
|
||||
<i class="fas fa-eye"></i> Voir
|
||||
</a>
|
||||
<a href="{{ path('app_membre_edit', {'id': membre.id}) }}" class="btn btn-sm btn-warning">
|
||||
<i class="fas fa-edit"></i> Modifier
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">Aucun membre trouvé</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const editableCells = document.querySelectorAll('.editable-cell');
|
||||
|
||||
editableCells.forEach(cell => {
|
||||
cell.addEventListener('click', function() {
|
||||
// Ne rien faire si la cellule est déjà en mode édition
|
||||
if (cell.querySelector('input')) return;
|
||||
|
||||
const currentValue = cell.textContent.trim();
|
||||
const field = cell.dataset.field;
|
||||
const entityId = cell.dataset.entityId;
|
||||
const entityType = cell.dataset.entityType;
|
||||
|
||||
// Créer un input pour l'édition
|
||||
const input = document.createElement('input');
|
||||
input.type = field === 'email' ? 'email' : 'text';
|
||||
input.value = currentValue;
|
||||
input.classList.add('form-control', 'form-control-sm');
|
||||
|
||||
// Style pour l'input
|
||||
input.style.width = '100%';
|
||||
input.style.boxSizing = 'border-box';
|
||||
|
||||
// Remplacer le contenu de la cellule par l'input
|
||||
cell.innerHTML = '';
|
||||
cell.appendChild(input);
|
||||
input.focus();
|
||||
|
||||
// Gestion de la sauvegarde
|
||||
const saveChanges = async () => {
|
||||
const newValue = input.value.trim();
|
||||
|
||||
// Ne rien faire si la valeur n'a pas changé
|
||||
if (newValue === currentValue) {
|
||||
cell.textContent = currentValue;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Envoyer la requête AJAX
|
||||
const response = await fetch('{{ path('app_membre_update_field') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: entityId,
|
||||
field: field,
|
||||
value: newValue
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
cell.textContent = newValue;
|
||||
cell.classList.add('bg-success', 'text-white');
|
||||
setTimeout(() => {
|
||||
cell.classList.remove('bg-success', 'text-white');
|
||||
}, 1000);
|
||||
} else {
|
||||
throw new Error(data.message || 'Erreur lors de la mise à jour');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
cell.textContent = currentValue;
|
||||
alert('Erreur lors de la sauvegarde: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// Sauvegarder lors de la perte de focus
|
||||
input.addEventListener('blur', saveChanges);
|
||||
|
||||
// Sauvegarder avec la touche Entrée
|
||||
input.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
input.blur();
|
||||
}
|
||||
});
|
||||
|
||||
// Annuler avec la touche Échap
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
cell.textContent = currentValue;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
22
templates/membre/new.html.twig
Normal file
22
templates/membre/new.html.twig
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Nouveau Membre{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Nouveau Membre</h1>
|
||||
<a href="{{ path('app_membre_index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Retour à la liste
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{ include('membre/_form.html.twig') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
92
templates/membre/show.html.twig
Normal file
92
templates/membre/show.html.twig
Normal file
@@ -0,0 +1,92 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Membre - {{ membre }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>{{ membre }}</h1>
|
||||
<div>
|
||||
<a href="{{ path('app_membre_edit', {'id': membre.id}) }}" class="btn btn-warning">
|
||||
<i class="fas fa-edit"></i> Modifier
|
||||
</a>
|
||||
<a href="{{ path('app_membre_index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Retour à la liste
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Informations générales</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>ID :</strong> {{ membre.id }}</p>
|
||||
<p><strong>Nom :</strong> {{ membre.nom }}</p>
|
||||
<p><strong>Prénom :</strong> {{ membre.prenom }}</p>
|
||||
<p><strong>Email :</strong> {{ membre.email }}</p>
|
||||
<p><strong>Nombre de contributions :</strong> {{ membre.contributions|length }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if membre.contributions|length > 0 %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Contributions ({{ membre.contributions|length }})</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Projet</th>
|
||||
<th>Date</th>
|
||||
<th>Durée</th>
|
||||
<th>Commentaire</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for contribution in membre.contributions %}
|
||||
<tr>
|
||||
<td>{{ contribution.projet.nom }}</td>
|
||||
<td>{{ contribution.dateContribution|date('d/m/Y') }}</td>
|
||||
<td>{{ contribution.dureeFormatee }}</td>
|
||||
<td>{{ contribution.commentaire|default('')|slice(0, 30) }}{% if contribution.commentaire|length > 30 %}...{% endif %}</td>
|
||||
<td>
|
||||
<a href="{{ path('app_contribution_show', {'id': contribution.id}) }}" class="btn btn-sm btn-info">
|
||||
<i class="fas fa-eye"></i> Voir
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Actions</h5>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ path('app_membre_edit', {'id': membre.id}) }}" class="btn btn-warning">
|
||||
<i class="fas fa-edit"></i> Modifier
|
||||
</a>
|
||||
{{ include('membre/_delete_form.html.twig') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user