Updated data, added manage/view icon, search & sort functionality

This commit is contained in:
CocoByte 2024-09-23 15:56:53 -06:00
parent d7fa9e8894
commit ecf2e4d0f6
No known key found for this signature in database
GPG key ID: BBFAA2526384C97F
3 changed files with 61 additions and 90 deletions

View file

@ -1918,29 +1918,31 @@ class MembersTable extends LoadTableBase {
data.members.forEach(member => { data.members.forEach(member => {
// const actionUrl = domain.action_url; // const actionUrl = domain.action_url;
const member_name = member.name; const member_name = member.name;
const member_email = member.email;
const last_active = member.last_active; const last_active = member.last_active;
const action_url = member.action_url;
const action_label = member.action_label;
const svg_icon = member.svg_icon;
const row = document.createElement('tr'); const row = document.createElement('tr');
row.innerHTML = ` row.innerHTML = `
<th scope="row" role="rowheader" data-label="member name"> <th scope="row" role="rowheader" data-label="member email">
${member_name} ${member_email}
</th> </th>
<td data-sort-value="${last_active}" data-label="name"> <td data-sort-value="${last_active}" data-label="last_active">
${last_active} ${last_active}
</td> </td>
<td>
<a href="${action_url}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#${svg_icon}"></use>
</svg>
${action_label} <span class="usa-sr-only">${member_name}</span>
</a>
</td>
`; `;
// <td>
// <a href="${actionUrl}">
// <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
// <use xlink:href="/public/img/sprite.svg#${domain.svg_icon}"></use>
// </svg>
// ${domain.action_label} <span class="usa-sr-only">${domain.name}</span>
// </a>
// </td>
// `;
memberList.appendChild(row); memberList.appendChild(row);
}); });
} }

View file

@ -12,11 +12,6 @@
<span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span> <span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span>
{% endif %} {% endif %}
<!-- ---------- SEARCH ---------- --> <!-- ---------- SEARCH ---------- -->
<!--
=====================
TODO: future ticket?
=====================
-->
<div class="section-outlined__search {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6{% endif %}"> <div class="section-outlined__search {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6{% endif %}">
<section aria-label="Members search component" class="margin-top-2"> <section aria-label="Members search component" class="margin-top-2">
<form class="usa-search usa-search--small" method="POST" role="search"> <form class="usa-search usa-search--small" method="POST" role="search">

View file

@ -2,6 +2,9 @@
from django.http import JsonResponse from django.http import JsonResponse
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.utils import timezone
from django.db.models import Q
from registrar.models.user import User from registrar.models.user import User
from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.user_portfolio_permission import UserPortfolioPermission
@ -13,19 +16,6 @@ from registrar.management.commands.utility.terminal_helper import TerminalColors
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# DEVELOPER'S NOTE (9-20-24):
# The way this works is first we get a list of "member" objects
# Then we pass this to "serialize_members", which extracts object information
# and puts it into a JSON that is then used in get-gov.js for dynamically
# populating the frontend members table with data.
# So, if you're wondering where these JSON values are used, check out the class "MembersTable"
# in get-gov.js (specifically the "loadTable" function).
#
# The way get-gov.js grabs this JSON is via the html. Specifically,
# "get_portfolio_members_json" is embedded in members_table.html as a string, which
# is then referenced in get-gov.js. This path is also added to urls.py.
@login_required @login_required
def get_portfolio_members_json(request): def get_portfolio_members_json(request):
"""Given the current request, """Given the current request,
@ -36,7 +26,7 @@ def get_portfolio_members_json(request):
member_ids = objects.values_list("id", flat=True) member_ids = objects.values_list("id", flat=True)
unfiltered_total = member_ids.count() unfiltered_total = member_ids.count()
# objects = apply_search(objects, request) objects = apply_search(objects, request)
# objects = apply_status_filter(objects, request) # objects = apply_status_filter(objects, request)
# objects = apply_sorting(objects, request) # objects = apply_sorting(objects, request)
@ -47,9 +37,20 @@ def get_portfolio_members_json(request):
serialize_members(request, member, request.user) for member in page_obj.object_list serialize_members(request, member, request.user) for member in page_obj.object_list
] ]
# DEVELOPER'S NOTE (9-20-24):
# The way this works is first we get a list of "member" objects
# Then we pass this to "serialize_members", which extracts object information
# and puts it into a JSON that is then used in get-gov.js for dynamically
# populating the frontend members table with data.
# So, if you're wondering where these JSON values are used, check out the class "MembersTable"
# in get-gov.js (specifically the "loadTable" function).
#
# The way get-gov.js grabs this JSON is via the html. Specifically,
# "get_portfolio_members_json" is embedded in members_table.html as a string, which
# is then referenced in get-gov.js. This path is also added to urls.py.
return JsonResponse( return JsonResponse(
{ {
"members": members, # "domain_requests": domain_requests, TODO: DELETE ME! "members": members,
"has_next": page_obj.has_next(), "has_next": page_obj.has_next(),
"has_previous": page_obj.has_previous(), "has_previous": page_obj.has_previous(),
"page": page_obj.number, "page": page_obj.number,
@ -65,7 +66,7 @@ def get_portfolio_members_json(request):
# Or maybe an assumption was made wherein we assume there will never be zero entries returned?? # Or maybe an assumption was made wherein we assume there will never be zero entries returned??
return JsonResponse( return JsonResponse(
{ {
"members": [], # "domain_requests": domain_requests, TODO: DELETE ME! "members": [],
"has_next": False, "has_next": False,
"has_previous": False, "has_previous": False,
"page": 0, "page": 0,
@ -103,32 +104,17 @@ def get_member_objects_from_request(request):
return members return members
# def apply_search(queryset, request): def apply_search(queryset, request):
# search_term = request.GET.get("search_term") search_term = request.GET.get("search_term")
# is_portfolio = request.GET.get("portfolio")
# if search_term: if search_term:
# search_term_lower = search_term.lower() queryset = queryset.filter(
# new_domain_request_text = "new domain request" Q(username__icontains=search_term)
| Q(first_name__icontains=search_term)
# # Check if the search term is a substring of 'New domain request' | Q(last_name__icontains=search_term)
# # If yes, we should return domain requests that do not have a | Q(email__icontains=search_term)
# # requested_domain (those display as New domain request in the UI) )
# if search_term_lower in new_domain_request_text: return queryset
# queryset = queryset.filter(
# Q(requested_domain__name__icontains=search_term) | Q(requested_domain__isnull=True)
# )
# elif is_portfolio:
# queryset = queryset.filter(
# Q(requested_domain__name__icontains=search_term)
# | Q(creator__first_name__icontains=search_term)
# | Q(creator__last_name__icontains=search_term)
# | Q(creator__email__icontains=search_term)
# )
# # For non org users
# else:
# queryset = queryset.filter(Q(requested_domain__name__icontains=search_term))
# return queryset
# def apply_status_filter(queryset, request): # def apply_status_filter(queryset, request):
@ -157,7 +143,7 @@ def get_member_objects_from_request(request):
def serialize_members(request, member, user): def serialize_members(request, member, user):
# ------- DELETABLE # ------- DELETABLE
# deletable_statuses = [ # deletable_statuses = [
# DomainRequest.DomainRequestStatus.STARTED, # DomainRequest.DomainRequestStatus.STARTED,
# DomainRequest.DomainRequestStatus.WITHDRAWN, # DomainRequest.DomainRequestStatus.WITHDRAWN,
@ -173,39 +159,27 @@ def serialize_members(request, member, user):
# ) and member.creator == user # ) and member.creator == user
# ------- EDIT / VIEW # ------- VIEW ONLY
# # Determine action label based on user permissions and request status # Determine action label based on user permissions
# editable_statuses = [ # If the user has permissions to edit/manage users, show the gear icon with "Manage" link.
# DomainRequest.DomainRequestStatus.STARTED, # If the user has view user permissions only, show the "View" link (no gear icon).
# DomainRequest.DomainRequestStatus.ACTION_NEEDED, view_only = not user.has_edit_members_portfolio_permission
# DomainRequest.DomainRequestStatus.WITHDRAWN,
# ]
# if user.has_edit_request_portfolio_permission and member.creator == user: # ------- ACTIVITY
# action_label = "Edit" if member.status in editable_statuses else "Manage" is_invited = member.verification_type == User.VerificationTypeChoices.INVITED
# else: last_active = "invited" if is_invited else "unknown"
# action_label = "View" if member.last_login:
last_active = member.last_login.strftime("%b. %d, %Y")
# # Map the action label to corresponding URLs and icons
# action_url_map = {
# "Edit": reverse("edit-domain-request", kwargs={"id": member.id}),
# "Manage": reverse("domain-request-status", kwargs={"pk": member.id}),
# "View": "#",
# }
# svg_icon_map = {"Edit": "edit", "Manage": "settings", "View": "visibility"}
# ------- INVITED
# TODO:....
# ------- SERIALIZE
# ------- SERIALIZE
return { return {
"id": member.id,
"name": member.get_formatted_name(), "name": member.get_formatted_name(),
"last_active": member.id, "email": member.email,
# ("manage icon...maybe svg_icon??") "last_active": last_active,
"action_url": '#', #reverse("members", kwargs={"pk": member.id}), #TODO: Future ticket?
"action_label": ("View" if view_only else "Manage"),
"svg_icon": ("visibility" if view_only else "settings"),
} }
# return { # return {