Kontakte - Telefonnummern und E-Mail-Adressen Bearbeiten ist drin
Dieser Commit ist enthalten in:
@@ -213,6 +213,35 @@ class LeadRepository:
|
|||||||
|
|
||||||
return result
|
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:
|
def delete_contact_detail(self, detail_id: UUID) -> bool:
|
||||||
with self.get_db_connection() as conn:
|
with self.get_db_connection() as conn:
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|||||||
@@ -165,6 +165,24 @@ def add_email(contact_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
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'])
|
@leads_bp.route('/api/details/<uuid:detail_id>', methods=['DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
def delete_detail(detail_id):
|
def delete_detail(detail_id):
|
||||||
|
|||||||
@@ -114,6 +114,27 @@ class LeadService:
|
|||||||
|
|
||||||
return detail
|
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:
|
def delete_contact_detail(self, detail_id: UUID, user: str) -> bool:
|
||||||
"""Delete a contact detail (phone/email)"""
|
"""Delete a contact detail (phone/email)"""
|
||||||
success = self.repo.delete_contact_detail(detail_id)
|
success = self.repo.delete_contact_detail(detail_id)
|
||||||
|
|||||||
@@ -51,10 +51,16 @@
|
|||||||
<span class="badge bg-secondary">{{ phone.detail_label }}</span>
|
<span class="badge bg-secondary">{{ phone.detail_label }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm btn-outline-danger"
|
<div>
|
||||||
onclick="deleteDetail('{{ phone.id }}')">
|
<button class="btn btn-sm btn-outline-primary"
|
||||||
<i class="bi bi-trash"></i>
|
onclick="editDetail('{{ phone.id }}', '{{ phone.detail_value }}', '{{ phone.detail_label or '' }}', 'phone')">
|
||||||
</button>
|
<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>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -83,10 +89,16 @@
|
|||||||
<span class="badge bg-secondary">{{ email.detail_label }}</span>
|
<span class="badge bg-secondary">{{ email.detail_label }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm btn-outline-danger"
|
<div>
|
||||||
onclick="deleteDetail('{{ email.id }}')">
|
<button class="btn btn-sm btn-outline-primary"
|
||||||
<i class="bi bi-trash"></i>
|
onclick="editDetail('{{ email.id }}', '{{ email.detail_value }}', '{{ email.detail_label or '' }}', 'email')">
|
||||||
</button>
|
<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>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -267,6 +279,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<script>
|
||||||
const contactId = '{{ contact.id }}';
|
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
|
// Delete detail
|
||||||
async function deleteDetail(detailId) {
|
async function deleteDetail(detailId) {
|
||||||
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren