From 074ab6a62a6a8d2e7d2268612f5c74b2188c2daa Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 17 Mar 2026 22:08:05 +0100 Subject: [PATCH] feat(email): add deadline and enhanced company search in email-to-sag flow --- RELEASE_NOTES_v2.2.54.md | 28 ++++++++ app/emails/backend/router.py | 31 ++++++-- app/emails/frontend/emails.html | 121 +++++++++++++++++++++++++++++--- 3 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 RELEASE_NOTES_v2.2.54.md diff --git a/RELEASE_NOTES_v2.2.54.md b/RELEASE_NOTES_v2.2.54.md new file mode 100644 index 0000000..8973260 --- /dev/null +++ b/RELEASE_NOTES_v2.2.54.md @@ -0,0 +1,28 @@ +# Release Notes - v2.2.54 + +Dato: 17. marts 2026 + +## Fokus + +Forbedringer i email til SAG workflow med deadline-felt og markant bedre firma/kunde-søgning i UI. + +## Tilføjet + +- Deadline understøttes nu i email->sag oprettelse. + - Backend request-model udvidet med `deadline`. + - `create-sag` gemmer nu deadline på `sag_sager`. + - Frontend forslagspanel har fået dedikeret deadline-felt. +- Kundevalg i email-panelet er opgraderet til en “super firma-søgning”: + - Live dropdown-resultater i stedet for simpel datalist. + - Bedre ranking af resultater (exact/prefix/relevans). + - Hurtig valg med klik, inklusive visning af CVR/domæne/email metadata. + +## Opdaterede filer + +- `app/emails/backend/router.py` +- `app/emails/frontend/emails.html` + +## Bemærkninger + +- Ingen breaking API changes. +- Ingen ekstra migration nødvendig for denne release. diff --git a/app/emails/backend/router.py b/app/emails/backend/router.py index 5e03dba..3cc0675 100644 --- a/app/emails/backend/router.py +++ b/app/emails/backend/router.py @@ -155,6 +155,7 @@ class CreateSagFromEmailRequest(BaseModel): case_type: str = "support" secondary_label: Optional[str] = None start_date: Optional[date] = None + deadline: Optional[date] = None priority: Optional[str] = None ansvarlig_bruger_id: Optional[int] = None assigned_group_id: Optional[int] = None @@ -202,10 +203,25 @@ async def get_sag_assignment_options(): async def search_customers(q: str = Query(..., min_length=1), limit: int = Query(20, ge=1, le=100)): """Autocomplete customers for email-to-case flow.""" try: - like = f"%{q.strip()}%" + q_clean = q.strip() + like = f"%{q_clean}%" + prefix = f"{q_clean}%" rows = execute_query( """ - SELECT id, name, email_domain + SELECT + id, + name, + email, + email_domain, + cvr_number, + CASE + WHEN LOWER(name) = LOWER(%s) THEN 500 + WHEN LOWER(name) LIKE LOWER(%s) THEN 300 + WHEN COALESCE(email_domain, '') ILIKE %s THEN 200 + WHEN COALESCE(cvr_number, '') ILIKE %s THEN 180 + WHEN COALESCE(email, '') ILIKE %s THEN 120 + ELSE 50 + END AS rank_score FROM customers WHERE ( name ILIKE %s @@ -213,10 +229,10 @@ async def search_customers(q: str = Query(..., min_length=1), limit: int = Query OR COALESCE(email_domain, '') ILIKE %s OR COALESCE(cvr_number, '') ILIKE %s ) - ORDER BY name + ORDER BY rank_score DESC, name ASC LIMIT %s """, - (like, like, like, like, limit) + (q_clean, prefix, prefix, prefix, prefix, like, like, like, like, limit) ) return rows or [] except Exception as e: @@ -507,9 +523,9 @@ async def create_sag_from_email(email_id: int, payload: CreateSagFromEmailReques case_result = execute_query( """ INSERT INTO sag_sager - (titel, beskrivelse, template_key, status, customer_id, ansvarlig_bruger_id, assigned_group_id, created_by_user_id, priority, start_date) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - RETURNING id, titel, customer_id, status, template_key, priority, start_date, created_at + (titel, beskrivelse, template_key, status, customer_id, ansvarlig_bruger_id, assigned_group_id, created_by_user_id, priority, start_date, deadline) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + RETURNING id, titel, customer_id, status, template_key, priority, start_date, deadline, created_at """, ( titel, @@ -522,6 +538,7 @@ async def create_sag_from_email(email_id: int, payload: CreateSagFromEmailReques payload.created_by_user_id, priority, payload.start_date, + payload.deadline, ) ) if not case_result: diff --git a/app/emails/frontend/emails.html b/app/emails/frontend/emails.html index 331a48e..9da3bba 100644 --- a/app/emails/frontend/emails.html +++ b/app/emails/frontend/emails.html @@ -475,6 +475,48 @@ gap: 0.5rem; flex-wrap: wrap; } + + .customer-search-wrap { + position: relative; + } + + .customer-search-results { + position: absolute; + left: 0; + right: 0; + top: calc(100% + 4px); + background: var(--bg-card); + border: 1px solid rgba(0,0,0,0.12); + border-radius: 10px; + max-height: 260px; + overflow-y: auto; + z-index: 12; + box-shadow: 0 8px 24px rgba(0,0,0,0.12); + } + + .customer-search-item { + padding: 0.55rem 0.65rem; + border-bottom: 1px solid rgba(0,0,0,0.06); + cursor: pointer; + } + + .customer-search-item:last-child { + border-bottom: none; + } + + .customer-search-item:hover { + background: var(--accent-light); + } + + .customer-search-name { + font-size: 0.85rem; + font-weight: 600; + } + + .customer-search-meta { + font-size: 0.74rem; + color: var(--text-secondary); + } /* Responsive Design */ @media (max-width: 1200px) { @@ -1355,6 +1397,7 @@ let selectedEmails = new Set(); let emailSearchTimeout = null; let autoRefreshInterval = null; let sagAssignmentOptions = { users: [], groups: [] }; +let customerSearchHideTimeout = null; // Initialize document.addEventListener('DOMContentLoaded', () => { @@ -1854,8 +1897,10 @@ function renderEmailAnalysis(email) {
- - +
+ + +
@@ -1880,6 +1925,11 @@ function renderEmailAnalysis(email) { +
+ + +
+