diff --git a/apcd-cms/src/apps/admin_submissions/static/admin_submissions/css/admin_table.css b/apcd-cms/src/apps/admin_submissions/static/admin_submissions/css/admin_table.css deleted file mode 100644 index d44a4c3e..00000000 --- a/apcd-cms/src/apps/admin_submissions/static/admin_submissions/css/admin_table.css +++ /dev/null @@ -1,21 +0,0 @@ -.pagination { - display: flex; - justify-content: center; - padding-top: 20px; - } - -.pagination a { - color: black; - float: left; - padding: 8px 16px; - text-decoration: none; - border: 1px solid var(--global-color-primary--light); -} - -.pagination a.active { - background-color: var(--global-color-accent--normal); - color: white; - } - -.pagination a:hover:not(.active) {background-color: var(--global-color-primary--light);} - diff --git a/apcd-cms/src/apps/admin_submissions/templates/list_admin_submissions.html b/apcd-cms/src/apps/admin_submissions/templates/list_admin_submissions.html index 89793bf1..904619d9 100644 --- a/apcd-cms/src/apps/admin_submissions/templates/list_admin_submissions.html +++ b/apcd-cms/src/apps/admin_submissions/templates/list_admin_submissions.html @@ -5,18 +5,8 @@ - - - - -
{% include "nav_cms_breadcrumbs.html" %} - -

View Submissions

-
-

All completed submissions by organizations

-
{% endblock %} diff --git a/apcd-cms/src/apps/admin_submissions/urls.py b/apcd-cms/src/apps/admin_submissions/urls.py index cb80d91d..96ef86a3 100644 --- a/apcd-cms/src/apps/admin_submissions/urls.py +++ b/apcd-cms/src/apps/admin_submissions/urls.py @@ -5,8 +5,6 @@ app_name = 'administration' urlpatterns = [ path('list-submissions/', TemplateView.as_view(template_name='list_admin_submissions.html'), name="admin_submissions"), - path(r'list-submissions/api/', AdminSubmissionsTable.as_view(), name="admin_submissions_api"), - path(r'list-submissions/api/?status=(?P)/', AdminSubmissionsTable.as_view(), name="admin_submissions_api"), - path(r'list-submissions/api/?sort=(?P)/', AdminSubmissionsTable.as_view(), name="admin_submissions_api"), - path(r'list-submissions/api/?sort=(?P)&filter=(?P)/', AdminSubmissionsTable.as_view(), name="admin_submissions_api"), + path('list-submissions/api/', AdminSubmissionsTable.as_view(), name="admin_submissions_api"), + path('list-submissions/api/options', AdminSubmissionsTable.as_view(), name='admin_submissions_api_options'), ] diff --git a/apcd-cms/src/apps/admin_submissions/views.py b/apcd-cms/src/apps/admin_submissions/views.py index 98fc9b89..d41ddcea 100644 --- a/apcd-cms/src/apps/admin_submissions/views.py +++ b/apcd-cms/src/apps/admin_submissions/views.py @@ -1,9 +1,9 @@ from django.http import HttpResponseRedirect, JsonResponse +from django.core.paginator import Paginator from django.views.generic.base import TemplateView from apps.utils.apcd_database import get_all_submissions_and_logs from apps.utils.apcd_groups import is_apcd_admin -from apps.utils.utils import title_case, table_filter -from apps.components.paginator.paginator import paginator +from apps.utils.utils import title_case import logging from dateutil import parser @@ -12,86 +12,88 @@ class AdminSubmissionsTable(TemplateView): template_name = 'list_admin_submissions.html' - - def get(self, request, *args, **kwargs): - submission_content = get_all_submissions_and_logs() - context = self.get_submission_list_json(submission_content, *args, **kwargs) - - return JsonResponse({'response': context}) - - def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not is_apcd_admin(request.user): return HttpResponseRedirect('/') return super(AdminSubmissionsTable, self).dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + if 'options' in request.path: + return self.get_options(request) + + status = request.GET.get('status', 'All') + sort = request.GET.get('sort', 'Newest Received') + page_number = int(request.GET.get('page', 1)) + items_per_page = int(request.GET.get('limit', 50)) + try: + submission_content = get_all_submissions_and_logs() + filtered_submissions = self.filtered_submissions(submission_content, status, sort) + + paginator = Paginator(filtered_submissions, items_per_page) + page_info = paginator.get_page(page_number) + + context= self.get_view_submissions_json(list(page_info), selected_status=status, selected_sort=sort) + context['page_num'] = page_info.number + context['total_pages'] = paginator.num_pages + return JsonResponse({'response': context}) + except Exception as e: + logger.error("Error fetching filtered user data: %s", e) + return JsonResponse({'error': str(e)}, status=500) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update(self.get_view_submissions_json(get_all_submissions_and_logs())) + return context - def get_submission_list_json(self, submission_content, *args, **kwargs): - - context = {} - queryStr = '' - dateSort = self.request.GET.get('sort') - status_filter = self.request.GET.get('status') - - def getDate(row): - date = row['received_timestamp'] - return parser.parse(date) if date is not None else parser.parse('1-1-3005') # put 'None' date entries all together at top/bottom depending on direction of sort - - if dateSort is not None: - context['selected_sort'] = dateSort - queryStr += f'&sort={dateSort}' - submission_content = sorted(submission_content, key=lambda row:getDate(row), reverse=(dateSort == 'newDate')) - + def get_options(self, request): try: - page_num = int(self.request.GET.get('page')) - except: - page_num = 1 - - context['selected_status'] = 'All' - if status_filter is not None and status_filter != 'All': - context['selected_status'] = status_filter - queryStr += f'&status={status_filter}' - submission_content = table_filter(status_filter, submission_content, 'status') - - limit = 50 - offset = limit * (page_num - 1) - - # modifies the object fields for display, only modifies a subset of entries that will be displayed - # on the current page using offset and limit - for s in submission_content[offset:offset + limit]: - s['status'] = title_case(s['status']) - s['entity_name'] = title_case(s['entity_name']) - s['outcome'] = title_case(s['outcome']) - s['received_timestamp'] = parser.parse(s['received_timestamp']) if s['received_timestamp'] else None - s['updated_at'] = parser.parse(s['updated_at']) if s['updated_at'] else None - s['view_modal_content'] = [{ - **t, - 'outcome': title_case(t['outcome']) - } for t in (s['view_modal_content'] or [])] - - context['header'] = ['Received', 'Entity Organization', 'File Name', 'Outcome', 'Status', 'Last Updated', 'Actions'] - context['status_options'] = ['All', 'In Process', 'Complete'] - context['sort_options'] = [ - {'name': '', 'value': ''}, - {'name': 'Newest Received', 'value': 'newDate'}, - {'name': 'Oldest Received', 'value': 'oldDate'} - ] - - context['query_str'] = queryStr - page_info = paginator(self.request, submission_content, limit) - - context['page'] = [{ - 'submission_id': obj['submission_id'], - 'status': obj['status'], - 'entity_name': obj['entity_name'], - 'file_name': obj['file_name'], - 'outcome': obj['outcome'], - 'received_timestamp': obj['received_timestamp'], - 'updated_at': obj['updated_at'], - 'view_modal_content': obj['view_modal_content'], - } for obj in page_info['page']] - - context['page_num'] = page_num - context['total_pages'] = page_info['page'].paginator.num_pages + status_options = ['All', 'In Process', 'Complete'] + sort_options = [ {'name': 'Newest Received', 'value': 'newDate'}, + {'name': 'Oldest Received', 'value': 'oldDate'}] + + return JsonResponse({ + 'status_options': status_options, + 'sort_options': sort_options, + }) + except Exception as e: + logger.error("Error fetching options data: %s", e) + return JsonResponse({'error': str(e)}, status=500) + + def filtered_submissions(self, submission_content, status, sort): + def getDate(submission): + date = submission['received_timestamp'] + return parser.parse(date) if date is not None else parser.parse('1-1-3005') + submission_list = sorted( + submission_content, + key=lambda row: getDate(row), + reverse=(sort == 'Newest Received') + ) + if status != 'All': + submission_list = [submission for submission in submission_content + if submission['status'].lower() == status.lower()] + + return submission_list + def get_view_submissions_json(self, submission_content, selected_status='All', selected_sort='Newest Received'): + context = { + 'page': [], + 'selected_status': selected_status, + 'selected_sort': selected_sort, + 'pagination_url_namespaces':'admin_submission:admin_submissions' + } + + def _set_submissions(submission): + return { + 'submission_id': submission['submission_id'], + 'status': submission['status'], + 'entity_name': submission['entity_name'], + 'file_name': submission['file_name'], + 'outcome': title_case(submission['outcome']) if submission['outcome'] else None, + 'received_timestamp': submission['received_timestamp'], + 'updated_at': submission['updated_at'], + 'view_modal_content': submission['view_modal_content'], + } + for submission in submission_content: + context['page'].append(_set_submissions(submission)) - context['pagination_url_namespaces'] = 'admin_submission:admin_submissions' return context + diff --git a/apcd-cms/src/apps/submissions/urls.py b/apcd-cms/src/apps/submissions/urls.py index 6fcb5b32..22939d31 100644 --- a/apcd-cms/src/apps/submissions/urls.py +++ b/apcd-cms/src/apps/submissions/urls.py @@ -5,8 +5,6 @@ app_name = 'submissions' urlpatterns = [ path('list-submissions/', TemplateView.as_view(template_name='list_submissions.html'), name="list_submissions"), - path(r'list-submissions/api/', SubmissionsTable.as_view(), name="list_submissions_api"), - path(r'list-submissions/api/?status=(?P)/', SubmissionsTable.as_view(), name="list_submissions_api"), - path(r'list-submissions/api/?sort=(?P)/', SubmissionsTable.as_view(), name="list_submissions_api"), - path(r'list-submissions/api/?sort=(?P)&filter=(?P)/', SubmissionsTable.as_view(), name="list_submissions_api"), + path('list-submissions/api/', SubmissionsTable.as_view(), name="list_submissions_api"), + path('list-submissions/api/options', SubmissionsTable.as_view(), name="list_submissions_api_options") ] diff --git a/apcd-cms/src/apps/submissions/views.py b/apcd-cms/src/apps/submissions/views.py index 22564d2f..0963bdc4 100644 --- a/apcd-cms/src/apps/submissions/views.py +++ b/apcd-cms/src/apps/submissions/views.py @@ -4,7 +4,7 @@ from apps.utils.apcd_database import get_user_submissions_and_logs from apps.utils.apcd_groups import has_apcd_group from apps.utils.utils import title_case, table_filter -from apps.components.paginator.paginator import paginator +from django.core.paginator import Paginator import logging from dateutil import parser @@ -15,78 +15,91 @@ class SubmissionsTable(TemplateView): template_name = 'list_submissions.html' - def get(self, request, *args, **kwargs): - submission_content = get_user_submissions_and_logs(request.user.username) - context = self.get_submission_list_json(submission_content, *args, **kwargs) - return JsonResponse({'response': context}) - def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not has_apcd_group(request.user): return HttpResponseRedirect('/') return super(SubmissionsTable, self).dispatch(request, *args, **kwargs) - def get_submission_list_json(self, submission_content, *args, **kwargs): - - context = {} - queryStr = '' - dateSort = self.request.GET.get('sort') - status_filter = self.request.GET.get('status') - - def getDate(row): - date = row['received_timestamp'] - return parser.parse(date) if date is not None else parser.parse('1-1-3005') # put 'None' date entries all together at top/bottom depending on direction of sort - - if dateSort is not None: - context['selected_sort'] = dateSort - queryStr += f'&sort={dateSort}' - submission_content = sorted(submission_content, key=lambda row:getDate(row), reverse=(dateSort == 'newDate')) - + def get(self, request, *args, **kwargs): + if 'options' in request.path: + return self.get_options(request) + + status = request.GET.get('status', 'All') + sort = request.GET.get('sort', 'Newest Received') + page_number = int(request.GET.get('page', 1)) + items_per_page = int(request.GET.get('limit', 50)) + try: + submission_content = get_user_submissions_and_logs(request.user.username) + logger.debug(print(submission_content)) + filtered_submissions = self.filtered_submissions(submission_content, status, sort) + + paginator = Paginator(filtered_submissions, items_per_page) + page_info = paginator.get_page(page_number) + context= self.get_view_submissions_json(list(page_info), selected_status=status, selected_sort=sort) + context['page_num'] = page_info.number + context['total_pages'] = paginator.num_pages + return JsonResponse({'response': context}) + except Exception as e: + logger.error("Error fetching filtered user data: %s", e) + return JsonResponse({'error': str(e)}, status=500) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update(self.get_view_submissions_json(get_user_submissions_and_logs(request.user.username))) + return context + + def get_options(self, request): try: - page_num = int(self.request.GET.get('page')) - except: - page_num = 1 - - context['selected_status'] = 'All' - if status_filter is not None and status_filter != 'All': - context['selected_status'] = status_filter - queryStr += f'&status={status_filter}' - submission_content = table_filter(status_filter, submission_content, 'status') - - limit = 50 - offset = limit * (page_num - 1) - - # modifies the object fields for display, only modifies a subset of entries that will be displayed - # on the current page using offset and limit - for s in submission_content[offset:offset + limit]: - s['status'] = title_case(s['status']) - s['outcome'] = title_case(s['outcome']) - s['received_timestamp'] = parser.parse(s['received_timestamp']) if s['received_timestamp'] else None - s['updated_at'] = parser.parse(s['updated_at']) if s['updated_at'] else None - s['view_modal_content'] = [{ - **t, - 'outcome': title_case(t['outcome']) - } for t in (s['view_modal_content'] or [])] - - context['header'] = ['Received', 'File Name', 'Outcome', 'Status', 'Last Updated', 'Actions'] - context['filter_options'] = ['All', 'In Process', 'Complete'] - context['sort_options'] = {'newDate': 'Newest Received', 'oldDate': 'Oldest Received'} - - context['query_str'] = queryStr - page_info = paginator(self.request, submission_content, limit) - - context['page'] = [{ - 'submission_id': obj['submission_id'], - 'status': obj['status'], - 'file_name': obj['file_name'], - 'outcome': obj['outcome'], - 'received_timestamp': obj['received_timestamp'], - 'updated_at': obj['updated_at'], - 'view_modal_content': obj['view_modal_content'], - } for obj in page_info['page']] - - context['page_num'] = page_num - context['total_pages'] = page_info['page'].paginator.num_pages - context['pagination_url_namespaces'] = 'submissions:list_submissions' + status_options = ['All', 'In Process', 'Complete'] + sort_options = [ {'name': 'Newest Received', 'value': 'newDate'}, + {'name': 'Oldest Received', 'value': 'oldDate'}] + + return JsonResponse({ + 'status_options': status_options, + 'sort_options': sort_options, + }) + except Exception as e: + logger.error("Error fetching options data: %s", e) + return JsonResponse({'error': str(e)}, status=500) + + def filtered_submissions(self, submission_content, status, sort): + def getDate(submission): + date = submission['received_timestamp'] + return parser.parse(date) if date is not None else parser.parse('1-1-3005') + + submission_list = sorted( + submission_content, + key=lambda row: getDate(row), + reverse=(sort == 'Newest Received') + ) + + if status != 'All': + submission_list = [submission for submission in submission_content + if submission['status'].lower() == status.lower()] + + return submission_list + + def get_view_submissions_json(self, submission_content, selected_status='All', selected_sort='Newest Received'): + context = { + 'page': [], + 'selected_status': selected_status, + 'selected_sort': selected_sort, + 'pagination_url_namespaces':'submissions:list_submissions' + } + + def _set_submissions(submission): + return { + 'submission_id': submission['submission_id'], + 'submitter_id': submission['submitter_id'], + 'file_name': submission['file_name'], + 'status': submission['status'], + 'outcome': title_case(submission['outcome']) if submission['outcome'] else None, + 'received_timestamp': submission['received_timestamp'], + 'updated_at': submission['updated_at'], + 'view_modal_content': submission['view_modal_content'], + } + for submission in submission_content: + context['page'].append(_set_submissions(submission)) return context diff --git a/apcd-cms/src/client/src/components/Admin/Exceptions/AdminExceptions.tsx b/apcd-cms/src/client/src/components/Admin/Exceptions/AdminExceptions.tsx index 0aa02578..1853d7e0 100644 --- a/apcd-cms/src/client/src/components/Admin/Exceptions/AdminExceptions.tsx +++ b/apcd-cms/src/client/src/components/Admin/Exceptions/AdminExceptions.tsx @@ -70,41 +70,42 @@ export const AdminExceptions: React.FC = () => {
{/* Filter */} -
- - Filter by Status: - - + + Filter by Status: + + + + {/* Filter by Organization */} + + Filter by Organization: + + + {data?.selected_status || data?.selected_org ? ( + + ) : null} - {/* Filter by Organization */} - - Filter by Organization: - - - {status !== 'Pending' || org !== 'All' ? ( - - ) : null} -
diff --git a/apcd-cms/src/client/src/components/Admin/Extensions/AdminExtensions.tsx b/apcd-cms/src/client/src/components/Admin/Extensions/AdminExtensions.tsx index 4e60902a..72cb9eec 100644 --- a/apcd-cms/src/client/src/components/Admin/Extensions/AdminExtensions.tsx +++ b/apcd-cms/src/client/src/components/Admin/Extensions/AdminExtensions.tsx @@ -73,45 +73,43 @@ export const AdminExtensions: React.FC = () => {
{/* Filter */} -
- - Filter by Status: - - + + Filter by Status: + + - {/* Filter by Organization */} - - Filter by Organization: - - - {data?.selected_status || data?.selected_org ? ( - - ) : null} -
+ {/* Filter by Organization */} + + Filter by Organization: + + + {data?.selected_status || data?.selected_org ? ( + + ) : null}
diff --git a/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.module.css b/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.module.css index 624f460b..eb4379d4 100644 --- a/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.module.css +++ b/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.module.css @@ -5,3 +5,6 @@ height: 100%; /* Adjust as needed */ padding-top: 20px; } +.submissionTable td { + word-wrap: break-word; +} diff --git a/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.tsx b/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.tsx index 1203eaa3..ab235e39 100644 --- a/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.tsx +++ b/apcd-cms/src/client/src/components/Admin/Submissions/AdminSubmissions.tsx @@ -1,42 +1,90 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { useSubmissions, SubmissionRow } from 'hooks/admin'; +import React, { useEffect, useState } from 'react'; +import { + useSubmissions, + SubmissionRow, + useSubmissionFilters, + SubmissionLogsModalContent, +} from 'hooks/admin'; import { ViewSubmissionLogsModal } from './ViewSubmissionLogsModal'; +import LoadingSpinner from 'core-components/LoadingSpinner'; +import SectionMessage from 'core-components/SectionMessage'; +import Button from 'core-components/Button'; import Paginator from 'core-components/Paginator'; +import { Link } from 'react-router-dom'; import styles from './AdminSubmissions.module.css'; import { formatDate } from 'utils/dateUtil'; +import { titleCase } from 'utils/stringUtil'; export const AdminSubmissions: React.FC = () => { - const [status, setStatus] = useState(''); - const [sort, setSort] = useState(''); + const header = [ + 'Received', + 'Entity Organization', + 'File Name', + 'Outcome', + 'Status', + 'Last Updated', + 'Actions', + ]; + const { + data: filterData, + isLoading: isFilterLoading, + isError: isFilterError, + } = useSubmissionFilters(); + + const [status, setStatus] = useState('All'); + const [sort, setSort] = useState('Newest Received'); const [page, setPage] = useState(1); + const [viewModalOpen, setViewModalOpen] = useState(false); + const [selectedSubmission, setSelectedSubmission] = + useState(null); - const { data, isLoading, isError, refetch } = useSubmissions( - status, - sort, - page - ); + const [selectedSubmissionLog, setSelectedSubmissionLog] = useState< + SubmissionLogsModalContent[] + >([]); + + const closeModal = () => { + setViewModalOpen(false); + setSelectedSubmission(null); + }; + + const { + data: submissionData, + isLoading: isSubmissionsLoading, + isError: isSubmissionsError, + refetch, + } = useSubmissions(status, sort, page); useEffect(() => { - console.log('useEffect', status, sort, page); refetch(); }, [status, sort, page]); - if (isLoading) { - return
Loading...
; + if (isSubmissionsLoading) { + return ; } - if (isError) { - return
Error loading data
; + if (isSubmissionsError) { + return ( + + There was an error loading the page.{' '} + + Please submit a ticket. + + + ); } const clearSelections = () => { - setStatus(''); - setSort(''); + setStatus('All'); + setSort('Newest Received'); setPage(1); }; return ( - <> +
+

View Submissions

+
+

All completed submissions by organizations

+
@@ -45,10 +93,12 @@ export const AdminSubmissions: React.FC = () => { setSort(e.target.value)} > - {data?.sort_options.map((option: any, index: number) => ( - ))} - {data?.selected_status || data?.selected_sort ? ( + {status !== 'All' || sort !== 'Newest Received' ? ( ) : null}
-
+
- {data?.header.map((columnName: string, index: number) => ( + {header.map((columnName: string, index: number) => ( ))} - {data?.page.map((row: SubmissionRow, rowIndex: number) => ( + {submissionData?.page.map((row: SubmissionRow, rowIndex: number) => ( - + - - - + + + ))} @@ -106,11 +159,18 @@ export const AdminSubmissions: React.FC = () => {
{columnName}
{formatDate(row.received_timestamp)}{row.entity_name}{titleCase(row.entity_name)} {row.file_name}{row.outcome}{row.status}{row.updated_at}{titleCase(row.outcome)}{titleCase(row.status)}{formatDate(row.updated_at)} - +
- + {selectedSubmission && viewModalOpen && ( + + )} + ); }; diff --git a/apcd-cms/src/client/src/components/Admin/Submissions/ViewSubmissionLogsModal.tsx b/apcd-cms/src/client/src/components/Admin/Submissions/ViewSubmissionLogsModal.tsx index d65ec8f1..9200fac4 100644 --- a/apcd-cms/src/client/src/components/Admin/Submissions/ViewSubmissionLogsModal.tsx +++ b/apcd-cms/src/client/src/components/Admin/Submissions/ViewSubmissionLogsModal.tsx @@ -1,62 +1,60 @@ +import React from 'react'; import { SubmissionLogsModalContent } from 'hooks/admin'; -import React, { useState } from 'react'; import { Modal, ModalBody, ModalHeader } from 'reactstrap'; -import Button from 'core-components/Button'; -import styles from './Submissions.css'; +import { formatDate } from 'utils/dateUtil'; +import { titleCase } from 'utils/stringUtil'; interface ViewSubmissionLogsModalProps { submission_logs: SubmissionLogsModalContent[]; + isOpen: boolean; + parentToggle: () => void; } export const ViewSubmissionLogsModal: React.FC< ViewSubmissionLogsModalProps -> = ({ submission_logs }) => { - const [isOpen, setIsOpen] = useState(false); - - const toggle = () => { - setIsOpen(!isOpen); - }; - +> = ({ submission_logs, isOpen, parentToggle }) => { const closeBtn = ( - ); return ( <> - - -
View Logs
+ + View Logs
-

Logs

{submission_logs.length > 0 ? ( - submission_logs.map((log, index) => ( + submission_logs.map((log: any, index: number) => (
Log ID
{log.log_id}
Entity Organization
-
{log.entity_name}
+
+ {titleCase(log.entity_name)} +
File Type
-
{log.file_type}
+
+ {titleCase(log.file_type)} +
Validation Suite
- {log.validation_suite} + {titleCase(log.validation_suite)}
Outcome
-
{log.outcome}
+
+ {titleCase(log.outcome)} +

diff --git a/apcd-cms/src/client/src/components/Admin/ViewUsers/ViewUsers.tsx b/apcd-cms/src/client/src/components/Admin/ViewUsers/ViewUsers.tsx index dd9cdc87..66e94280 100644 --- a/apcd-cms/src/client/src/components/Admin/ViewUsers/ViewUsers.tsx +++ b/apcd-cms/src/client/src/components/Admin/ViewUsers/ViewUsers.tsx @@ -89,7 +89,7 @@ export const ViewUsers: React.FC = () => { } return ( -
+

View Users


View submitted users.

@@ -97,43 +97,41 @@ export const ViewUsers: React.FC = () => {
{/* Filter */} -
- - Filter by Status: - - + + Filter by Status: + + - {/* Filter by Organization */} - - Filter by Organization: - - - {status !== 'All' || org !== 'All' ? ( - - ) : null} -
+ {/* Filter by Organization */} + + Filter by Organization: + + + {status !== 'All' || org !== 'All' ? ( + + ) : null}
diff --git a/apcd-cms/src/client/src/components/Registrations/RegistrationList/RegistrationList.tsx b/apcd-cms/src/client/src/components/Registrations/RegistrationList/RegistrationList.tsx index e884be59..7d17f97b 100644 --- a/apcd-cms/src/client/src/components/Registrations/RegistrationList/RegistrationList.tsx +++ b/apcd-cms/src/client/src/components/Registrations/RegistrationList/RegistrationList.tsx @@ -82,42 +82,40 @@ export const RegistrationList: React.FC<{
{/* Filter */} -
- - Filter by Status: - - + + Filter by Status: + + - - Filter by Organization: - - - {data?.selected_status || data?.selected_org ? ( - - ) : null} -
+ + Filter by Organization: + + + {data?.selected_status || data?.selected_org ? ( + + ) : null}
diff --git a/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.module.css b/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.module.css index e69de29b..eb4379d4 100644 --- a/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.module.css +++ b/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.module.css @@ -0,0 +1,10 @@ +.paginatorContainer { + display: flex; + justify-content: center; /* Centers the child horizontally */ + align-items: center; /* Centers the child vertically */ + height: 100%; /* Adjust as needed */ + padding-top: 20px; +} +.submissionTable td { + word-wrap: break-word; +} diff --git a/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.tsx b/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.tsx index d6cf55aa..b0ab57d2 100644 --- a/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.tsx +++ b/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissions.tsx @@ -1,34 +1,79 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { useListSubmissions, FileSubmissionRow } from 'hooks/submissions'; +import { + useListSubmissions, + FileSubmissionRow, + useSubmissionFilters, + FileSubmissionLogsModalContent, +} from 'hooks/submissions'; +import LoadingSpinner from 'core-components/LoadingSpinner'; +import Button from 'core-components/Button'; +import SectionMessage from 'core-components/SectionMessage'; +import Paginator from 'core-components/Paginator'; +import styles from './ViewSubmissions.module.css'; +import { Link } from 'react-router-dom'; import { FileSubmissionLogsModal } from './ViewSubmissionsModal'; import { formatDate } from 'utils/dateUtil'; +import { titleCase } from 'utils/stringUtil'; export const ViewFileSubmissions: React.FC = () => { - const [status, setStatus] = useState(''); - const [sort, setSort] = useState(''); + const header = [ + 'Received', + 'File Name', + 'Outcome', + 'Status', + 'Last Updated', + 'Actions', + ]; + const { + data: filterData, + isLoading: isFilterLoading, + isError: isFilterError, + } = useSubmissionFilters(); + + const [status, setStatus] = useState('All'); + const [sort, setSort] = useState('Newest Received'); const [page, setPage] = useState(1); + const [viewModalOpen, setViewModalOpen] = useState(false); + const [selectedSubmission, setSelectedSubmission] = + useState(null); + const [selectedSubmissionLog, setSelectedSubmissionLog] = useState< + FileSubmissionLogsModalContent[] + >([]); - const { data, isLoading, isError, refetch } = useListSubmissions( - status, - sort, - page - ); + const closeModal = () => { + setViewModalOpen(false); + setSelectedSubmission(null); + }; + + const { + data: submissionData, + isLoading: isSubmissionLoading, + isError: isSubmissionError, + refetch, + } = useListSubmissions(status, sort, page); useEffect(() => { refetch(); }, [status, sort, page]); - if (isLoading) { - return
Loading...
; + if (isSubmissionLoading) { + return ; } - if (isError) { - return
Error loading data
; + if (isSubmissionError) { + return ( + + There was an error loading the page.{' '} + + Please submit a ticket. + + + ); } const clearSelections = () => { - setStatus(''); - setSort(''); + setStatus('All'); + setSort('All'); setPage(1); }; @@ -37,74 +82,89 @@ export const ViewFileSubmissions: React.FC = () => {
{/* Filter */} -
- - Filter by Status: - - - - Sort by: - - - {data?.selected_status || data?.selected_sort ? ( - - ) : null} -
+ + Filter by Status: + + + + Sort by: + + + {status !== 'All' || sort !== 'Newest Received' ? ( + + ) : null}
-
+
- {data?.header.map((columnName: string, index: number) => ( + {header.map((columnName: string, index: number) => ( ))} - {data?.page.map((row: FileSubmissionRow, rowIndex: number) => ( - - - - - - - - - ))} + {submissionData?.page.map( + (row: FileSubmissionRow, rowIndex: number) => ( + + + + + + + + + ) + )}
{columnName}
{formatDate(row.received_timestamp)}{row.file_name}{row.outcome}{row.status}{row.updated_at} - -
{formatDate(row.received_timestamp)}{titleCase(row.file_name)}{titleCase(row.outcome)}{titleCase(row.status)}{formatDate(row.updated_at)} + +
+
+ +
+ {selectedSubmission && viewModalOpen && ( + + )}
); }; diff --git a/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissionsModal.tsx b/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissionsModal.tsx index 8519ea9e..44714ab5 100644 --- a/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissionsModal.tsx +++ b/apcd-cms/src/client/src/components/Submissions/ViewFileSubmissions/ViewSubmissionsModal.tsx @@ -1,46 +1,37 @@ import { FileSubmissionLogsModalContent } from 'hooks/submissions'; import React, { useState } from 'react'; import { Modal, ModalBody, ModalHeader } from 'reactstrap'; -import Button from 'core-components/Button'; -import styles from './Submissions.css'; +import { titleCase } from 'utils/stringUtil'; interface ViewSubmissionLogsModalProps { submission_logs: FileSubmissionLogsModalContent[]; + isOpen: boolean; + parentToggle: () => void; } export const FileSubmissionLogsModal: React.FC< ViewSubmissionLogsModalProps -> = ({ submission_logs }) => { - const [isOpen, setIsOpen] = useState(false); - - const toggle = () => { - setIsOpen(!isOpen); - }; - +> = ({ submission_logs, isOpen, parentToggle }) => { const closeBtn = ( - ); return ( <> - - -
View Logs
+ + View Logs
-

Logs

{submission_logs.length > 0 ? ( submission_logs.map((log, index) => (
@@ -55,10 +46,12 @@ export const FileSubmissionLogsModal: React.FC<
{log.file_type}
Validation Suite
- {log.validation_suite} + {titleCase(log.validation_suite)}
Outcome
-
{log.outcome}
+
+ {titleCase(log.outcome)} +

diff --git a/apcd-cms/src/client/src/hooks/admin/index.ts b/apcd-cms/src/client/src/hooks/admin/index.ts index d486947a..8b5594d2 100644 --- a/apcd-cms/src/client/src/hooks/admin/index.ts +++ b/apcd-cms/src/client/src/hooks/admin/index.ts @@ -14,6 +14,7 @@ export type UserRow = { export type FilterOptions = { status_options: string[]; org_options: string[]; + sort_options: string[]; }; export type UserResult = { @@ -39,9 +40,6 @@ export type SubmissionRow = { export type SubmissionResult = { header: string[]; - status_options: string[]; - filter_options: string[]; - sort_options: { name: string; value: string }[]; selected_status: string; selected_sort: string; query_str: string; @@ -150,4 +148,5 @@ export { useUsers, useExceptions, useUserFilters, + useSubmissionFilters, } from './useAdmin'; diff --git a/apcd-cms/src/client/src/hooks/admin/useAdmin.ts b/apcd-cms/src/client/src/hooks/admin/useAdmin.ts index ae51df67..388fefdb 100644 --- a/apcd-cms/src/client/src/hooks/admin/useAdmin.ts +++ b/apcd-cms/src/client/src/hooks/admin/useAdmin.ts @@ -47,6 +47,22 @@ export const useUserFilters = (): UseQueryResult => { return { ...query }; }; +const getSubmissionFilters = async () => { + const url = 'administration/list-submissions/api/options'; + const response = await fetchUtil({ url }); + return response; +}; + +export const useSubmissionFilters = (): UseQueryResult => { + const query = useQuery(['submissionFilters'], () => getSubmissionFilters(), { + staleTime: 5 * 60 * 1000, + cacheTime: 10 * 60 * 1000, + refetchOnWindowFocus: false, + }) as UseQueryResult; + + return { ...query }; +}; + const getSubmissions = async (params: any) => { const url = `administration/list-submissions/api/`; const response = await fetchUtil({ diff --git a/apcd-cms/src/client/src/hooks/submissions/index.ts b/apcd-cms/src/client/src/hooks/submissions/index.ts index 9c4f5a2b..ffc744c5 100644 --- a/apcd-cms/src/client/src/hooks/submissions/index.ts +++ b/apcd-cms/src/client/src/hooks/submissions/index.ts @@ -16,8 +16,6 @@ export type FileSubmissionRow = { export type FileSubmissionResult = { header: string[]; - filter_options: string[]; - sort_options: { name: string; value: string }[]; selected_status: string; selected_sort: string; query_str: string; @@ -35,4 +33,4 @@ export type FileSubmissionLogsModalContent = { outcome: string; }; -export { useListSubmissions } from './useSubmissions'; +export { useListSubmissions, useSubmissionFilters } from './useSubmissions'; diff --git a/apcd-cms/src/client/src/hooks/submissions/useSubmissions.ts b/apcd-cms/src/client/src/hooks/submissions/useSubmissions.ts index 3be8705e..2daf5b8d 100644 --- a/apcd-cms/src/client/src/hooks/submissions/useSubmissions.ts +++ b/apcd-cms/src/client/src/hooks/submissions/useSubmissions.ts @@ -1,6 +1,23 @@ import { useQuery, UseQueryResult } from 'react-query'; import { fetchUtil } from 'utils/fetchUtil'; import { FileSubmissionResult } from '.'; +import { FilterOptions } from 'hooks/admin'; //May want to refactor where this hook lives + +const getSubmissionFilters = async () => { + const url = 'submissions/list-submissions/api/options'; + const response = await fetchUtil({ url }); + return response; +}; + +export const useSubmissionFilters = (): UseQueryResult => { + const query = useQuery(['submissionFilters'], () => getSubmissionFilters(), { + staleTime: 5 * 60 * 1000, + cacheTime: 10 * 60 * 1000, + refetchOnWindowFocus: false, + }) as UseQueryResult; + + return { ...query }; +}; const getListSubmissions = async (params: any) => { const url = `submissions/list-submissions/api/`; diff --git a/apcd-cms/src/client/src/utils/index.ts b/apcd-cms/src/client/src/utils/index.ts index 9d4796c4..30491281 100644 --- a/apcd-cms/src/client/src/utils/index.ts +++ b/apcd-cms/src/client/src/utils/index.ts @@ -1,3 +1,4 @@ // index.ts export * from './dateUtil'; export * from './fetchUtil'; +export * from './stringUtil'; diff --git a/apcd-cms/src/client/src/utils/stringUtil.ts b/apcd-cms/src/client/src/utils/stringUtil.ts new file mode 100644 index 00000000..29097684 --- /dev/null +++ b/apcd-cms/src/client/src/utils/stringUtil.ts @@ -0,0 +1,7 @@ +export const titleCase = (field: string): string => { + if (!field) return field; + return field + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +}; diff --git a/apcd-cms/src/taccsite_custom/apcd_cms/static/apcd-cms/css/table.css b/apcd-cms/src/taccsite_custom/apcd_cms/static/apcd-cms/css/table.css index 88f36bfc..8ef0cf3f 100644 --- a/apcd-cms/src/taccsite_custom/apcd_cms/static/apcd-cms/css/table.css +++ b/apcd-cms/src/taccsite_custom/apcd_cms/static/apcd-cms/css/table.css @@ -107,9 +107,13 @@ table { .filter-container { margin-bottom: 30px; width: auto; + } .filter-content { float: right; + display: flex; + align-items: center; + gap: 10px; } /* To reveal floated child (.filter-content) on narrow screen i.e. "clearfix" */ /* SEE: https://www.google.com/search?q=clearfix */