feat: Add user edit functionality in admin panel

- Add /admin/users/<id>/update endpoint to update name, email, phone
- Add edit button with pencil icon to users table
- Add edit modal with form fields
- Add JavaScript functions for edit modal handling
This commit is contained in:
Maciej Pienczyn 2026-01-13 21:17:34 +01:00
parent 6b7046bf49
commit d82dc0caec
2 changed files with 143 additions and 0 deletions

55
app.py
View File

@ -2094,6 +2094,61 @@ def admin_user_toggle_verified(user_id):
db.close()
@app.route('/admin/users/<int:user_id>/update', methods=['POST'])
@login_required
def admin_user_update(user_id):
"""Update user data (name, email)"""
if not current_user.is_admin:
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
db = SessionLocal()
try:
user = db.query(User).filter(User.id == user_id).first()
if not user:
return jsonify({'success': False, 'error': 'Użytkownik nie znaleziony'}), 404
data = request.get_json() or {}
# Update name if provided
if 'name' in data:
user.name = data['name'].strip() if data['name'] else None
# Update email if provided (with validation)
if 'email' in data:
new_email = data['email'].strip().lower()
if new_email and new_email != user.email:
# Check if email already exists
existing = db.query(User).filter(User.email == new_email, User.id != user_id).first()
if existing:
return jsonify({'success': False, 'error': 'Ten email jest już używany'}), 400
user.email = new_email
# Update phone if provided
if 'phone' in data:
user.phone = data['phone'].strip() if data['phone'] else None
db.commit()
logger.info(f"Admin {current_user.email} updated user {user.email}: name={user.name}, phone={user.phone}")
return jsonify({
'success': True,
'user': {
'id': user.id,
'name': user.name,
'email': user.email,
'phone': user.phone
},
'message': 'Dane użytkownika zaktualizowane'
})
except Exception as e:
db.rollback()
logger.error(f"Error updating user {user_id}: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@app.route('/admin/users/<int:user_id>/assign-company', methods=['POST'])
@login_required
def admin_user_assign_company(user_id):

View File

@ -1127,6 +1127,15 @@
</td>
<td>
<div class="action-buttons">
<!-- Edit User -->
<button class="btn-icon"
onclick="openEditUserModal({{ user.id }}, '{{ user.name|e if user.name else '' }}', '{{ user.email|e }}', '{{ user.phone|e if user.phone else '' }}')"
title="Edytuj dane">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</button>
<!-- Toggle Admin -->
<button class="btn-icon admin-toggle {{ 'active' if user.is_admin else '' }}"
onclick="toggleAdmin({{ user.id }})"
@ -1309,6 +1318,31 @@
</div>
</div>
<!-- Edit User Modal -->
<div id="editUserModal" class="modal">
<div class="modal-content">
<div class="modal-header">Edytuj użytkownika</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">Imię i nazwisko</label>
<input type="text" id="editUserName" class="form-control" placeholder="Jan Kowalski">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input type="email" id="editUserEmail" class="form-control" placeholder="jan@firma.pl">
</div>
<div class="form-group">
<label class="form-label">Telefon</label>
<input type="text" id="editUserPhone" class="form-control" placeholder="+48 123 456 789">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeEditUserModal()">Anuluj</button>
<button class="btn btn-primary" onclick="saveEditUser()">Zapisz zmiany</button>
</div>
</div>
</div>
<!-- Confirmation Modal -->
<div id="confirmModal" class="modal">
<div class="modal-content">
@ -1517,6 +1551,60 @@ Lub format CSV, Excel, lista emaili..."></textarea>
const csrfToken = '{{ csrf_token() }}';
let currentUserId = null;
let confirmCallback = null;
let editUserId = null;
// Edit User functions
function openEditUserModal(userId, name, email, phone) {
editUserId = userId;
document.getElementById('editUserName').value = name || '';
document.getElementById('editUserEmail').value = email || '';
document.getElementById('editUserPhone').value = phone || '';
document.getElementById('editUserModal').classList.add('active');
}
function closeEditUserModal() {
editUserId = null;
document.getElementById('editUserModal').classList.remove('active');
}
async function saveEditUser() {
if (!editUserId) return;
const name = document.getElementById('editUserName').value.trim();
const email = document.getElementById('editUserEmail').value.trim();
const phone = document.getElementById('editUserPhone').value.trim();
if (!email) {
showToast('Email jest wymagany', 'error');
return;
}
try {
const response = await fetch(`/admin/users/${editUserId}/update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({
name: name || null,
email: email,
phone: phone || null
})
});
const data = await response.json();
if (data.success) {
closeEditUserModal();
showToast(data.message || 'Zapisano zmiany', 'success');
location.reload();
} else {
showToast(data.error || 'Wystąpił błąd', 'error');
}
} catch (error) {
showToast('Błąd połączenia', 'error');
}
}
// Toast notification system
function showToast(message, type = 'success') {