Kontakte - Telefonnummern und E-Mail-Adressen Bearbeiten ist drin

Dieser Commit ist enthalten in:
2025-06-19 18:10:48 +02:00
Ursprung 9e5843afcf
Commit b822504413
4 geänderte Dateien mit 196 neuen und 8 gelöschten Zeilen

Datei anzeigen

@@ -213,6 +213,35 @@ class LeadRepository:
return result
def get_contact_detail_by_id(self, detail_id: UUID) -> Optional[Dict[str, Any]]:
with self.get_db_connection() as conn:
cur = conn.cursor(cursor_factory=RealDictCursor)
query = "SELECT * FROM lead_contact_details WHERE id = %s"
cur.execute(query, (str(detail_id),))
result = cur.fetchone()
cur.close()
return result
def update_contact_detail(self, detail_id: UUID, detail_value: str,
detail_label: str = None) -> Dict[str, Any]:
with self.get_db_connection() as conn:
cur = conn.cursor(cursor_factory=RealDictCursor)
query = """
UPDATE lead_contact_details
SET detail_value = %s, detail_label = %s, updated_at = CURRENT_TIMESTAMP
WHERE id = %s
RETURNING *
"""
cur.execute(query, (detail_value, detail_label, str(detail_id)))
result = cur.fetchone()
cur.close()
return result
def delete_contact_detail(self, detail_id: UUID) -> bool:
with self.get_db_connection() as conn:
cur = conn.cursor()

Datei anzeigen

@@ -165,6 +165,24 @@ def add_email(contact_id):
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@leads_bp.route('/api/details/<uuid:detail_id>', methods=['PUT'])
@login_required
def update_detail(detail_id):
"""Update contact detail (phone/email)"""
try:
data = request.get_json()
detail = lead_service.update_contact_detail(
detail_id,
data['detail_value'],
data.get('detail_label'),
flask_session.get('username')
)
return jsonify({'success': True, 'detail': detail})
except ValueError as e:
return jsonify({'success': False, 'error': str(e)}), 400
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@leads_bp.route('/api/details/<uuid:detail_id>', methods=['DELETE'])
@login_required
def delete_detail(detail_id):

Datei anzeigen

@@ -114,6 +114,27 @@ class LeadService:
return detail
def update_contact_detail(self, detail_id: UUID, detail_value: str,
detail_label: str = None, user: str = None) -> Dict[str, Any]:
"""Update a contact detail (phone/email)"""
if not detail_value or len(detail_value.strip()) == 0:
raise ValueError("Detail value cannot be empty")
# Get current detail to check type
current_detail = self.repo.get_contact_detail_by_id(detail_id)
if not current_detail:
raise ValueError("Contact detail not found")
# Validation based on type
if current_detail['detail_type'] == 'email' and '@' not in detail_value:
raise ValueError("Invalid email format")
detail = self.repo.update_contact_detail(
detail_id, detail_value.strip(), detail_label
)
return detail
def delete_contact_detail(self, detail_id: UUID, user: str) -> bool:
"""Delete a contact detail (phone/email)"""
success = self.repo.delete_contact_detail(detail_id)

Datei anzeigen

@@ -51,10 +51,16 @@
<span class="badge bg-secondary">{{ phone.detail_label }}</span>
{% endif %}
</div>
<div>
<button class="btn btn-sm btn-outline-primary"
onclick="editDetail('{{ phone.id }}', '{{ phone.detail_value }}', '{{ phone.detail_label or '' }}', 'phone')">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger"
onclick="deleteDetail('{{ phone.id }}')">
<i class="bi bi-trash"></i>
</button>
</div>
</li>
{% endfor %}
</ul>
@@ -83,10 +89,16 @@
<span class="badge bg-secondary">{{ email.detail_label }}</span>
{% endif %}
</div>
<div>
<button class="btn btn-sm btn-outline-primary"
onclick="editDetail('{{ email.id }}', '{{ email.detail_value }}', '{{ email.detail_label or '' }}', 'email')">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger"
onclick="deleteDetail('{{ email.id }}')">
<i class="bi bi-trash"></i>
</button>
</div>
</li>
{% endfor %}
</ul>
@@ -267,6 +279,38 @@
</div>
</div>
<!-- Edit Detail Modal -->
<div class="modal fade" id="editDetailModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editDetailTitle">Detail bearbeiten</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editDetailForm">
<input type="hidden" id="editDetailId">
<input type="hidden" id="editDetailType">
<div class="mb-3">
<label for="editDetailValue" class="form-label" id="editDetailValueLabel">Wert</label>
<input type="text" class="form-control" id="editDetailValue" required>
</div>
<div class="mb-3">
<label for="editDetailLabel" class="form-label">Typ</label>
<select class="form-select" id="editDetailLabel">
<option value="">Bitte wählen...</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-primary" onclick="updateDetail()">Speichern</button>
</div>
</div>
</div>
</div>
<script>
const contactId = '{{ contact.id }}';
@@ -379,6 +423,82 @@ async function saveEmail() {
}
}
// Edit detail
function editDetail(detailId, detailValue, detailLabel, detailType) {
document.getElementById('editDetailId').value = detailId;
document.getElementById('editDetailType').value = detailType;
document.getElementById('editDetailValue').value = detailValue;
// Set appropriate input type and options based on detail type
const valueInput = document.getElementById('editDetailValue');
const labelSelect = document.getElementById('editDetailLabel');
const valueLabel = document.getElementById('editDetailValueLabel');
labelSelect.innerHTML = '<option value="">Bitte wählen...</option>';
if (detailType === 'phone') {
valueInput.type = 'tel';
valueLabel.textContent = 'Telefonnummer';
document.getElementById('editDetailTitle').textContent = 'Telefonnummer bearbeiten';
// Add phone type options
['Mobil', 'Geschäftlich', 'Privat', 'Fax'].forEach(type => {
const option = document.createElement('option');
option.value = type;
option.textContent = type;
if (type === detailLabel) option.selected = true;
labelSelect.appendChild(option);
});
} else if (detailType === 'email') {
valueInput.type = 'email';
valueLabel.textContent = 'E-Mail-Adresse';
document.getElementById('editDetailTitle').textContent = 'E-Mail-Adresse bearbeiten';
// Add email type options
['Geschäftlich', 'Privat'].forEach(type => {
const option = document.createElement('option');
option.value = type;
option.textContent = type;
if (type === detailLabel) option.selected = true;
labelSelect.appendChild(option);
});
}
new bootstrap.Modal(document.getElementById('editDetailModal')).show();
}
// Update detail
async function updateDetail() {
const detailId = document.getElementById('editDetailId').value;
const detailValue = document.getElementById('editDetailValue').value.trim();
const detailLabel = document.getElementById('editDetailLabel').value;
if (!detailValue) {
alert('Bitte geben Sie einen Wert ein.');
return;
}
try {
const response = await fetch(`/leads/api/details/${detailId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
detail_value: detailValue,
detail_label: detailLabel
})
});
const data = await response.json();
if (data.success) {
location.reload();
} else {
alert('Fehler: ' + (data.error || 'Unbekannter Fehler'));
}
} catch (error) {
alert('Fehler beim Speichern: ' + error.message);
}
}
// Delete detail
async function deleteDetail(detailId) {
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {