From 7deb8d577eb60c6a931edc4d52d0f158c43d29f7 Mon Sep 17 00:00:00 2001 From: Nithin Shekar Kuruba <81444731+NithinKuruba@users.noreply.github.com> Date: Fri, 8 Sep 2023 09:11:04 -0700 Subject: [PATCH 1/4] feat: refactor of my dashboard and use react-table (#108) --- app/components/SearchBar.tsx | 36 +++ app/components/Table.tsx | 165 ++++++++++ app/controllers/realm.ts | 21 +- app/html-components/Table.tsx | 4 +- app/package.json | 4 + app/page-partials/my-dashboard/RealmEdit.tsx | 60 ++-- .../my-dashboard/RealmLeftPanel.tsx | 6 +- app/page-partials/my-dashboard/RealmTable.tsx | 151 +++++---- app/pages/api/realms/one.ts | 95 +++--- app/pages/realm/[rid].tsx | 63 ++-- app/yarn.lock | 287 +++++++++++++++++- helm/webapp/migration.sql | 13 +- 12 files changed, 695 insertions(+), 210 deletions(-) create mode 100644 app/components/SearchBar.tsx create mode 100644 app/components/Table.tsx diff --git a/app/components/SearchBar.tsx b/app/components/SearchBar.tsx new file mode 100644 index 0000000..d25b065 --- /dev/null +++ b/app/components/SearchBar.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import styled from 'styled-components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; + +const Wrapper = styled.div` + width: 100%; + position: relative; +`; + +const Icon = styled.i` + position: absolute; + right: 0.5em; + top: 0.5em; + color: grey; +`; + +const Input = styled.input` + width: 100%; + border: 2px solid #606060; + padding: 0.3em 0.5em; + border-radius: 0.25em; +`; + +function SearchBar(props: any) { + return ( + + + + + + + ); +} + +export default SearchBar; diff --git a/app/components/Table.tsx b/app/components/Table.tsx new file mode 100644 index 0000000..8198336 --- /dev/null +++ b/app/components/Table.tsx @@ -0,0 +1,165 @@ +import { + getCoreRowModel, + useReactTable, + flexRender, + getPaginationRowModel, + FilterFn, + getFilteredRowModel, +} from '@tanstack/react-table'; +import type { ColumnDef } from '@tanstack/react-table'; +import Pagination from 'react-bootstrap/Pagination'; +import styled from 'styled-components'; +import Select from 'react-select'; +import Grid from '@button-inc/bcgov-theme/Grid'; +import StyledTable from 'html-components/Table'; +import { useState } from 'react'; +import { rankItem } from '@tanstack/match-sorter-utils'; +import SearchBar from './SearchBar'; + +const StyledPagination = styled(Pagination)` + margin: 0 !important; + & li { + margin: 0 !important; + } +`; + +const PageInfo = styled.li` + padding-left: 5px; + line-height: 40px; +`; + +const StyledSelect = styled(Select)` + width: 150px; + display: inline-block; +`; + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value); + + // Store the itemRank info + addMeta({ + itemRank, + }); + + // Return if the item should be filtered in/out + return itemRank.passed; +}; + +interface ReactTableProps { + data: T[]; + columns: ColumnDef[]; + noDataFoundMessage: string; +} +const Table = ({ data, columns, noDataFoundMessage = 'no data found.' }: ReactTableProps) => { + const numOfItemsPerPage = () => { + const options = [5, 10, 15, 20, 25, 30].map((val) => { + return { value: val, label: `${val} per page` }; + }); + + return options; + }; + + const [globalFilter, setGlobalFilter] = useState(''); + + const table = useReactTable({ + data, + columns, + initialState: { + pagination: { + pageSize: 5, + }, + }, + state: { + globalFilter, + }, + onGlobalFilterChange: setGlobalFilter, + globalFilterFn: fuzzyFilter, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getFilteredRowModel: getFilteredRowModel(), + }); + + return ( + <> + setGlobalFilter(String(v.target.value))} + placeholder="Search all columns..." + style={{ marginTop: '10px', maxWidth: '25%' }} + /> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} + + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + ) : ( + + {noDataFoundMessage}{' '} + + )} + + + + + + + { + table.previousPage(); + }} + > + Previous + + { + table.nextPage(); + }} + > + Next + + {`${table.getState().pagination.pageIndex + 1} of ${table.getPageCount()}`} + + + +
+ { + table.setPageSize(Number(e.value)); + }} + > +
+
+
+
+ + ); +}; + +export default Table; diff --git a/app/controllers/realm.ts b/app/controllers/realm.ts index 0d42c2b..5618a0f 100644 --- a/app/controllers/realm.ts +++ b/app/controllers/realm.ts @@ -16,7 +16,6 @@ export async function getAllowedRealms(session: any) { id, realm, product_name, - openshift_namespace, product_owner_email, product_owner_idir_userid, technical_contact_email, @@ -27,8 +26,10 @@ export async function getAllowedRealms(session: any) { division, branch, created_at, - updated_at - FROM rosters WHERE LOWER(technical_contact_idir_userid)=LOWER($1) OR LOWER(product_owner_idir_userid)=LOWER($1) ORDER BY id ASC + updated_at, + rc_channel, + rc_channel_owned_by + FROM rosters WHERE LOWER(technical_contact_idir_userid)=LOWER($1) OR LOWER(second_technical_contact_idir_userid)=LOWER($1) OR LOWER(product_owner_idir_userid)=LOWER($1) ORDER BY id ASC `, [username], ); @@ -41,18 +42,10 @@ export async function getAllowedRealms(session: any) { if (kcAdminClient) { for (let x = 0; x < result?.rows.length; x++) { const realm = result?.rows[x]; - const [realmData, poName, techName, secTechName] = await Promise.all([ - kcCore.getRealm(realm.realm), - kcCore.getIdirUserName(realm.product_owner_idir_userid), - kcCore.getIdirUserName(realm.technical_contact_idir_userid), - kcCore.getIdirUserName(realm.second_technical_contact_idir_userid), - ]); - - realm.product_owner_name = poName; - realm.technical_contact_name = techName; - realm.second_technical_contact_name = secTechName; - realm.displayName = realmData?.displayName || ''; + const [realmData] = await Promise.all([kcCore.getRealm(realm.realm)]); realm.idps = realmData?.identityProviders?.map((v) => v.displayName || v.alias) || []; + const distinctProviders = new Set(realmData?.identityProviders?.map((v) => v.providerId) || []); + realm.protocol = Array.from(distinctProviders); } } } diff --git a/app/html-components/Table.tsx b/app/html-components/Table.tsx index daa1935..4a5536b 100644 --- a/app/html-components/Table.tsx +++ b/app/html-components/Table.tsx @@ -7,7 +7,8 @@ const Table = styled.table` box-shadow: none; text-align: left; border-collapse: separate; - border-spacing: 0 5px; + border-spacing: 0; + table-layout: fixed; & thead { font-size: 12px; @@ -38,6 +39,7 @@ const Table = styled.table` & th, & td { border: none; + word-wrap: break-word; } `; diff --git a/app/package.json b/app/package.json index 575ea21..1ecbf7c 100644 --- a/app/package.json +++ b/app/package.json @@ -14,6 +14,8 @@ "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", + "@tanstack/match-sorter-utils": "^8.8.4", + "@tanstack/react-table": "^8.9.7", "axios": "^0.27.2", "bootstrap": "^5.1.3", "easy-soap-request": "^4.7.0", @@ -24,6 +26,7 @@ "lodash.get": "^4.4.2", "lodash.map": "^4.6.0", "next": "12.1.6", + "node-cron": "^3.0.2", "pg": "^8.7.3", "pg-format": "^1.0.4", "qs": "^6.10.3", @@ -32,6 +35,7 @@ "react-dom": "18.1.0", "react-hook-form": "^7.31.3", "react-loader-spinner": "^6.0.0-0", + "react-select": "^5.7.4", "react-typography": "^0.16.20", "semantic-ui-react": "^2.1.3", "store2": "^2.13.2", diff --git a/app/page-partials/my-dashboard/RealmEdit.tsx b/app/page-partials/my-dashboard/RealmEdit.tsx index e2a99f5..300913c 100644 --- a/app/page-partials/my-dashboard/RealmEdit.tsx +++ b/app/page-partials/my-dashboard/RealmEdit.tsx @@ -180,14 +180,14 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) {

Realm Name: {realm.realm}