Kontakte - Telefonnummern und E-Mail-Adressen Bearbeiten ist drin
Dieser Commit ist enthalten in:
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?')) {
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren