Skip to content

Commit

Permalink
✨ [#2149] Display new answer header for mijn vragen list
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenbal committed Mar 5, 2024
1 parent 7104017 commit 88bb783
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 52 deletions.
43 changes: 38 additions & 5 deletions src/open_inwoner/accounts/views/contactmoments.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging
from datetime import datetime
from typing import TypedDict
from datetime import datetime, timedelta
from typing import Optional, TypedDict

from django.conf import settings
from django.contrib.auth.mixins import AccessMixin
from django.http import Http404
from django.shortcuts import redirect
Expand All @@ -15,14 +16,16 @@
from open_inwoner.openklant.api_models import KlantContactMoment
from open_inwoner.openklant.clients import build_client
from open_inwoner.openklant.constants import Status
from open_inwoner.openklant.models import ContactFormSubject
from open_inwoner.openklant.models import ContactFormSubject, KlantContactMomentLocal
from open_inwoner.openklant.wrap import (
fetch_klantcontactmoment,
fetch_klantcontactmomenten,
get_fetch_parameters,
get_local_kcm_mapping,
)
from open_inwoner.openzaak.clients import build_client as build_client_openzaak
from open_inwoner.utils.mixins import PaginationMixin
from open_inwoner.utils.time import is_new
from open_inwoner.utils.views import CommonPageMixin

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -69,12 +72,27 @@ class KCMDict(TypedDict):
onderwerp: str
status: str
antwoord: str
new_answer_available: bool


class KlantContactMomentBaseView(
CommonPageMixin, BaseBreadcrumbMixin, KlantContactMomentAccessMixin, TemplateView
):
def get_kcm_data(self, kcm: KlantContactMoment) -> KCMDict:
def get_kcm_data(
self,
kcm: KlantContactMoment,
local_kcm_mapping: Optional[dict[str, KlantContactMomentLocal]] = None,
) -> KCMDict:
_is_new = is_new(
kcm.contactmoment,
"registratiedatum",
timedelta(days=settings.CONTACTMOMENT_NEW_DAYS),
)
if local_kcm_mapping:
is_seen = getattr(local_kcm_mapping.get(kcm.url), "is_seen", False)
else:
# In the detail view, this is automatically true
is_seen = True
data = {
"registered_date": kcm.contactmoment.registratiedatum,
"channel": kcm.contactmoment.kanaal.title(),
Expand All @@ -85,6 +103,9 @@ def get_kcm_data(self, kcm: KlantContactMoment) -> KCMDict:
"type": kcm.contactmoment.type,
"status": Status.safe_label(kcm.contactmoment.status, _("Onbekend")),
"antwoord": kcm.contactmoment.antwoord,
"new_answer_available": bool(kcm.contactmoment.antwoord)
and _is_new
and not is_seen,
}

# replace e_suite_subject_code with OIP configured subject, if applicable
Expand Down Expand Up @@ -139,7 +160,12 @@ def get_context_data(self, **kwargs):
kcms = fetch_klantcontactmomenten(
**get_fetch_parameters(self.request, use_vestigingsnummer=True)
)
ctx["contactmomenten"] = [self.get_kcm_data(kcm) for kcm in kcms]
ctx["contactmomenten"] = [
self.get_kcm_data(
kcm, local_kcm_mapping=get_local_kcm_mapping(kcms, self.request.user)
)
for kcm in kcms
]
paginator_dict = self.paginate_with_context(ctx["contactmomenten"])
ctx.update(paginator_dict)
return ctx
Expand Down Expand Up @@ -172,6 +198,13 @@ def get_context_data(self, **kwargs):
if not kcm:
raise Http404()

local_kcm, is_created = KlantContactMomentLocal.objects.get_or_create( # noqa
user=self.request.user, kcm_url=kcm.url
)
if not local_kcm.is_seen:
local_kcm.is_seen = True
local_kcm.save()

if client := build_client("contactmomenten"):
zaken_client = build_client_openzaak("zaak")
ocm = client.retrieve_objectcontactmoment(
Expand Down
3 changes: 3 additions & 0 deletions src/open_inwoner/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,9 @@
# recent documents: created/added no longer than n days in the past
DOCUMENT_RECENT_DAYS = config("DOCUMENT_RECENT_DAYS", default=1)

# recent answers to contactmomenten: no longer than n days in the past
CONTACTMOMENT_NEW_DAYS = config("CONTACTMOMENT_NEW_DAYS", default=7)

#
# Maykin 2FA
#
Expand Down
4 changes: 4 additions & 0 deletions src/open_inwoner/conf/fixtures/django-admin-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@
[
"filer",
"folderpermission"
],
[
"openklant",
"klantcontactmomentlocal"
]
]
}
Expand Down
9 changes: 8 additions & 1 deletion src/open_inwoner/openklant/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ordered_model.admin import OrderedInlineModelAdminMixin, OrderedTabularInline
from solo.admin import SingletonModelAdmin

from .models import ContactFormSubject, OpenKlantConfig
from .models import ContactFormSubject, KlantContactMomentLocal, OpenKlantConfig


class ContactFormSubjectInlineAdmin(OrderedTabularInline):
Expand Down Expand Up @@ -70,3 +70,10 @@ class OpenKlantConfigAdmin(OrderedInlineModelAdminMixin, SingletonModelAdmin):
},
),
]


@admin.register(KlantContactMomentLocal)
class KlantContactMomentLocalAdmin(admin.ModelAdmin):
search_fields = ["user", "kcm_uuid"]
list_filter = ["is_seen"]
list_display = ["user", "kcm_url", "is_seen"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Generated by Django 4.2.10 on 2024-03-04 16:13

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("openklant", "0008_openklantconfig_use_rsin_for_innnnpid_query_parameter"),
]

operations = [
migrations.CreateModel(
name="KlantContactMomentLocal",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"kcm_url",
models.URLField(
max_length=1000,
unique=True,
verbose_name="KlantContactMoment URL",
),
),
(
"is_seen",
models.BooleanField(
default=False,
help_text="Whether or not the user has seen the answer",
verbose_name="Is seen",
),
),
(
"user",
models.ForeignKey(
help_text="This is the user that asked the question to which this is an answer.",
on_delete=django.db.models.deletion.CASCADE,
related_name="contactmoment_answers",
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
],
options={
"verbose_name": "KlantContactMoment",
"verbose_name_plural": "KlantContactMomenten",
},
),
]
24 changes: 24 additions & 0 deletions src/open_inwoner/openklant/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,27 @@ class Meta(OrderedModel.Meta):

def __str__(self):
return self.subject


class KlantContactMomentLocal(models.Model):
user = models.ForeignKey(
"accounts.User",
verbose_name=_("User"),
on_delete=models.CASCADE,
related_name="contactmoment_answers",
help_text=_(
"This is the user that asked the question to which this is an answer."
),
)
kcm_url = models.URLField(
verbose_name=_("KlantContactMoment URL"), unique=True, max_length=1000
)
is_seen = models.BooleanField(
verbose_name=_("Is seen"),
help_text=_("Whether or not the user has seen the answer"),
default=False,
)

class Meta:
verbose_name = _("KlantContactMoment")
verbose_name_plural = _("KlantContactMomenten")
27 changes: 26 additions & 1 deletion src/open_inwoner/openklant/wrap.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logging
from typing import List, Optional

from open_inwoner.accounts.models import User
from open_inwoner.kvk.branches import get_kvk_branch_number
from open_inwoner.openklant.api_models import KlantContactMoment
from open_inwoner.openklant.clients import build_client
from open_inwoner.openklant.models import OpenKlantConfig
from open_inwoner.openklant.models import KlantContactMomentLocal, OpenKlantConfig

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -95,3 +96,27 @@ def get_fetch_parameters(request, use_vestigingsnummer: bool = False) -> dict:
parameters.update({"vestigingsnummer": vestigingsnummer})
return parameters
return {}


def get_local_kcm_mapping(
kcms: list[KlantContactMoment], user: User
) -> dict[str, KlantContactMomentLocal]:
existing_kcms = KlantContactMomentLocal.objects.filter(user=user).values_list(
"kcm_url", flat=True
)

to_create = []
for kcm in kcms:
if kcm.url in existing_kcms:
continue

to_create.append(KlantContactMomentLocal(user=user, kcm_url=kcm.url))

KlantContactMomentLocal.objects.bulk_create(to_create)

kcm_answer_mapping = {
kcm_answer.kcm_url: kcm_answer
for kcm_answer in KlantContactMomentLocal.objects.filter(user=user)
}

return kcm_answer_mapping
43 changes: 43 additions & 0 deletions src/open_inwoner/scss/components/Card/Card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,49 @@
position: relative;
text-decoration: none;

.card__header {
display: flex;
background-color: var(--color-info-lighter);
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
color: var(--color-info-darker);
font-size: var(--font-size-body);

.card__status_indicator_text {
// Fix for optical illusion
padding: 8px var(--spacing-large) 10px 0;
}

[class*='icons'] {
// Fix for optical illusion
margin: 6px 10px 6px 10px;
}

&.success {
display: flex;
background-color: var(--color-success-lighter);
color: var(--color-success);
}

&.info {
display: flex;
background-color: var(--color-info-lighter);
color: var(--color-info-darker);
}

&.warning {
display: flex;
background-color: var(--color-danger-lightest);
color: var(--color-danger-darker);
}

&.failure {
display: flex;
background-color: var(--color-error-lighter);
color: var(--color-error-darker);
}
}

&__product-card {
min-height: 150px;
}
Expand Down
45 changes: 0 additions & 45 deletions src/open_inwoner/scss/components/Cases/Cases.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,49 +41,4 @@
&__detail {
box-sizing: border-box;
}

/// cards with statuses

.card__header {
display: flex;
background-color: var(--color-info-lighter);
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
color: var(--color-info-darker);
font-size: var(--font-size-body);

.card__status_indicator_text {
// Fix for optical illusion
padding: 8px var(--spacing-large) 10px 0;
}

[class*='icons'] {
// Fix for optical illusion
margin: 6px 10px 6px 10px;
}

&.success {
display: flex;
background-color: var(--color-success-lighter);
color: var(--color-success);
}

&.info {
display: flex;
background-color: var(--color-info-lighter);
color: var(--color-info-darker);
}

&.warning {
display: flex;
background-color: var(--color-danger-lightest);
color: var(--color-danger-darker);
}

&.failure {
display: flex;
background-color: var(--color-error-lighter);
color: var(--color-error-darker);
}
}
}
4 changes: 4 additions & 0 deletions src/open_inwoner/templates/pages/contactmoment/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ <h1 class="utrecht-heading-1" id="contactmomenten">{{ page_title }} ({{ contactm
{% for contactmoment in page_obj.object_list %}
{% render_column start=forloop.counter_0|multiply:4 span=4 %}
<div class="card card--compact card--stretch">
{% if contactmoment.new_answer_available %}
{% translate "Nieuw antwoord beschikbaar" as new_answer_text %}
{% include "components/StatusIndicator/StatusIndicator.html" with status_indicator="info" status_indicator_text=new_answer_text %}
{% endif %}
<div class="card__body">
<a href="{{ contactmoment.url }}" class="contactmomenten__link">
{% render_list %}
Expand Down

0 comments on commit 88bb783

Please sign in to comment.