From f553a9dffce2563248d9b9229135caa5ca147a47 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Sun, 7 Jul 2024 22:35:33 -0700 Subject: [PATCH 01/36] Refactor,Style(Documents): DocumentsTable now uses DocumentPreview components/cards. Currently no document utilites reimplemented; i.e Share, Replace, Remove, and View. --- src/components/Documents/DocumentPreview.jsx | 154 +++++++++++++++++++ src/components/Documents/DocumentTable.jsx | 46 ++---- 2 files changed, 163 insertions(+), 37 deletions(-) create mode 100644 src/components/Documents/DocumentPreview.jsx diff --git a/src/components/Documents/DocumentPreview.jsx b/src/components/Documents/DocumentPreview.jsx new file mode 100644 index 000000000..ec264ed54 --- /dev/null +++ b/src/components/Documents/DocumentPreview.jsx @@ -0,0 +1,154 @@ +// React Imports +import React from 'react'; +// Material UI Imports +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Container from '@mui/material/Container'; +import Divider from '@mui/material/Divider'; +import Grid from '@mui/material/Grid'; +import ListItemButton from '@mui/material/ListItemButton'; +import Paper from '@mui/material/Paper'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; + +/** + * @typedef {import("../../typedefs.js").DocumentListContext} documentListObject //@todo Update this + */ + +/** + * DocumentPreview - Component that displays document previews from + * user's documents container + * + * @memberof Documents + * @name DocumentPreview + * @param {object} Props - Component props for Document Preview + * @param {documentListObject} Props.document - The document object + * @returns {React.JSX.Element} React component for DocumentPreview + */ +const DocumentPreview = ({ document }) => { + /** + * @todo: Import document utilities from + * @file DocumentListContext.jsx + * @file DocumentList.js + */ + + /** @todo: Implement buttons */ + const handleShare = async () => { + try { + // await shareDocument(document); + } catch { + throw new Error('Failed to update read status'); + } + }; + + const handleReplace = async () => { + try { + // await replaceDocument(document); + } catch { + throw new Error('Failed to update read status'); + } + }; + + const handleRemove = async () => { + try { + // await removeDocument(document); + } catch { + throw new Error('Failed to update read status'); + } + }; + + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); + + const renderMediumGridLeft = () => { + if (isMediumScreen) return 8; + return 5; + }; + + const renderMediumGridRight = () => { + if (isMediumScreen) return 4; + return 2; + }; + + const documentInfo = [ + { + title: 'Name: ', + text: document?.name, + xs_value: isSmallScreen ? 12 : renderMediumGridLeft() + }, + { + title: 'Type: ', + text: document?.type, + xs_value: isSmallScreen ? 12 : renderMediumGridLeft() + }, + { + title: 'Description: ', + text: document?.description, + xs_value: isSmallScreen ? 12 : renderMediumGridRight() + } + ]; + + /** + * DOCUMENT INFO + * ------------------ + id: document.name, + type: document.type, + name: document.name, + description: document.description, + delete: document, + 'upload date': document?.uploadDate.toLocaleDateString(), + 'expiration date': document?.endDate?.toLocaleDateString(), + 'preview file': document.fileUrl + */ + + return ( + + + + + + {documentInfo.map((info, index) => ( + + + {info.title} {info.text} + + + ))} + + + + + + + + + + + + + + + ); +}; + +export default DocumentPreview; diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index 92adc9865..cf0ffb3ad 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -3,11 +3,13 @@ import React, { useContext } from 'react'; // Constants import DOC_TYPES from '@constants/doc_types'; // Material UI Imports +// eslint-disable-next-line no-unused-vars import Box from '@mui/material/Box'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import ShareIcon from '@mui/icons-material/Share'; import FileOpenIcon from '@mui/icons-material/FileOpen'; import { + // eslint-disable-next-line no-unused-vars DataGrid, GridActionsCellItem, GridToolbarContainer, @@ -20,11 +22,14 @@ import { useSession } from '@hooks'; // Utility Imports import { getBlobFromSolid } from '@utils'; // Theme Imports +// eslint-disable-next-line no-unused-vars import theme from '../../theme'; // Component Imports import { EmptyListNotification, LoadingAnimation } from '../Notification'; +import DocumentPreview from './DocumentPreview'; // DataGrid Toolbar +// eslint-disable-next-line no-unused-vars const CustomToolbar = () => ( @@ -80,6 +85,7 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => window.open(urlFileBlob); }; + // eslint-disable-next-line no-unused-vars const columnTitlesArray = [ { headerName: 'Name', @@ -182,50 +188,16 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => type: mappingType(document.type) })); - const determineDocumentsTable = mappedDocuments?.length ? ( + const documents = mappedDocuments?.length ? ( // Render if documents - - ({ - id: document.name, - type: document.type, - name: document.name, - description: document.description, - delete: document, - 'upload date': document?.uploadDate.toLocaleDateString(), - 'expiration date': document?.endDate?.toLocaleDateString(), - 'preview file': document.fileUrl - }))} - pageSizeOptions={[10]} - initialState={{ - pagination: { - paginationModel: { pageSize: 10, page: 0 } - } - }} - slots={{ - toolbar: CustomToolbar - }} - disableColumnMenu - disableRowSelectionOnClick - sx={{ - '.MuiDataGrid-columnHeader': { - background: theme.palette.primary.light, - color: 'white' - }, - '.MuiDataGrid-columnSeparator': { - display: 'none' - } - }} - /> - + mappedDocuments.map((document) => ) ) : ( // Render if no documents ); // MAIN RETURN OF COMPONENT - return loadingDocuments ? : determineDocumentsTable; + return loadingDocuments ? : documents; }; export default DocumentTable; From a6bc9d56ee5f566abcb6a83f9acb6feb882ada07 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 9 Jul 2024 00:06:28 -0700 Subject: [PATCH 02/36] refactor(documents): Pull remaining document data for cards. Use elevated eventHandlers for cards to use DocumentTable logic. Delete doesn't work, but preview does. --- src/components/Documents/DocumentPreview.jsx | 94 ++++++------- src/components/Documents/DocumentTable.jsx | 132 ++----------------- 2 files changed, 60 insertions(+), 166 deletions(-) diff --git a/src/components/Documents/DocumentPreview.jsx b/src/components/Documents/DocumentPreview.jsx index ec264ed54..b48d47292 100644 --- a/src/components/Documents/DocumentPreview.jsx +++ b/src/components/Documents/DocumentPreview.jsx @@ -6,13 +6,16 @@ import Button from '@mui/material/Button'; import Container from '@mui/material/Container'; import Divider from '@mui/material/Divider'; import Grid from '@mui/material/Grid'; -import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import useMediaQuery from '@mui/material/useMediaQuery'; import { useTheme } from '@mui/material/styles'; +import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import ShareIcon from '@mui/icons-material/Share'; +import FileOpenIcon from '@mui/icons-material/FileOpen'; + /** * @typedef {import("../../typedefs.js").DocumentListContext} documentListObject //@todo Update this */ @@ -25,9 +28,12 @@ import { useTheme } from '@mui/material/styles'; * @name DocumentPreview * @param {object} Props - Component props for Document Preview * @param {documentListObject} Props.document - The document object + * @param {EventListener} Props.onPreview - The document preview event + * @param {EventListener} Props.onShare - The document share event + * @param {EventListener} Props.onRemove - The document remove event * @returns {React.JSX.Element} React component for DocumentPreview */ -const DocumentPreview = ({ document }) => { +const DocumentPreview = ({ document, onPreview, onShare, onRemove }) => { /** * @todo: Import document utilities from * @file DocumentListContext.jsx @@ -35,27 +41,28 @@ const DocumentPreview = ({ document }) => { */ /** @todo: Implement buttons */ - const handleShare = async () => { + + const handlePreview = async () => { try { - // await shareDocument(document); + await onPreview(); } catch { - throw new Error('Failed to update read status'); + throw new Error('Failed to preview'); } }; - const handleReplace = async () => { + const handleShare = async () => { try { - // await replaceDocument(document); + await onShare(); } catch { - throw new Error('Failed to update read status'); + throw new Error('Failed to share'); } }; const handleRemove = async () => { try { - // await removeDocument(document); + await onRemove(); } catch { - throw new Error('Failed to update read status'); + throw new Error('Failed to remove'); } }; @@ -88,6 +95,16 @@ const DocumentPreview = ({ document }) => { title: 'Description: ', text: document?.description, xs_value: isSmallScreen ? 12 : renderMediumGridRight() + }, + { + title: 'Upload Date: ', + text: document?.uploadDate.toLocaleDateString(), + xs_value: isSmallScreen ? 12 : renderMediumGridLeft() + }, + { + title: 'Expiration Date: ', + text: document?.endDate?.toLocaleDateString(), + xs_value: isSmallScreen ? 12 : renderMediumGridLeft() } ]; @@ -108,43 +125,30 @@ const DocumentPreview = ({ document }) => { - - - {documentInfo.map((info, index) => ( - - - {info.title} {info.text} - - - ))} - - - - - - - - + + {documentInfo.map((info, index) => ( + + + {info.title} {info.text} + + ))} + + + + + + + + - + diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index cf0ffb3ad..719303761 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -2,41 +2,16 @@ import React, { useContext } from 'react'; // Constants import DOC_TYPES from '@constants/doc_types'; -// Material UI Imports -// eslint-disable-next-line no-unused-vars -import Box from '@mui/material/Box'; -import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; -import ShareIcon from '@mui/icons-material/Share'; -import FileOpenIcon from '@mui/icons-material/FileOpen'; -import { - // eslint-disable-next-line no-unused-vars - DataGrid, - GridActionsCellItem, - GridToolbarContainer, - GridToolbarDensitySelector, - GridToolbarFilterButton -} from '@mui/x-data-grid'; + // Context Imports import { DocumentListContext } from '@contexts'; import { useSession } from '@hooks'; // Utility Imports import { getBlobFromSolid } from '@utils'; -// Theme Imports -// eslint-disable-next-line no-unused-vars -import theme from '../../theme'; // Component Imports import { EmptyListNotification, LoadingAnimation } from '../Notification'; import DocumentPreview from './DocumentPreview'; -// DataGrid Toolbar -// eslint-disable-next-line no-unused-vars -const CustomToolbar = () => ( - - - - -); - /** * DocumentTable - Component that shows the list of documents * stored on Solid @@ -79,106 +54,13 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => * @returns {Promise} A promise that resolves with the Blob of the document. * @throws {Error} Throws an error if there is an issue fetching the document blob. */ + const urlFileBlob = await getBlobFromSolid(session, urlToOpen); // Opens a new window with the Blob URL displaying the document. window.open(urlFileBlob); }; - // eslint-disable-next-line no-unused-vars - const columnTitlesArray = [ - { - headerName: 'Name', - field: 'name', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Type', - field: 'type', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Description', - field: 'description', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Upload Date', - field: 'upload date', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Expiration Date', - field: 'expiration date', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Preview File', - field: 'preview file', - minWidth: 100, - flex: 1, - headerAlign: 'center', - align: 'center', - sortable: false, - filterable: false, - renderCell: (fileUrl) => ( - } - onClick={() => handleShowDocumentLocal(fileUrl)} - label="Preview" - /> - ) - }, - { - headerName: 'Sharing', - field: 'sharing', - type: 'actions', - minWidth: 80, - flex: 1, - headerAlign: 'center', - align: 'center', - getActions: (data) => [ - } - onClick={() => handleAclPermissionsModal('document', data.row.id, data.row.type)} - label="Share" - /> - ] - }, - { - headerName: 'Delete', - field: 'actions', - type: 'actions', - minWidth: 80, - flex: 1, - headerAlign: 'center', - align: 'center', - getActions: (data) => [ - } - onClick={() => handleSelectDeleteDoc(data.row.delete)} - label="Delete" - /> - ] - } - ]; - // Updates type value to use DOC_TYPES for formatting the string const mappingType = (type) => DOC_TYPES[type] || type; @@ -190,7 +72,15 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => const documents = mappedDocuments?.length ? ( // Render if documents - mappedDocuments.map((document) => ) + mappedDocuments.map((document) => ( + handleAclPermissionsModal('document', document.id, document.type)} + onRemove={() => handleSelectDeleteDoc(document.delete)} + onPreview={() => handleShowDocumentLocal(document.fileUrl)} + /> + )) ) : ( // Render if no documents From e924cd40372beab3965b82fd3cdf2aa59edf4d1a Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 9 Jul 2024 19:13:45 -0700 Subject: [PATCH 03/36] refactor(documents): Fix delete. Debugging share in case of filenames with spaces. --- src/components/Documents/DocumentTable.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index 719303761..ff400706b 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -74,10 +74,10 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => // Render if documents mappedDocuments.map((document) => ( handleAclPermissionsModal('document', document.id, document.type)} - onRemove={() => handleSelectDeleteDoc(document.delete)} + onShare={() => handleAclPermissionsModal('document', document.name, document.type)} + onRemove={() => handleSelectDeleteDoc(document)} onPreview={() => handleShowDocumentLocal(document.fileUrl)} /> )) @@ -85,7 +85,7 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => // Render if no documents ); - + // Failed to share. Reason: Fetching the metadata of the Resource at [http://localhost:3000/rss/PASS/Documents/New_Text%20Document%20(2).txt] failed: [404] [Not Found]. // MAIN RETURN OF COMPONENT return loadingDocuments ? : documents; }; From 984e06790dd280d27bdc2009b41778da447417d2 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 9 Jul 2024 19:47:58 -0700 Subject: [PATCH 04/36] refactor(documents): Fix share. Adjust document URL encoding to follow ' ' -> '%20' convention. All buttons functional. --- .../Documents/{DocumentPreview.jsx => DocumentCard.jsx} | 4 ++-- src/components/Documents/DocumentTable.jsx | 8 ++++---- src/utils/network/session-core.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/components/Documents/{DocumentPreview.jsx => DocumentCard.jsx} (97%) diff --git a/src/components/Documents/DocumentPreview.jsx b/src/components/Documents/DocumentCard.jsx similarity index 97% rename from src/components/Documents/DocumentPreview.jsx rename to src/components/Documents/DocumentCard.jsx index b48d47292..c12fb093c 100644 --- a/src/components/Documents/DocumentPreview.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -33,7 +33,7 @@ import FileOpenIcon from '@mui/icons-material/FileOpen'; * @param {EventListener} Props.onRemove - The document remove event * @returns {React.JSX.Element} React component for DocumentPreview */ -const DocumentPreview = ({ document, onPreview, onShare, onRemove }) => { +const DocumentCard = ({ document, onPreview, onShare, onRemove }) => { /** * @todo: Import document utilities from * @file DocumentListContext.jsx @@ -155,4 +155,4 @@ const DocumentPreview = ({ document, onPreview, onShare, onRemove }) => { ); }; -export default DocumentPreview; +export default DocumentCard; diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index ff400706b..5a3602afd 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -10,7 +10,7 @@ import { useSession } from '@hooks'; import { getBlobFromSolid } from '@utils'; // Component Imports import { EmptyListNotification, LoadingAnimation } from '../Notification'; -import DocumentPreview from './DocumentPreview'; +import DocumentCard from './DocumentCard'; /** * DocumentTable - Component that shows the list of documents @@ -70,10 +70,10 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => type: mappingType(document.type) })); - const documents = mappedDocuments?.length ? ( + const documentCards = mappedDocuments?.length ? ( // Render if documents mappedDocuments.map((document) => ( - handleAclPermissionsModal('document', document.name, document.type)} @@ -87,7 +87,7 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => ); // Failed to share. Reason: Fetching the metadata of the Resource at [http://localhost:3000/rss/PASS/Documents/New_Text%20Document%20(2).txt] failed: [404] [Not Found]. // MAIN RETURN OF COMPONENT - return loadingDocuments ? : documents; + return loadingDocuments ? : documentCards; }; export default DocumentTable; diff --git a/src/utils/network/session-core.js b/src/utils/network/session-core.js index d66d09655..83c7c3401 100644 --- a/src/utils/network/session-core.js +++ b/src/utils/network/session-core.js @@ -41,7 +41,7 @@ import { */ export const setDocAclPermission = async (session, docName, permissions, podUrl, webId) => { const containerUrl = `${podUrl}PASS/Documents/`; - const documentUrl = `${containerUrl}${docName.replace("'", '').replace(' ', '_')}`; + const documentUrl = `${containerUrl}${docName.replace("'", '').replace(' ', '%20')}`; await universalAccess.setAgentAccess(documentUrl, webId, permissions, { fetch: session.fetch }); }; From 200d98f567d8fa225f356134e955f1312f925705 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Wed, 10 Jul 2024 18:21:56 -0700 Subject: [PATCH 05/36] [refactor,style](documents): Reintegrate desktop documents table. Implement mobile switch to document cards and responsiveness. --- src/components/Documents/DocumentCard.jsx | 13 -- src/components/Documents/DocumentTable.jsx | 167 ++++++++++++++++++++- 2 files changed, 165 insertions(+), 15 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index c12fb093c..a6f0680f0 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -108,19 +108,6 @@ const DocumentCard = ({ document, onPreview, onShare, onRemove }) => { } ]; - /** - * DOCUMENT INFO - * ------------------ - id: document.name, - type: document.type, - name: document.name, - description: document.description, - delete: document, - 'upload date': document?.uploadDate.toLocaleDateString(), - 'expiration date': document?.endDate?.toLocaleDateString(), - 'preview file': document.fileUrl - */ - return ( diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index 5a3602afd..ec00cb09a 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -2,16 +2,38 @@ import React, { useContext } from 'react'; // Constants import DOC_TYPES from '@constants/doc_types'; - +// Material UI Imports +import { useMediaQuery } from '@mui/material'; +import Box from '@mui/material/Box'; +import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import ShareIcon from '@mui/icons-material/Share'; +import FileOpenIcon from '@mui/icons-material/FileOpen'; +import { + DataGrid, + GridActionsCellItem, + GridToolbarContainer, + GridToolbarDensitySelector, + GridToolbarFilterButton +} from '@mui/x-data-grid'; // Context Imports import { DocumentListContext } from '@contexts'; import { useSession } from '@hooks'; // Utility Imports import { getBlobFromSolid } from '@utils'; +// Theme Imports +import theme from '../../theme'; // Component Imports import { EmptyListNotification, LoadingAnimation } from '../Notification'; import DocumentCard from './DocumentCard'; +// DataGrid Toolbar +const CustomToolbar = () => ( + + + + +); + /** * DocumentTable - Component that shows the list of documents * stored on Solid @@ -30,6 +52,8 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => const { session } = useSession(); const { documentListObject, loadingDocuments } = useContext(DocumentListContext); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + /** * Handles the local display of a document by opening it in a new window. * @@ -61,6 +85,99 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => window.open(urlFileBlob); }; + const columnTitlesArray = [ + { + headerName: 'Name', + field: 'name', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Type', + field: 'type', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Description', + field: 'description', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Upload Date', + field: 'upload date', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Expiration Date', + field: 'expiration date', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Preview File', + field: 'preview file', + minWidth: 100, + flex: 1, + headerAlign: 'center', + align: 'center', + sortable: false, + filterable: false, + renderCell: (fileUrl) => ( + } + onClick={() => handleShowDocumentLocal(fileUrl)} + label="Preview" + /> + ) + }, + { + headerName: 'Sharing', + field: 'sharing', + type: 'actions', + minWidth: 80, + flex: 1, + headerAlign: 'center', + align: 'center', + getActions: (data) => [ + } + onClick={() => handleAclPermissionsModal('document', data.row.id, data.row.type)} + label="Share" + /> + ] + }, + { + headerName: 'Delete', + field: 'actions', + type: 'actions', + minWidth: 80, + flex: 1, + headerAlign: 'center', + align: 'center', + getActions: (data) => [ + } + onClick={() => handleSelectDeleteDoc(data.row.delete)} + label="Delete" + /> + ] + } + ]; + // Updates type value to use DOC_TYPES for formatting the string const mappingType = (type) => DOC_TYPES[type] || type; @@ -70,6 +187,48 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => type: mappingType(document.type) })); + const documentTable = mappedDocuments?.length ? ( + // Render if documents + + ({ + id: document.name, + type: document.type, + name: document.name, + description: document.description, + delete: document, + 'upload date': document?.uploadDate.toLocaleDateString(), + 'expiration date': document?.endDate?.toLocaleDateString(), + 'preview file': document.fileUrl + }))} + pageSizeOptions={[10]} + initialState={{ + pagination: { + paginationModel: { pageSize: 10, page: 0 } + } + }} + slots={{ + toolbar: CustomToolbar + }} + disableColumnMenu + disableRowSelectionOnClick + sx={{ + '.MuiDataGrid-columnHeader': { + background: theme.palette.primary.light, + color: 'white' + }, + '.MuiDataGrid-columnSeparator': { + display: 'none' + } + }} + /> + + ) : ( + // Render if no documents + + ); + const documentCards = mappedDocuments?.length ? ( // Render if documents mappedDocuments.map((document) => ( @@ -87,7 +246,11 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => ); // Failed to share. Reason: Fetching the metadata of the Resource at [http://localhost:3000/rss/PASS/Documents/New_Text%20Document%20(2).txt] failed: [404] [Not Found]. // MAIN RETURN OF COMPONENT - return loadingDocuments ? : documentCards; + if (isMobile) { + return loadingDocuments ? : documentCards; + } + + return loadingDocuments ? : documentTable; }; export default DocumentTable; From e288c86c7c0055755eaab9e239cdf7cb5536eac1 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 16 Jul 2024 00:17:16 -0700 Subject: [PATCH 06/36] refactor(documents): Split desktop and mobile views into separate components (DocumentsDesktop, DocumentsMobile). Remove redundant handler redefinitions. --- src/components/Documents/DocumentCard.jsx | 52 ++--- src/components/Documents/DocumentTable.jsx | 188 ++---------------- src/components/Documents/DocumentsDesktop.jsx | 157 +++++++++++++++ src/components/Documents/DocumentsMobile.jsx | 15 ++ 4 files changed, 198 insertions(+), 214 deletions(-) create mode 100644 src/components/Documents/DocumentsDesktop.jsx create mode 100644 src/components/Documents/DocumentsMobile.jsx diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index a6f0680f0..c49ed737e 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -28,44 +28,16 @@ import FileOpenIcon from '@mui/icons-material/FileOpen'; * @name DocumentPreview * @param {object} Props - Component props for Document Preview * @param {documentListObject} Props.document - The document object - * @param {EventListener} Props.onPreview - The document preview event - * @param {EventListener} Props.onShare - The document share event - * @param {EventListener} Props.onRemove - The document remove event + * @param {object} Props.handlers - Object containing pertinent event handlers + * @param {EventListener} Props.handlers.onPreview - The document preview event + * @param Props.onShare + * @param Props.onDelete + * @param Props.onPreview + * @param {EventListener} Props.handlers.onShare - The document share event + * @param {EventListener} Props.handlers.onDelete - The document delete event * @returns {React.JSX.Element} React component for DocumentPreview */ -const DocumentCard = ({ document, onPreview, onShare, onRemove }) => { - /** - * @todo: Import document utilities from - * @file DocumentListContext.jsx - * @file DocumentList.js - */ - - /** @todo: Implement buttons */ - - const handlePreview = async () => { - try { - await onPreview(); - } catch { - throw new Error('Failed to preview'); - } - }; - - const handleShare = async () => { - try { - await onShare(); - } catch { - throw new Error('Failed to share'); - } - }; - - const handleRemove = async () => { - try { - await onRemove(); - } catch { - throw new Error('Failed to remove'); - } - }; - +const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const theme = useTheme(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); @@ -124,13 +96,13 @@ const DocumentCard = ({ document, onPreview, onShare, onRemove }) => { - - - diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index ec00cb09a..b049fb7b1 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -4,17 +4,7 @@ import React, { useContext } from 'react'; import DOC_TYPES from '@constants/doc_types'; // Material UI Imports import { useMediaQuery } from '@mui/material'; -import Box from '@mui/material/Box'; -import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; -import ShareIcon from '@mui/icons-material/Share'; -import FileOpenIcon from '@mui/icons-material/FileOpen'; -import { - DataGrid, - GridActionsCellItem, - GridToolbarContainer, - GridToolbarDensitySelector, - GridToolbarFilterButton -} from '@mui/x-data-grid'; + // Context Imports import { DocumentListContext } from '@contexts'; import { useSession } from '@hooks'; @@ -24,15 +14,9 @@ import { getBlobFromSolid } from '@utils'; import theme from '../../theme'; // Component Imports import { EmptyListNotification, LoadingAnimation } from '../Notification'; -import DocumentCard from './DocumentCard'; -// DataGrid Toolbar -const CustomToolbar = () => ( - - - - -); +import DocumentsMobile from './DocumentsMobile'; +import DocumentsDesktop from './DocumentsDesktop'; /** * DocumentTable - Component that shows the list of documents @@ -85,172 +69,28 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => window.open(urlFileBlob); }; - const columnTitlesArray = [ - { - headerName: 'Name', - field: 'name', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Type', - field: 'type', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Description', - field: 'description', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Upload Date', - field: 'upload date', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Expiration Date', - field: 'expiration date', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - headerName: 'Preview File', - field: 'preview file', - minWidth: 100, - flex: 1, - headerAlign: 'center', - align: 'center', - sortable: false, - filterable: false, - renderCell: (fileUrl) => ( - } - onClick={() => handleShowDocumentLocal(fileUrl)} - label="Preview" - /> - ) - }, - { - headerName: 'Sharing', - field: 'sharing', - type: 'actions', - minWidth: 80, - flex: 1, - headerAlign: 'center', - align: 'center', - getActions: (data) => [ - } - onClick={() => handleAclPermissionsModal('document', data.row.id, data.row.type)} - label="Share" - /> - ] - }, - { - headerName: 'Delete', - field: 'actions', - type: 'actions', - minWidth: 80, - flex: 1, - headerAlign: 'center', - align: 'center', - getActions: (data) => [ - } - onClick={() => handleSelectDeleteDoc(data.row.delete)} - label="Delete" - /> - ] - } - ]; - // Updates type value to use DOC_TYPES for formatting the string const mappingType = (type) => DOC_TYPES[type] || type; // Map types for each document in the array - const mappedDocuments = documentListObject?.docList.map((document) => ({ + const documents = documentListObject?.docList.map((document) => ({ ...document, type: mappingType(document.type) })); - const documentTable = mappedDocuments?.length ? ( - // Render if documents - - ({ - id: document.name, - type: document.type, - name: document.name, - description: document.description, - delete: document, - 'upload date': document?.uploadDate.toLocaleDateString(), - 'expiration date': document?.endDate?.toLocaleDateString(), - 'preview file': document.fileUrl - }))} - pageSizeOptions={[10]} - initialState={{ - pagination: { - paginationModel: { pageSize: 10, page: 0 } - } - }} - slots={{ - toolbar: CustomToolbar - }} - disableColumnMenu - disableRowSelectionOnClick - sx={{ - '.MuiDataGrid-columnHeader': { - background: theme.palette.primary.light, - color: 'white' - }, - '.MuiDataGrid-columnSeparator': { - display: 'none' - } - }} - /> - - ) : ( - // Render if no documents - - ); + const handlers = { + onShare: handleAclPermissionsModal, + onDelete: handleSelectDeleteDoc, + onPreview: handleShowDocumentLocal + }; - const documentCards = mappedDocuments?.length ? ( - // Render if documents - mappedDocuments.map((document) => ( - handleAclPermissionsModal('document', document.name, document.type)} - onRemove={() => handleSelectDeleteDoc(document)} - onPreview={() => handleShowDocumentLocal(document.fileUrl)} - /> - )) + if (!documents?.length) return ; + if (loadingDocuments) return ; + return isMobile ? ( + ) : ( - // Render if no documents - + ); - // Failed to share. Reason: Fetching the metadata of the Resource at [http://localhost:3000/rss/PASS/Documents/New_Text%20Document%20(2).txt] failed: [404] [Not Found]. - // MAIN RETURN OF COMPONENT - if (isMobile) { - return loadingDocuments ? : documentCards; - } - - return loadingDocuments ? : documentTable; }; export default DocumentTable; diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx new file mode 100644 index 000000000..8ae57fd6c --- /dev/null +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -0,0 +1,157 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import ShareIcon from '@mui/icons-material/Share'; +import FileOpenIcon from '@mui/icons-material/FileOpen'; +import { + DataGrid, + GridActionsCellItem, + GridToolbarContainer, + GridToolbarDensitySelector, + GridToolbarFilterButton +} from '@mui/x-data-grid'; +// Theme Imports +import theme from '../../theme'; + +// DataGrid Toolbar +const CustomToolbar = () => ( + + + + +); + +const DocumentsDesktop = ({ documents, handlers }) => { + const columnTitlesArray = [ + { + headerName: 'Name', + field: 'name', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Type', + field: 'type', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Description', + field: 'description', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Upload Date', + field: 'upload date', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Expiration Date', + field: 'expiration date', + minWidth: 120, + flex: 1, + headerAlign: 'center', + align: 'center' + }, + { + headerName: 'Preview File', + field: 'preview file', + minWidth: 100, + flex: 1, + headerAlign: 'center', + align: 'center', + sortable: false, + filterable: false, + renderCell: (fileUrl) => ( + } + onClick={() => handlers.onPreview(fileUrl)} + label="Preview" + /> + ) + }, + { + headerName: 'Sharing', + field: 'sharing', + type: 'actions', + minWidth: 80, + flex: 1, + headerAlign: 'center', + align: 'center', + getActions: (data) => [ + } + onClick={() => handlers.onShare('document', data.row.id, data.row.type)} + label="Share" + /> + ] + }, + { + headerName: 'Delete', + field: 'actions', + type: 'actions', + minWidth: 80, + flex: 1, + headerAlign: 'center', + align: 'center', + getActions: (data) => [ + } + onClick={() => handlers.onDelete(data.row.delete)} + label="Delete" + /> + ] + } + ]; + // Render if documents + return ( + + ({ + id: document.name, + type: document.type, + name: document.name, + description: document.description, + delete: document, + 'upload date': document?.uploadDate.toLocaleDateString(), + 'expiration date': document?.endDate?.toLocaleDateString(), + 'preview file': document.fileUrl + }))} + pageSizeOptions={[10]} + initialState={{ + pagination: { + paginationModel: { pageSize: 10, page: 0 } + } + }} + slots={{ + toolbar: CustomToolbar + }} + disableColumnMenu + disableRowSelectionOnClick + sx={{ + '.MuiDataGrid-columnHeader': { + background: theme.palette.primary.light, + color: 'white' + }, + '.MuiDataGrid-columnSeparator': { + display: 'none' + } + }} + /> + + ); +}; + +export default DocumentsDesktop; diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx new file mode 100644 index 000000000..90dc9a7d1 --- /dev/null +++ b/src/components/Documents/DocumentsMobile.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import DocumentCard from './DocumentCard'; + +const DocumentsMobile = ({ documents, handlers }) => + documents.map((document) => ( + handlers.onShare('document', document.name, document.type)} + onDelete={() => handlers.onDelete(document)} + onPreview={() => handlers.onPreview(document.fileUrl)} + /> + )); + +export default DocumentsMobile; From e5f067b19da0bb0793a92d0ff8969cb16e613794 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Sat, 20 Jul 2024 19:22:39 -0700 Subject: [PATCH 07/36] [chore,style](documents - desktop): Complete JSDocs comments for documents components. Adopt Contacts stylization methodology for desktop documents. --- src/components/Documents/DocumentCard.jsx | 27 ++--- src/components/Documents/DocumentTable.jsx | 19 +++- src/components/Documents/DocumentsDesktop.jsx | 104 ++++++++++-------- src/components/Documents/DocumentsMobile.jsx | 82 ++++++++++++-- 4 files changed, 161 insertions(+), 71 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index c49ed737e..133ade7af 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -17,25 +17,26 @@ import ShareIcon from '@mui/icons-material/Share'; import FileOpenIcon from '@mui/icons-material/FileOpen'; /** - * @typedef {import("../../typedefs.js").DocumentListContext} documentListObject //@todo Update this + * @typedef {object} Document + * @property {string} name - The given name of the document + * @property {string} type - The given type of the document + * @property {string} description - The given description of the document + * @property {string} uploadDate- The upload date of the document + * @property {string} endDate - The expiration date of the document + * @property {string} fileUrl - The file URL of the document */ /** - * DocumentPreview - Component that displays document previews from - * user's documents container + * DocumentCard - Component that contains a document * * @memberof Documents - * @name DocumentPreview + * @name DocumentCard * @param {object} Props - Component props for Document Preview - * @param {documentListObject} Props.document - The document object - * @param {object} Props.handlers - Object containing pertinent event handlers - * @param {EventListener} Props.handlers.onPreview - The document preview event - * @param Props.onShare - * @param Props.onDelete - * @param Props.onPreview - * @param {EventListener} Props.handlers.onShare - The document share event - * @param {EventListener} Props.handlers.onDelete - The document delete event - * @returns {React.JSX.Element} React component for DocumentPreview + * @param {Document} Props.document - The document + * @param {Function} Props.onPreview - The document preview event + * @param {Function} Props.onShare - The document share event + * @param {Function} Props.onDelete - The document delete event + * @returns {React.JSX.Element} React component for DocumentCard */ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const theme = useTheme(); diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index b049fb7b1..d0308e243 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -3,6 +3,7 @@ import React, { useContext } from 'react'; // Constants import DOC_TYPES from '@constants/doc_types'; // Material UI Imports +import Box from '@mui/material/Box'; import { useMediaQuery } from '@mui/material'; // Context Imports @@ -86,10 +87,20 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => if (!documents?.length) return ; if (loadingDocuments) return ; - return isMobile ? ( - - ) : ( - + return ( + + {isMobile ? ( + + ) : ( + + )} + ); }; diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index 8ae57fd6c..93f47796e 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import Box from '@mui/material/Box'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import ShareIcon from '@mui/icons-material/Share'; import FileOpenIcon from '@mui/icons-material/FileOpen'; @@ -21,10 +20,36 @@ const CustomToolbar = () => ( ); +/** + * @typedef {object} Document + * @property {string} name - The given name of the document + * @property {string} type - The given type of the document + * @property {string} description - The given description of the document + * @property {string} uploadDate- The upload date of the document + * @property {string} endDate - The expiration date of the document + * @property {string} fileUrl - The file URL of the document + */ + +/** + * @typedef {object} Handlers + * @property {Function} onPreview - Function to handle document previewing. + * @property {Function} onShare - Function to handle document sharing. + * @property {Function} onDelete - Function to handle document deletion. + */ + +/** + * DocumentsDesktop - Component for displaying documents in a DataGrid + * + * @memberof Documents + * @name DocumentsDesktop + * @param {object} Props - The props for DocumentsDesktop + * @param {Document[]} Props.documents - The list of documents to display + * @param {Handlers} Props.handlers - Object containing event handler functions. + * @returns {React.JSX.Element} The DocumentsDesktop component + */ const DocumentsDesktop = ({ documents, handlers }) => { const columnTitlesArray = [ { - headerName: 'Name', field: 'name', minWidth: 120, flex: 1, @@ -32,7 +57,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center' }, { - headerName: 'Type', field: 'type', minWidth: 120, flex: 1, @@ -40,7 +64,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center' }, { - headerName: 'Description', field: 'description', minWidth: 120, flex: 1, @@ -48,7 +71,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center' }, { - headerName: 'Upload Date', field: 'upload date', minWidth: 120, flex: 1, @@ -56,7 +78,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center' }, { - headerName: 'Expiration Date', field: 'expiration date', minWidth: 120, flex: 1, @@ -64,7 +85,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center' }, { - headerName: 'Preview File', field: 'preview file', minWidth: 100, flex: 1, @@ -82,7 +102,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { ) }, { - headerName: 'Sharing', field: 'sharing', type: 'actions', minWidth: 80, @@ -98,7 +117,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { ] }, { - headerName: 'Delete', field: 'actions', type: 'actions', minWidth: 80, @@ -116,41 +134,39 @@ const DocumentsDesktop = ({ documents, handlers }) => { ]; // Render if documents return ( - - ({ - id: document.name, - type: document.type, - name: document.name, - description: document.description, - delete: document, - 'upload date': document?.uploadDate.toLocaleDateString(), - 'expiration date': document?.endDate?.toLocaleDateString(), - 'preview file': document.fileUrl - }))} - pageSizeOptions={[10]} - initialState={{ - pagination: { - paginationModel: { pageSize: 10, page: 0 } - } - }} - slots={{ - toolbar: CustomToolbar - }} - disableColumnMenu - disableRowSelectionOnClick - sx={{ - '.MuiDataGrid-columnHeader': { - background: theme.palette.primary.light, - color: 'white' - }, - '.MuiDataGrid-columnSeparator': { - display: 'none' - } - }} - /> - + ({ + id: document.name, + type: document.type, + name: document.name, + description: document.description, + delete: document, + 'upload date': document?.uploadDate.toLocaleDateString(), + 'expiration date': document?.endDate?.toLocaleDateString(), + 'preview file': document.fileUrl + }))} + pageSizeOptions={[10]} + initialState={{ + pagination: { + paginationModel: { pageSize: 10, page: 0 } + } + }} + slots={{ + toolbar: CustomToolbar + }} + sx={{ + '.MuiDataGrid-columnHeader': { + background: theme.palette.primary.light, + color: 'white' + }, + '.MuiDataGrid-columnSeparator': { + display: 'none' + } + }} + disableColumnMenu + disableRowSelectionOnClick + /> ); }; diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 90dc9a7d1..7cbf2512d 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -1,15 +1,77 @@ import React from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; + +import { useTheme } from '@mui/material/styles'; + import DocumentCard from './DocumentCard'; -const DocumentsMobile = ({ documents, handlers }) => - documents.map((document) => ( - handlers.onShare('document', document.name, document.type)} - onDelete={() => handlers.onDelete(document)} - onPreview={() => handlers.onPreview(document.fileUrl)} - /> - )); +/** + * @typedef {object} Document + * @property {string} name - The given name of the document + * @property {string} type - The given type of the document + * @property {string} description - The given description of the document + * @property {string} uploadDate- The upload date of the document + * @property {string} endDate - The expiration date of the document + * @property {string} fileUrl - The file URL of the document + */ + +/** + * @typedef {object} Handlers + * @property {Function} onPreview - Function to handle document previewing. + * @property {Function} onShare - Function to handle document sharing. + * @property {Function} onDelete - Function to handle document deletion. + */ + +/** + * DocumentsMobile - Component for displaying documents in a DataGrid + * + * @memberof Documents + * @name DocumentsDesktop + * @param {object} Props - The props for DocumentsMobile + * @param {Document[]} Props.documents - The list of documents to display + * @param {Handlers} Props.handlers - Object containing event handler functions. + * @returns {React.JSX.Element} The DocumentsMobile component + */ +const DocumentsMobile = ({ documents, handlers }) => { + const theme = useTheme(); + return ( + + + + Name + + Actions + + + + {documents.map((document) => ( + handlers.onShare('document', document.name, document.type)} + onDelete={() => handlers.onDelete(document)} + onPreview={() => handlers.onPreview(document.fileUrl)} + /> + ))} + + ); +}; export default DocumentsMobile; From 8a7163c9e77a0c10b3b01b567c37517ea7c94d93 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Sun, 21 Jul 2024 17:05:42 -0700 Subject: [PATCH 08/36] style(mobile documents): Apply mobile contacts styling methodology to mobile documents. Actions follow same dropdown effect with appropriate icons and remain functional. --- src/components/Documents/DocumentCard.jsx | 176 ++++++++++++--------- src/components/Documents/DocumentTable.jsx | 7 +- 2 files changed, 102 insertions(+), 81 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 133ade7af..8b7081fcd 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -1,20 +1,18 @@ // React Imports -import React from 'react'; +import React, { useState } from 'react'; // Material UI Imports import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import Container from '@mui/material/Container'; -import Divider from '@mui/material/Divider'; -import Grid from '@mui/material/Grid'; -import Paper from '@mui/material/Paper'; -import Stack from '@mui/material/Stack'; -import Typography from '@mui/material/Typography'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import { useTheme } from '@mui/material/styles'; - -import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import PreviewIcon from '@mui/icons-material/Preview'; import ShareIcon from '@mui/icons-material/Share'; -import FileOpenIcon from '@mui/icons-material/FileOpen'; +import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import IconButton from '@mui/material/IconButton'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import Typography from '@mui/material/Typography'; /** * @typedef {object} Document @@ -39,79 +37,101 @@ import FileOpenIcon from '@mui/icons-material/FileOpen'; * @returns {React.JSX.Element} React component for DocumentCard */ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { - const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); - const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); + const [anchorEl, setAnchorEl] = useState(null); + const [openMenu, setOpenMenu] = useState(null); - const renderMediumGridLeft = () => { - if (isMediumScreen) return 8; - return 5; + const handleClick = (event, clickedDocument) => { + setAnchorEl(event.currentTarget); + setOpenMenu(clickedDocument.id); + }; + const handleClose = () => { + setAnchorEl(null); + setOpenMenu(null); }; - const renderMediumGridRight = () => { - if (isMediumScreen) return 4; - return 2; + const handleMenuItemClick = (action, clickedDocument) => () => { + action(clickedDocument); + handleClose(); }; - const documentInfo = [ - { - title: 'Name: ', - text: document?.name, - xs_value: isSmallScreen ? 12 : renderMediumGridLeft() - }, - { - title: 'Type: ', - text: document?.type, - xs_value: isSmallScreen ? 12 : renderMediumGridLeft() - }, - { - title: 'Description: ', - text: document?.description, - xs_value: isSmallScreen ? 12 : renderMediumGridRight() - }, - { - title: 'Upload Date: ', - text: document?.uploadDate.toLocaleDateString(), - xs_value: isSmallScreen ? 12 : renderMediumGridLeft() - }, - { - title: 'Expiration Date: ', - text: document?.endDate?.toLocaleDateString(), - xs_value: isSmallScreen ? 12 : renderMediumGridLeft() - } - ]; + const iconSize = { + height: '24px', + width: '24px' + }; - return ( - - - - - {documentInfo.map((info, index) => ( - - - {info.title} {info.text} - - - ))} + const iconStyling = { + width: '100%' + }; - - - - - - - - - - - - + return ( + + + + + + {document.name || '[No name given]'} + + + handleClick(event, document)} + > + + + + + + + } + sx={iconStyling} + > + Preview + + } + sx={iconStyling} + > + Share + + } + sx={iconStyling} + > + Delete + + + + ); }; diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index d0308e243..f4d12d518 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -1,5 +1,5 @@ // React Imports -import React, { useContext } from 'react'; +import React, { useContext, useId } from 'react'; // Constants import DOC_TYPES from '@constants/doc_types'; // Material UI Imports @@ -75,8 +75,9 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => // Map types for each document in the array const documents = documentListObject?.docList.map((document) => ({ - ...document, - type: mappingType(document.type) + id: useId(), + type: mappingType(document.type), + ...document })); const handlers = { From e6ee3bb172e799ba2b28ece04138e0eb0b565a8d Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 23 Jul 2024 14:07:14 -0700 Subject: [PATCH 09/36] fix(documents): In light of the concise mapping of documents and the nature of hooks, using UUID instead of useId() to avoid changes in hook order. --- src/components/Documents/DocumentCard.jsx | 2 +- src/components/Documents/DocumentTable.jsx | 17 +++++++++++------ src/components/Documents/DocumentsMobile.jsx | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 8b7081fcd..918d0bd6b 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -64,7 +64,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { }; return ( - + const mappingType = (type) => DOC_TYPES[type] || type; // Map types for each document in the array - const documents = documentListObject?.docList.map((document) => ({ - id: useId(), - type: mappingType(document.type), - ...document - })); + const documents = useMemo( + () => + documentListObject?.docList.map((document) => ({ + id: uuidv4(), // Generate a UUID (a unique ID) + type: mappingType(document.type), + ...document + })), + [documentListObject?.docList] + ); const handlers = { onShare: handleAclPermissionsModal, diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 7cbf2512d..74ee069ef 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -63,7 +63,7 @@ const DocumentsMobile = ({ documents, handlers }) => { {documents.map((document) => ( handlers.onShare('document', document.name, document.type)} onDelete={() => handlers.onDelete(document)} From cad0534dc38eedb436ede164ddc42c9541e021b7 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Thu, 8 Aug 2024 03:41:27 -0700 Subject: [PATCH 10/36] style(DesktopDocuments): Restore and capitalize headers --- src/components/Documents/DocumentsDesktop.jsx | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index 93f47796e..a05ffef8f 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -50,42 +50,42 @@ const CustomToolbar = () => ( const DocumentsDesktop = ({ documents, handlers }) => { const columnTitlesArray = [ { - field: 'name', + field: 'Name', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, { - field: 'type', + field: 'Type', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, { - field: 'description', + field: 'Description', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, { - field: 'upload date', + field: 'Upload Date', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, { - field: 'expiration date', + field: 'Expiration Date', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, { - field: 'preview file', + field: 'Preview', minWidth: 100, flex: 1, headerAlign: 'center', @@ -102,14 +102,16 @@ const DocumentsDesktop = ({ documents, handlers }) => { ) }, { - field: 'sharing', - type: 'actions', - minWidth: 80, + field: 'Share', + minWidth: 100, flex: 1, headerAlign: 'center', align: 'center', - getActions: (data) => [ + sortable: false, + filterable: false, + renderCell: (data) => [ } onClick={() => handlers.onShare('document', data.row.id, data.row.type)} label="Share" @@ -117,13 +119,12 @@ const DocumentsDesktop = ({ documents, handlers }) => { ] }, { - field: 'actions', - type: 'actions', - minWidth: 80, + field: 'Delete', + minWidth: 100, flex: 1, headerAlign: 'center', align: 'center', - getActions: (data) => [ + renderCell: (data) => [ } onClick={() => handlers.onDelete(data.row.delete)} @@ -138,13 +139,14 @@ const DocumentsDesktop = ({ documents, handlers }) => { columns={columnTitlesArray} rows={documents.map((document) => ({ id: document.name, - type: document.type, - name: document.name, - description: document.description, - delete: document, - 'upload date': document?.uploadDate.toLocaleDateString(), - 'expiration date': document?.endDate?.toLocaleDateString(), - 'preview file': document.fileUrl + Name: document.name, + Type: document.type, + Description: document.description, + 'Upload Date': document?.uploadDate.toLocaleDateString(), + 'Expiration Date': document?.endDate?.toLocaleDateString(), + Preview: document.fileUrl, + Share: document, + Delete: document }))} pageSizeOptions={[10]} initialState={{ From f963828436b6aaee253c2ba1534070bd13c907ac Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Fri, 9 Aug 2024 22:33:14 -0700 Subject: [PATCH 11/36] chore(DesktopDocuments): Minimized data flow to action items. Extend unique ids as keys to action items. --- src/components/Documents/DocumentsDesktop.jsx | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index a05ffef8f..8b7619ba6 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -22,9 +22,10 @@ const CustomToolbar = () => ( /** * @typedef {object} Document - * @property {string} name - The given name of the document - * @property {string} type - The given type of the document - * @property {string} description - The given description of the document + * @property {string} id - The id of the document + * @property {string} name - The provided name of the document + * @property {string} type - The provided type of the document + * @property {string} description - The provided description of the document * @property {string} uploadDate- The upload date of the document * @property {string} endDate - The expiration date of the document * @property {string} fileUrl - The file URL of the document @@ -92,14 +93,17 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center', sortable: false, filterable: false, - renderCell: (fileUrl) => ( - } - onClick={() => handlers.onPreview(fileUrl)} - label="Preview" - /> - ) + renderCell: (data) => { + const [id, fileUrl] = data.value; + return ( + } + onClick={() => handlers.onPreview(fileUrl)} + label="Preview" + /> + ); + } }, { field: 'Share', @@ -109,14 +113,17 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center', sortable: false, filterable: false, - renderCell: (data) => [ - } - onClick={() => handlers.onShare('document', data.row.id, data.row.type)} - label="Share" - /> - ] + renderCell: (data) => { + const [id, type] = data.value; + return ( + } + onClick={() => handlers.onShare('document', id, type)} + label="Share" + /> + ); + } }, { field: 'Delete', @@ -124,13 +131,19 @@ const DocumentsDesktop = ({ documents, handlers }) => { flex: 1, headerAlign: 'center', align: 'center', - renderCell: (data) => [ - } - onClick={() => handlers.onDelete(data.row.delete)} - label="Delete" - /> - ] + sortable: false, + filterable: false, + renderCell: (data) => { + const [document] = data.value; + return ( + } + onClick={() => handlers.onDelete(document)} + label="Delete" + /> + ); + } } ]; // Render if documents @@ -138,15 +151,15 @@ const DocumentsDesktop = ({ documents, handlers }) => { ({ - id: document.name, + id: document.id, Name: document.name, Type: document.type, Description: document.description, 'Upload Date': document?.uploadDate.toLocaleDateString(), 'Expiration Date': document?.endDate?.toLocaleDateString(), - Preview: document.fileUrl, - Share: document, - Delete: document + Preview: [document.id, document.fileUrl], + Share: [document.id, document.name, document.type], + Delete: [document] }))} pageSizeOptions={[10]} initialState={{ From a41b965a5ed152981f7793815065ddfadff133d1 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Fri, 9 Aug 2024 22:40:48 -0700 Subject: [PATCH 12/36] style(DocumentsMobile): Use FileOpenIcon for Preview, in compliance with established convention --- src/components/Documents/DocumentCard.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 918d0bd6b..6fdd06e53 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -5,7 +5,7 @@ import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; -import PreviewIcon from '@mui/icons-material/Preview'; +import FileOpenIcon from '@mui/icons-material/FileOpen'; import ShareIcon from '@mui/icons-material/Share'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import IconButton from '@mui/material/IconButton'; @@ -108,7 +108,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { } + startIcon={} sx={iconStyling} > Preview From 9a6aa7da89650a929d0c266dddacf963ab0a79be Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Fri, 9 Aug 2024 23:59:15 -0700 Subject: [PATCH 13/36] style(DocumentsMobile): Add Description column header. Adapt styles to use Flexbox. Move styles into reuseable constants. --- src/components/Documents/DocumentsMobile.jsx | 25 ++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 74ee069ef..45097cdf1 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -35,6 +35,13 @@ import DocumentCard from './DocumentCard'; */ const DocumentsMobile = ({ documents, handlers }) => { const theme = useTheme(); + + // parent box element + const tableHeaderStyling = { display: 'flex', width: '100%', justifyContent: 'flex-start' }; + + // topography elements + const columnHeaderStyling = { display: 'flex', flexBasis: '50%', justifyContent: 'flex-start' }; + return ( { position: 'relative' }} > - - Name - - Actions - + + Name + Description + + Actions + {documents.map((document) => ( From d209dbf35bfb08a532dc8b2e4de64015fe83f8bb Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Sun, 25 Aug 2024 22:15:28 -0700 Subject: [PATCH 14/36] style(DocumentsMobile): Move upload and expiration dates to their own secondary row. Move Description to it's own bottom-level row. Style rows based on informational hierarchy and context. Simplify CSS. Modify truncate utility to allow for optional cutoff parameter. Utilize truncate utility to truncate document description at 140 characters. --- src/components/Documents/DocumentCard.jsx | 107 +++++++++++++++++-- src/components/Documents/DocumentTable.jsx | 2 +- src/components/Documents/DocumentsMobile.jsx | 20 ++-- src/utils/text/truncateText.js | 7 +- 4 files changed, 118 insertions(+), 18 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 6fdd06e53..18540712c 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -14,6 +14,9 @@ import MenuItem from '@mui/material/MenuItem'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import Typography from '@mui/material/Typography'; +// Utility Imports +import { truncateText } from '@utils'; + /** * @typedef {object} Document * @property {string} name - The given name of the document @@ -37,6 +40,11 @@ import Typography from '@mui/material/Typography'; * @returns {React.JSX.Element} React component for DocumentCard */ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { + // primary columns + // name, type, uploaddate, expiration, actions + // expandable information + // description + const [anchorEl, setAnchorEl] = useState(null); const [openMenu, setOpenMenu] = useState(null); @@ -54,6 +62,60 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { handleClose(); }; + const getTypeText = (type) => { + switch (type) { + case 'driversLicense': + return "Driver's License"; + case 'passport': + return 'Passport'; + case 'bankStatement': + return 'Bank Statement'; + default: + return 'Unknown Document Type'; + } + }; + + // styling for primary, top-level information + const primaryRowStyling = { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center' + }; + + // styling for primary, top-level information + const secondaryRowStyling = { + display: 'flex', + justifyContent: 'space-between', + marginTop: '10px', + columnGap: '10px', + flexWrap: 'wrap' + }; + + // styling for secondary information + const descriptionRowStyling = { + marginTop: '10px' + }; + + // ubiquitous information styling + const primaryColumnStyling = { + columnGap: '10px' + }; + + const secondaryColumnStyling = { + fontSize: '0.8rem', + color: 'text.secondary', + fontWeight: 'bold' + }; + + // secondary information styling + const descriptionColumnStyling = { + fontSize: '0.875rem', + wordWrap: 'break-word', + color: 'text.secondary', + fontStyle: 'italic' + }; + + // icon styles const iconSize = { height: '24px', width: '24px' @@ -72,16 +134,25 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { }} > - - - {document.name || '[No name given]'} + + + {document.name || '[No Name provided]'} + + + {document.type ? getTypeText(document.type) : 'N/A'} + { + + + {document.uploadDate + ? `Uploaded ${document.uploadDate.toLocaleDateString()}` + : 'Unknown Upload Date'} + + + {document.endDate + ? `Expires ${document.endDate.toLocaleDateString()}` + : 'No Expiration'} + + + + + {truncateText(document.description, 140)} + + const { session } = useSession(); const { documentListObject, loadingDocuments } = useContext(DocumentListContext); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); /** * Handles the local display of a document by opening it in a new window. diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 45097cdf1..bff671b5c 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -37,10 +37,10 @@ const DocumentsMobile = ({ documents, handlers }) => { const theme = useTheme(); // parent box element - const tableHeaderStyling = { display: 'flex', width: '100%', justifyContent: 'flex-start' }; - - // topography elements - const columnHeaderStyling = { display: 'flex', flexBasis: '50%', justifyContent: 'flex-start' }; + const tableHeaderStyling = { + display: 'flex', + justifyContent: 'space-between' + }; return ( @@ -55,9 +55,15 @@ const DocumentsMobile = ({ documents, handlers }) => { }} > - Name - Description - + Name + Type + Actions diff --git a/src/utils/text/truncateText.js b/src/utils/text/truncateText.js index d4867a173..efb834c6d 100644 --- a/src/utils/text/truncateText.js +++ b/src/utils/text/truncateText.js @@ -4,11 +4,12 @@ * @memberof utils * @function truncateText * @param {string} text - Text to be truncated + * @param {number} cutoff - Where to cut the text off * @returns {string} Returns the truncated text */ -const truncateText = (text) => { - if (text.length > 25) { - return `${text.substring(0, 25)}...`; +const truncateText = (text, cutoff = 25) => { + if (text.length > cutoff) { + return `${text.substring(0, cutoff)}...`; } return text; }; From 935cab58de0f3a55d978886783276b036a7c90a9 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Mon, 26 Aug 2024 22:19:14 -0700 Subject: [PATCH 15/36] style(DocumentsMobile): Remove Type column for consistency with Contacts mobile layout. Move all other (secondary) information to their own rows, offset slightly and beneath the Name. Utilize CSS nesting for consolidation and better organization and readability. --- src/components/Documents/DocumentCard.jsx | 164 ++++++++++--------- src/components/Documents/DocumentsMobile.jsx | 1 - 2 files changed, 87 insertions(+), 78 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 18540712c..d790a20fa 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -13,7 +13,6 @@ import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import Typography from '@mui/material/Typography'; - // Utility Imports import { truncateText } from '@utils'; @@ -71,48 +70,77 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { case 'bankStatement': return 'Bank Statement'; default: - return 'Unknown Document Type'; + return 'Other'; } }; - // styling for primary, top-level information - const primaryRowStyling = { + // Imported theme for reuse + // Have row, column, primaryInfo, secondaryInfo + + const rowStyling = { display: 'flex', - justifyContent: 'space-between', - alignItems: 'center' + columnGap: '10px' }; - // styling for primary, top-level information - const secondaryRowStyling = { + // contains the main column styling + const columnStyling = { display: 'flex', - justifyContent: 'space-between', - marginTop: '10px', - columnGap: '10px', - flexWrap: 'wrap' + flexDirection: 'column' }; - // styling for secondary information - const descriptionRowStyling = { - marginTop: '10px' + // top row in the data column containing the primary data + const dataStyling = { + ...rowStyling, + justifyContent: 'flex-start' }; - // ubiquitous information styling - const primaryColumnStyling = { - columnGap: '10px' + // contains styling for the left (data) column + const dataColumnStyling = { + ...columnStyling, + flex: '1 0 90%', + rowGap: '10px', + '& div': { + ...dataStyling + }, + '& div > div': { + rowGap: '10px' + } + }; + + // right (actions) column + const actionsColumnStyling = { + ...columnStyling, + flex: '1 0 10%', + maxWidth: '54px', + justifyContent: 'center', + alignItems: 'flex-end' + }; + + // lower row in the data column - section of secondary data rows + const secondaryDataSectionStyling = { + ...columnStyling, + paddingLeft: '20px', + '& div': { + ...dataStyling, + color: 'text.secondary', + fontSize: '.875rem' + } }; - const secondaryColumnStyling = { - fontSize: '0.8rem', - color: 'text.secondary', - fontWeight: 'bold' + const descriptionStyling = { + marginTop: '10px', + '& div': { + fontSize: '.8rem', + fontStyle: 'italic', + textWrap: 'wrap', + fontWeight: 'bold' + } }; - // secondary information styling - const descriptionColumnStyling = { - fontSize: '0.875rem', - wordWrap: 'break-word', - color: 'text.secondary', - fontStyle: 'italic' + const cardStyling = { + ...rowStyling, + justifyContent: 'space-between', + gap: '10px' }; // icon styles @@ -133,60 +161,42 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { position: 'relative' }} > - - - + + + {document.name || '[No Name provided]'} - - {document.type ? getTypeText(document.type) : 'N/A'} - - - - handleClick(event, document)} - > - - + + + {document.type ? getTypeText(document.type) : 'N/A'} + + + {document.uploadDate + ? `Uploaded ${document.uploadDate.toLocaleDateString()}` + : 'Unknown Upload Date'} + + + {document.endDate + ? `Expires ${document.endDate.toLocaleDateString()}` + : 'No Expiration'} + + + + {truncateText(document.description, 140)} + + - - - {document.uploadDate - ? `Uploaded ${document.uploadDate.toLocaleDateString()}` - : 'Unknown Upload Date'} - - + handleClick(event, document)} > - {document.endDate - ? `Expires ${document.endDate.toLocaleDateString()}` - : 'No Expiration'} - - - - - {truncateText(document.description, 140)} - + + { > Name - Type Date: Tue, 27 Aug 2024 01:46:40 -0700 Subject: [PATCH 16/36] style(DocumentsMobile): Adjust card style to more closely correspond with original design plan. --- src/components/Documents/DocumentCard.jsx | 94 +++++++++++------------ 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index d790a20fa..f70e404b4 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -13,6 +13,8 @@ import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import Typography from '@mui/material/Typography'; +import { useTheme } from '@mui/material/styles'; + // Utility Imports import { truncateText } from '@utils'; @@ -44,6 +46,8 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { // expandable information // description + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); const [openMenu, setOpenMenu] = useState(null); @@ -74,71 +78,53 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { } }; - // Imported theme for reuse - // Have row, column, primaryInfo, secondaryInfo - const rowStyling = { display: 'flex', columnGap: '10px' }; - // contains the main column styling const columnStyling = { display: 'flex', flexDirection: 'column' }; - // top row in the data column containing the primary data + // styling for data contained in each section const dataStyling = { ...rowStyling, + fontSize: '.8rem', justifyContent: 'flex-start' }; - // contains styling for the left (data) column + // styling for each data section in the document data column + const dataSectionStyling = { + ...columnStyling, + '& div': { + ...dataStyling + } + }; + + // styling for the document data column const dataColumnStyling = { ...columnStyling, - flex: '1 0 90%', + flex: '1 1 90%', rowGap: '10px', '& div': { - ...dataStyling - }, - '& div > div': { - rowGap: '10px' + ...dataSectionStyling } }; - // right (actions) column + // styling for the document actions column const actionsColumnStyling = { ...columnStyling, - flex: '1 0 10%', + flex: '0 1 10%', maxWidth: '54px', justifyContent: 'center', alignItems: 'flex-end' }; - // lower row in the data column - section of secondary data rows - const secondaryDataSectionStyling = { - ...columnStyling, - paddingLeft: '20px', - '& div': { - ...dataStyling, - color: 'text.secondary', - fontSize: '.875rem' - } - }; - - const descriptionStyling = { - marginTop: '10px', - '& div': { - fontSize: '.8rem', - fontStyle: 'italic', - textWrap: 'wrap', - fontWeight: 'bold' - } - }; - const cardStyling = { ...rowStyling, + background: theme.palette.background.tint, justifyContent: 'space-between', gap: '10px' }; @@ -163,28 +149,40 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { > - - {document.name || '[No Name provided]'} - - - + + + {document.name || '[No Name provided]'} + + {document.type ? getTypeText(document.type) : 'N/A'} + + {document.uploadDate - ? `Uploaded ${document.uploadDate.toLocaleDateString()}` - : 'Unknown Upload Date'} + ? `Uploaded: ${document.uploadDate.toLocaleDateString()}` + : 'Upload Date Unset'} {document.endDate - ? `Expires ${document.endDate.toLocaleDateString()}` - : 'No Expiration'} + ? `Expires: ${document.endDate.toLocaleDateString()}` + : 'Expiration Unset'} + + + + + {truncateText(document.description, 140)} - - - {truncateText(document.description, 140)} - - From cd26b3df8c835770fd6a8f2c4114e99174567e12 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 02:00:08 -0700 Subject: [PATCH 17/36] refactor(utils, documents): Add and integrate getTypeText util for user-friendly document types --- src/components/Documents/DocumentCard.jsx | 15 +----------- src/components/Documents/DocumentsDesktop.jsx | 6 ++++- src/utils/index.js | 4 +++- src/utils/text/documentTypes.js | 23 +++++++++++++++++++ 4 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 src/utils/text/documentTypes.js diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index f70e404b4..7ba656686 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -16,7 +16,7 @@ import Typography from '@mui/material/Typography'; import { useTheme } from '@mui/material/styles'; // Utility Imports -import { truncateText } from '@utils'; +import { truncateText, getTypeText } from '@utils'; /** * @typedef {object} Document @@ -65,19 +65,6 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { handleClose(); }; - const getTypeText = (type) => { - switch (type) { - case 'driversLicense': - return "Driver's License"; - case 'passport': - return 'Passport'; - case 'bankStatement': - return 'Bank Statement'; - default: - return 'Other'; - } - }; - const rowStyling = { display: 'flex', columnGap: '10px' diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index 8b7619ba6..16cb72e02 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -9,6 +9,10 @@ import { GridToolbarDensitySelector, GridToolbarFilterButton } from '@mui/x-data-grid'; + +// Util Imports +import { getTypeText } from '@utils'; + // Theme Imports import theme from '../../theme'; @@ -153,7 +157,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { rows={documents.map((document) => ({ id: document.id, Name: document.name, - Type: document.type, + Type: getTypeText(document.type), Description: document.description, 'Upload Date': document?.uploadDate.toLocaleDateString(), 'Expiration Date': document?.endDate?.toLocaleDateString(), diff --git a/src/utils/index.js b/src/utils/index.js index f93dd4b4e..40188e446 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -11,6 +11,7 @@ import formattedDate from './barcode/barcode-date-parser'; import createPASSContainer, { generateACL } from './pod-management/pod-helper'; import saveToClipboard from './text/saveToClipboard'; import truncateText from './text/truncateText'; +import getTypeText from './text/documentTypes'; export { getDriversLicenseData, @@ -18,7 +19,8 @@ export { createPASSContainer, generateACL, saveToClipboard, - truncateText + truncateText, + getTypeText }; export * from './network/session-core'; diff --git a/src/utils/text/documentTypes.js b/src/utils/text/documentTypes.js new file mode 100644 index 000000000..78e172fcd --- /dev/null +++ b/src/utils/text/documentTypes.js @@ -0,0 +1,23 @@ +/** + * Function that gets user-friendly text versions of documentTypes + * + * @memberof utils + * @function getTypeText + * @param {string} type - Type to be converted + * @returns {string} Returns the converted type text + */ + +const getTypeText = (type) => { + switch (type) { + case 'driversLicense': + return "Driver's License"; + case 'passport': + return 'Passport'; + case 'bankStatement': + return 'Bank Statement'; + default: + return 'Other'; + } +}; + +export default getTypeText; From c5a31a17fcc099ce267d60e2ee27d5dfc220f7b9 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 02:02:18 -0700 Subject: [PATCH 18/36] [chore, style](DocumentsMobile): Rename 'Name' column to 'Document' in light of left-aligned overview --- src/components/Documents/DocumentsMobile.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 9d6640af8..85cacadbe 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -55,7 +55,7 @@ const DocumentsMobile = ({ documents, handlers }) => { }} > - Name + Document Date: Tue, 27 Aug 2024 15:03:56 -0700 Subject: [PATCH 19/36] chore(Documents): Add more thorough commenting --- src/components/Documents/DocumentCard.jsx | 63 ++++++---- src/components/Documents/DocumentTable.jsx | 60 +++++---- src/components/Documents/DocumentsDesktop.jsx | 115 ++++++++---------- src/components/Documents/DocumentsMobile.jsx | 50 +++++--- 4 files changed, 160 insertions(+), 128 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 7ba656686..f2812a0e1 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -1,5 +1,6 @@ // React Imports -import React, { useState } from 'react'; +import React, { useState } from 'react'; // Used to keep track of card menu state + // Material UI Imports import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -29,42 +30,46 @@ import { truncateText, getTypeText } from '@utils'; */ /** - * DocumentCard - Component that contains a document + * DocumentCard - A component that displays a single document's information + * and provides actions (preview, share, delete) through a context menu. * * @memberof Documents * @name DocumentCard - * @param {object} Props - Component props for Document Preview - * @param {Document} Props.document - The document - * @param {Function} Props.onPreview - The document preview event - * @param {Function} Props.onShare - The document share event - * @param {Function} Props.onDelete - The document delete event - * @returns {React.JSX.Element} React component for DocumentCard + * @param {object} props - Component props + * @param {Document} props.document - The document object to be displayed + * @param {Function} props.onPreview - Function to handle the "Preview" action + * @param {Function} props.onShare - Function to handle the "Share" action + * @param {Function} props.onDelete - Function to handle the "Delete" action + * @returns {React.JSX.Element} The DocumentCard component */ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { - // primary columns - // name, type, uploaddate, expiration, actions - // expandable information - // description - const theme = useTheme(); + // State to manage the anchor element for the context menu const [anchorEl, setAnchorEl] = useState(null); + // State to control the visibility of the context menu for a specific document const [openMenu, setOpenMenu] = useState(null); + // Handles the click on the "MoreVertIcon" (three dots) to open the context menu const handleClick = (event, clickedDocument) => { setAnchorEl(event.currentTarget); setOpenMenu(clickedDocument.id); }; + + // Closes the context menu const handleClose = () => { setAnchorEl(null); setOpenMenu(null); }; + // Handles the click on a context menu item, executing the corresponding action const handleMenuItemClick = (action, clickedDocument) => () => { action(clickedDocument); handleClose(); }; + // ... (Styling definitions below) + const rowStyling = { display: 'flex', columnGap: '10px' @@ -109,6 +114,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { alignItems: 'flex-end' }; + // overall card styling, keeping 'Document' and 'Action' columns on opposite sides const cardStyling = { ...rowStyling, background: theme.palette.background.tint, @@ -128,15 +134,15 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { return ( - + + {/* Card container for the document */} + {/* Card content with flexbox layout */} + {/* Column for document data */} + {/* Section for document name and type */} + {/* Document name (or placeholder if not provided) */} { > {document.name || '[No Name provided]'} + {/* Document type (or 'Unknown Type' if not provided), formatted using `getTypeText` */} - {document.type ? getTypeText(document.type) : 'N/A'} + {document.type ? getTypeText(document.type) : 'Unknown Type'} + {/* Section for upload and expiration dates */} + {/* Upload date (or placeholder if not set) */} {document.uploadDate ? `Uploaded: ${document.uploadDate.toLocaleDateString()}` : 'Upload Date Unset'} + {/* Expiration date (or placeholder if not set) */} {document.endDate ? `Expires: ${document.endDate.toLocaleDateString()}` - : 'Expiration Unset'} + : 'No expiration given'} + {/* Section for document description (truncated if too long) */} - {truncateText(document.description, 140)} + {document.description + ? truncateText(document.description, 140) + : 'No description given.'} + {/* Column for action icons */} + {/* "MoreVertIcon" (three dots) to open the context menu */} { + {/* Context menu with "Preview", "Share", and "Delete" options */} { 'aria-labelledby': 'actions-icon-button' }} > + {/* "Preview" menu option */} { > Preview + {/* "Share" menu option */} { > Share + {/* "Delete" menu option */} void} Props.handleAclPermissionsModal - Function for setting up the - * correct version of the SetAclPermissions Modal, and opening it. - * @param {(document: object) => void} Props.handleSelectDeleteDoc - method - * to delete document + * @param {object} props - Component props + * @param {(modalType: string, docName: string, docType: string) => void} props.handleAclPermissionsModal - Function to open the ACL permissions modal + * @param {(document: object) => void} props.handleSelectDeleteDoc - Function to handle document deletion * @returns {React.JSX.Element} The DocumentTable component */ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => { + // Get the current Solid session using the `useSession` hook const { session } = useSession(); + // Retrieve the document list and loading state from the `DocumentListContext` const { documentListObject, loadingDocuments } = useContext(DocumentListContext); + // Determine if the screen size is mobile using Material-UI's `useMediaQuery` const isMobile = useMediaQuery(theme.breakpoints.down('sm')); /** @@ -48,10 +61,6 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => * @param {string} urlToOpen - The URL of the document to be opened. * @throws {Error} Throws an error if there is an issue fetching the document blob. * @returns {Promise} A promise that resolves after the document is opened. - * @example - * // Example usage: - * const documentUrl = 'https://example.com/document.pdf'; - * await handleShowDocumentLocal(documentUrl); */ const handleShowDocumentLocal = async (urlToOpen) => { /** @@ -64,35 +73,40 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => * @returns {Promise} A promise that resolves with the Blob of the document. * @throws {Error} Throws an error if there is an issue fetching the document blob. */ - const urlFileBlob = await getBlobFromSolid(session, urlToOpen); - // Opens a new window with the Blob URL displaying the document. + // Open a new window to display the document using the blob URL window.open(urlFileBlob); }; - // Updates type value to use DOC_TYPES for formatting the string + // Maps raw document types to user-friendly display names using `DOC_TYPES` const mappingType = (type) => DOC_TYPES[type] || type; - // Map types for each document in the array + // Processes the document list, adding unique IDs and mapping types const documents = useMemo( () => documentListObject?.docList.map((document) => ({ - id: uuidv4(), // Generate a UUID (a unique ID) - type: mappingType(document.type), - ...document + id: uuidv4(), // Generate a unique ID for each document + type: mappingType(document.type), // Map the document type + ...document // Include all other document properties })), - [documentListObject?.docList] + [documentListObject?.docList] // Re-compute only when the document list changes ); + // An object containing event handler functions for document actions const handlers = { onShare: handleAclPermissionsModal, onDelete: handleSelectDeleteDoc, onPreview: handleShowDocumentLocal }; + // Render a notification if there are no documents if (!documents?.length) return ; + + // Render a loading animation while documents are being fetched if (loadingDocuments) return ; + + // Render the document list based on screen size (mobile or desktop) return ( ( - - + {/* Add a filter button to the toolbar */} + {/* Add a density selector to the toolbar */} ); /** * @typedef {object} Document - * @property {string} id - The id of the document - * @property {string} name - The provided name of the document - * @property {string} type - The provided type of the document - * @property {string} description - The provided description of the document - * @property {string} uploadDate- The upload date of the document - * @property {string} endDate - The expiration date of the document - * @property {string} fileUrl - The file URL of the document + * @property {string} id - The unique identifier of the document + * @property {string} name - The name of the document + * @property {string} type - The type of the document + * @property {string} description - A brief description of the document + * @property {string} uploadDate- The date when the document was uploaded + * @property {string} endDate - The expiration date of the document (if applicable) + * @property {string} fileUrl - The URL where the document file is located */ /** @@ -43,64 +48,38 @@ const CustomToolbar = () => ( */ /** - * DocumentsDesktop - Component for displaying documents in a DataGrid + * DocumentsDesktop - A component that displays a list of documents in a tabular + * format (DataGrid) suitable for desktop screens. It provides actions like + * preview, share, and delete for each document. * * @memberof Documents * @name DocumentsDesktop - * @param {object} Props - The props for DocumentsDesktop - * @param {Document[]} Props.documents - The list of documents to display - * @param {Handlers} Props.handlers - Object containing event handler functions. + * @param {object} props - Component props + * @param {Document[]} props.documents - An array of document objects to be displayed + * @param {Handlers} props.handlers - An object containing handler functions for document actions * @returns {React.JSX.Element} The DocumentsDesktop component */ const DocumentsDesktop = ({ documents, handlers }) => { + // Define the columns for the DataGrid const columnTitlesArray = [ - { - field: 'Name', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - field: 'Type', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - field: 'Description', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - field: 'Upload Date', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, - { - field: 'Expiration Date', - minWidth: 120, - flex: 1, - headerAlign: 'center', - align: 'center' - }, + { field: 'Name', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, + { field: 'Type', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, + { field: 'Description', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, + { field: 'Upload Date', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, + { field: 'Expiration Date', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, { field: 'Preview', minWidth: 100, flex: 1, headerAlign: 'center', align: 'center', - sortable: false, - filterable: false, + sortable: false, // Disable sorting for this column + filterable: false, // Disable filtering for this column + // Render a "Preview" button in each row's cell renderCell: (data) => { const [id, fileUrl] = data.value; return ( - } onClick={() => handlers.onPreview(fileUrl)} @@ -118,12 +97,12 @@ const DocumentsDesktop = ({ documents, handlers }) => { sortable: false, filterable: false, renderCell: (data) => { - const [id, type] = data.value; + const [id, name, type] = data.value; return ( } - onClick={() => handlers.onShare('document', id, type)} + onClick={() => handlers.onShare('document', name, type)} label="Share" /> ); @@ -137,6 +116,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center', sortable: false, filterable: false, + // Render a "Delete" button in each row's cell renderCell: (data) => { const [document] = data.value; return ( @@ -150,31 +130,34 @@ const DocumentsDesktop = ({ documents, handlers }) => { } } ]; - // Render if documents + + // Render the DataGrid if there are documents to display return ( ({ + // Map document data to row data format expected by DataGrid id: document.id, Name: document.name, Type: getTypeText(document.type), Description: document.description, 'Upload Date': document?.uploadDate.toLocaleDateString(), 'Expiration Date': document?.endDate?.toLocaleDateString(), - Preview: [document.id, document.fileUrl], - Share: [document.id, document.name, document.type], - Delete: [document] + Preview: [document.id, document.fileUrl], // Pass data needed for "Preview" action + Share: [document.id, document.name, document.type], // Pass data needed for "Share" action + Delete: [document] // Pass the document object for "Delete" action }))} - pageSizeOptions={[10]} + pageSizeOptions={[10]} // Allow only 10 rows per page initialState={{ pagination: { - paginationModel: { pageSize: 10, page: 0 } + paginationModel: { pageSize: 10, page: 0 } // Start with 10 rows on the first page } }} slots={{ - toolbar: CustomToolbar + toolbar: CustomToolbar // Use the custom toolbar }} sx={{ + // Apply styling to the DataGrid using Material-UI's `sx` prop '.MuiDataGrid-columnHeader': { background: theme.palette.primary.light, color: 'white' @@ -183,8 +166,8 @@ const DocumentsDesktop = ({ documents, handlers }) => { display: 'none' } }} - disableColumnMenu - disableRowSelectionOnClick + disableColumnMenu // Disable the default column menu + disableRowSelectionOnClick // Prevent row selection on click /> ); }; diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 85cacadbe..7576691dd 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -1,9 +1,14 @@ +// React Import import React from 'react'; + +// Material UI Imports import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; +// Material UI Styling import { useTheme } from '@mui/material/styles'; +// Component Import import DocumentCard from './DocumentCard'; /** @@ -24,41 +29,50 @@ import DocumentCard from './DocumentCard'; */ /** - * DocumentsMobile - Component for displaying documents in a DataGrid + * DocumentsMobile - A component designed to display a list of documents + * in a mobile-friendly layout using `DocumentCard` components. * * @memberof Documents - * @name DocumentsDesktop - * @param {object} Props - The props for DocumentsMobile - * @param {Document[]} Props.documents - The list of documents to display - * @param {Handlers} Props.handlers - Object containing event handler functions. + * @name DocumentsMobile + * @param {object} props - Component props + * @param {Document[]} props.documents - An array of document objects to be displayed + * @param {Handlers} props.handlers - An object containing handler functions for document actions (preview, share, delete) * @returns {React.JSX.Element} The DocumentsMobile component */ const DocumentsMobile = ({ documents, handlers }) => { const theme = useTheme(); - // parent box element + // Styling for the parent container of the document list const tableHeaderStyling = { display: 'flex', - justifyContent: 'space-between' + justifyContent: 'space-between' // Arrange child elements with space between them }; return ( + {/* Header section with "Document" and "Actions" labels */} - Document + {/* Apply the tableHeaderStyling */} + Document + + { + + {/* Map through the documents array and render a `DocumentCard` for each */} {documents.map((document) => ( handlers.onShare('document', document.name, document.type)} - onDelete={() => handlers.onDelete(document)} - onPreview={() => handlers.onPreview(document.fileUrl)} + key={document.id} // Unique key for each card + document={document} // Pass the document data to the card + onShare={() => handlers.onShare('document', document.name, document.type)} // Share handler + onDelete={() => handlers.onDelete(document)} // Delete handler + onPreview={() => handlers.onPreview(document.fileUrl)} // Preview handler /> ))} From fb795279219955394a4b0696e292a234e40d1e72 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 15:07:31 -0700 Subject: [PATCH 20/36] chore(Documents): Add id to Document typedef --- src/components/Documents/DocumentsMobile.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 7576691dd..64d15bdae 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -13,6 +13,7 @@ import DocumentCard from './DocumentCard'; /** * @typedef {object} Document + * @property {string} id - The unique id of the document * @property {string} name - The given name of the document * @property {string} type - The given type of the document * @property {string} description - The given description of the document From 13b3dbc8742daba439111d9adf6c48866b0fe455 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 15:09:05 -0700 Subject: [PATCH 21/36] chore(Documents): Add id to Document typedef in other references --- src/components/Documents/DocumentCard.jsx | 1 + src/components/Documents/DocumentsDesktop.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index f2812a0e1..df8a2535f 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -21,6 +21,7 @@ import { truncateText, getTypeText } from '@utils'; /** * @typedef {object} Document + * @property {string} id - The unique id of the document * @property {string} name - The given name of the document * @property {string} type - The given type of the document * @property {string} description - The given description of the document diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index 3a0cc629d..1c9ee5684 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -31,7 +31,7 @@ const CustomToolbar = () => ( /** * @typedef {object} Document - * @property {string} id - The unique identifier of the document + * @property {string} id - The unique id of the document * @property {string} name - The name of the document * @property {string} type - The type of the document * @property {string} description - A brief description of the document From eaadf576147e97ad4ba23b0b1d79f12ae94f035c Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 15:22:57 -0700 Subject: [PATCH 22/36] [chore,style](DocumentsDesktop): Added default cases for unprovided expiration or description. --- src/components/Documents/DocumentsDesktop.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index 1c9ee5684..817d13971 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -140,9 +140,9 @@ const DocumentsDesktop = ({ documents, handlers }) => { id: document.id, Name: document.name, Type: getTypeText(document.type), - Description: document.description, + Description: document.description || '[Not Provided]', 'Upload Date': document?.uploadDate.toLocaleDateString(), - 'Expiration Date': document?.endDate?.toLocaleDateString(), + 'Expiration Date': document?.endDate?.toLocaleDateString() || '[Not Provided]', Preview: [document.id, document.fileUrl], // Pass data needed for "Preview" action Share: [document.id, document.name, document.type], // Pass data needed for "Share" action Delete: [document] // Pass the document object for "Delete" action From cb02e1989d204e5645e48e6f31bab8105e3d3b4f Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 18:36:01 -0700 Subject: [PATCH 23/36] chore(Documents): Make comments more consistent with established convention --- src/components/Documents/DocumentCard.jsx | 24 ----------- src/components/Documents/DocumentTable.jsx | 32 +++----------- src/components/Documents/DocumentsDesktop.jsx | 43 +++++++------------ src/components/Documents/DocumentsMobile.jsx | 30 +++++-------- 4 files changed, 33 insertions(+), 96 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index df8a2535f..29bbf0a25 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -1,6 +1,5 @@ // React Imports import React, { useState } from 'react'; // Used to keep track of card menu state - // Material UI Imports import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -15,7 +14,6 @@ import MenuItem from '@mui/material/MenuItem'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import Typography from '@mui/material/Typography'; import { useTheme } from '@mui/material/styles'; - // Utility Imports import { truncateText, getTypeText } from '@utils'; @@ -51,7 +49,6 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { // State to control the visibility of the context menu for a specific document const [openMenu, setOpenMenu] = useState(null); - // Handles the click on the "MoreVertIcon" (three dots) to open the context menu const handleClick = (event, clickedDocument) => { setAnchorEl(event.currentTarget); setOpenMenu(clickedDocument.id); @@ -81,14 +78,12 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { flexDirection: 'column' }; - // styling for data contained in each section const dataStyling = { ...rowStyling, fontSize: '.8rem', justifyContent: 'flex-start' }; - // styling for each data section in the document data column const dataSectionStyling = { ...columnStyling, '& div': { @@ -96,7 +91,6 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { } }; - // styling for the document data column const dataColumnStyling = { ...columnStyling, flex: '1 1 90%', @@ -106,7 +100,6 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { } }; - // styling for the document actions column const actionsColumnStyling = { ...columnStyling, flex: '0 1 10%', @@ -115,7 +108,6 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { alignItems: 'flex-end' }; - // overall card styling, keeping 'Document' and 'Action' columns on opposite sides const cardStyling = { ...rowStyling, background: theme.palette.background.tint, @@ -136,14 +128,9 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { return ( - {/* Card container for the document */} - {/* Card content with flexbox layout */} - {/* Column for document data */} - {/* Section for document name and type */} - {/* Document name (or placeholder if not provided) */} { > {document.name || '[No Name provided]'} - {/* Document type (or 'Unknown Type' if not provided), formatted using `getTypeText` */} { - {/* Section for upload and expiration dates */} - {/* Upload date (or placeholder if not set) */} {document.uploadDate ? `Uploaded: ${document.uploadDate.toLocaleDateString()}` : 'Upload Date Unset'} - {/* Expiration date (or placeholder if not set) */} {document.endDate ? `Expires: ${document.endDate.toLocaleDateString()}` @@ -178,7 +161,6 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { - {/* Section for document description (truncated if too long) */} {document.description ? truncateText(document.description, 140) @@ -187,8 +169,6 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { - {/* Column for action icons */} - {/* "MoreVertIcon" (three dots) to open the context menu */} { - {/* Context menu with "Preview", "Share", and "Delete" options */} { 'aria-labelledby': 'actions-icon-button' }} > - {/* "Preview" menu option */} { > Preview - {/* "Share" menu option */} { > Share - {/* "Delete" menu option */} void} props.handleAclPermissionsModal - Function to open the ACL permissions modal * @param {(document: object) => void} props.handleSelectDeleteDoc - Function to handle document deletion - * @returns {React.JSX.Element} The DocumentTable component + * @returns {React.JSX.Element} - The DocumentTable component */ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => { - // Get the current Solid session using the `useSession` hook const { session } = useSession(); - // Retrieve the document list and loading state from the `DocumentListContext` const { documentListObject, loadingDocuments } = useContext(DocumentListContext); - // Determine if the screen size is mobile using Material-UI's `useMediaQuery` const isMobile = useMediaQuery(theme.breakpoints.down('sm')); /** @@ -86,33 +71,28 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => const documents = useMemo( () => documentListObject?.docList.map((document) => ({ - id: uuidv4(), // Generate a unique ID for each document - type: mappingType(document.type), // Map the document type - ...document // Include all other document properties + id: uuidv4(), + type: mappingType(document.type), + ...document })), - [documentListObject?.docList] // Re-compute only when the document list changes + [documentListObject?.docList] ); - // An object containing event handler functions for document actions const handlers = { onShare: handleAclPermissionsModal, onDelete: handleSelectDeleteDoc, onPreview: handleShowDocumentLocal }; - // Render a notification if there are no documents if (!documents?.length) return ; - - // Render a loading animation while documents are being fetched if (loadingDocuments) return ; - // Render the document list based on screen size (mobile or desktop) return ( {isMobile ? ( diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index 817d13971..cc2fb881c 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -1,12 +1,9 @@ // React Import import React from 'react'; - -// Material UI Icon Imports +// Material UI Imports import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import ShareIcon from '@mui/icons-material/Share'; import FileOpenIcon from '@mui/icons-material/FileOpen'; - -// Material UI DataGrid Imports import { DataGrid, GridActionsCellItem, @@ -14,18 +11,16 @@ import { GridToolbarDensitySelector, GridToolbarFilterButton } from '@mui/x-data-grid'; - // Util Imports -import { getTypeText } from '@utils'; // Import utility function for getting formatted document type text - +import { getTypeText } from '@utils'; // Theme Imports import theme from '../../theme'; // Custom toolbar for the DataGrid const CustomToolbar = () => ( - {/* Add a filter button to the toolbar */} - {/* Add a density selector to the toolbar */} + + ); @@ -60,7 +55,6 @@ const CustomToolbar = () => ( * @returns {React.JSX.Element} The DocumentsDesktop component */ const DocumentsDesktop = ({ documents, handlers }) => { - // Define the columns for the DataGrid const columnTitlesArray = [ { field: 'Name', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, { field: 'Type', minWidth: 120, flex: 1, headerAlign: 'center', align: 'center' }, @@ -73,13 +67,12 @@ const DocumentsDesktop = ({ documents, handlers }) => { flex: 1, headerAlign: 'center', align: 'center', - sortable: false, // Disable sorting for this column - filterable: false, // Disable filtering for this column - // Render a "Preview" button in each row's cell + sortable: false, + filterable: false, renderCell: (data) => { const [id, fileUrl] = data.value; return ( - } onClick={() => handlers.onPreview(fileUrl)} @@ -116,7 +109,6 @@ const DocumentsDesktop = ({ documents, handlers }) => { align: 'center', sortable: false, filterable: false, - // Render a "Delete" button in each row's cell renderCell: (data) => { const [document] = data.value; return ( @@ -131,33 +123,30 @@ const DocumentsDesktop = ({ documents, handlers }) => { } ]; - // Render the DataGrid if there are documents to display return ( ({ - // Map document data to row data format expected by DataGrid id: document.id, Name: document.name, Type: getTypeText(document.type), Description: document.description || '[Not Provided]', 'Upload Date': document?.uploadDate.toLocaleDateString(), 'Expiration Date': document?.endDate?.toLocaleDateString() || '[Not Provided]', - Preview: [document.id, document.fileUrl], // Pass data needed for "Preview" action - Share: [document.id, document.name, document.type], // Pass data needed for "Share" action - Delete: [document] // Pass the document object for "Delete" action + Preview: [document.id, document.fileUrl], + Share: [document.id, document.name, document.type], + Delete: [document] }))} - pageSizeOptions={[10]} // Allow only 10 rows per page + pageSizeOptions={[10]} initialState={{ pagination: { - paginationModel: { pageSize: 10, page: 0 } // Start with 10 rows on the first page + paginationModel: { pageSize: 10, page: 0 } } }} slots={{ - toolbar: CustomToolbar // Use the custom toolbar + toolbar: CustomToolbar }} sx={{ - // Apply styling to the DataGrid using Material-UI's `sx` prop '.MuiDataGrid-columnHeader': { background: theme.palette.primary.light, color: 'white' @@ -166,8 +155,8 @@ const DocumentsDesktop = ({ documents, handlers }) => { display: 'none' } }} - disableColumnMenu // Disable the default column menu - disableRowSelectionOnClick // Prevent row selection on click + disableColumnMenu + disableRowSelectionOnClick /> ); }; diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 64d15bdae..7d0e5b186 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -1,13 +1,9 @@ // React Import import React from 'react'; - // Material UI Imports import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; - -// Material UI Styling import { useTheme } from '@mui/material/styles'; - // Component Import import DocumentCard from './DocumentCard'; @@ -43,37 +39,34 @@ import DocumentCard from './DocumentCard'; const DocumentsMobile = ({ documents, handlers }) => { const theme = useTheme(); - // Styling for the parent container of the document list const tableHeaderStyling = { display: 'flex', - justifyContent: 'space-between' // Arrange child elements with space between them + justifyContent: 'space-between' }; return ( - {/* Header section with "Document" and "Actions" labels */} - {/* Apply the tableHeaderStyling */} Document { - {/* Map through the documents array and render a `DocumentCard` for each */} {documents.map((document) => ( handlers.onShare('document', document.name, document.type)} // Share handler - onDelete={() => handlers.onDelete(document)} // Delete handler - onPreview={() => handlers.onPreview(document.fileUrl)} // Preview handler + key={document.id} + document={document} + onShare={() => handlers.onShare('document', document.name, document.type)} + onDelete={() => handlers.onDelete(document)} + onPreview={() => handlers.onPreview(document.fileUrl)} /> ))} From 893cbccc2ffd2124c2d0e9ddc08f38d8de93b0a4 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 21:33:04 -0700 Subject: [PATCH 24/36] style(DocumentsMobile): Style more consistently with contacts while still retaining distinctive document data formatting --- src/components/Documents/DocumentCard.jsx | 37 +++++++++++--------- src/components/Documents/DocumentsMobile.jsx | 1 - 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 29bbf0a25..f216717b9 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -44,11 +44,10 @@ import { truncateText, getTypeText } from '@utils'; const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const theme = useTheme(); - // State to manage the anchor element for the context menu const [anchorEl, setAnchorEl] = useState(null); - // State to control the visibility of the context menu for a specific document const [openMenu, setOpenMenu] = useState(null); + // Opens the context menu const handleClick = (event, clickedDocument) => { setAnchorEl(event.currentTarget); setOpenMenu(clickedDocument.id); @@ -80,7 +79,8 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const dataStyling = { ...rowStyling, - fontSize: '.8rem', + color: 'text.secondary', + fontSize: '.875rem', justifyContent: 'flex-start' }; @@ -94,7 +94,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const dataColumnStyling = { ...columnStyling, flex: '1 1 90%', - rowGap: '10px', + rowGap: '5px', '& div': { ...dataSectionStyling } @@ -110,9 +110,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const cardStyling = { ...rowStyling, - background: theme.palette.background.tint, - justifyContent: 'space-between', - gap: '10px' + justifyContent: 'space-between' }; // icon styles @@ -132,7 +130,10 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { { {document.name || '[No Name provided]'} { : 'No expiration given'} - - - {document.description - ? truncateText(document.description, 140) - : 'No description given.'} - - + {document.description && ( + + + {truncateText(document.description, 140)} + + + )} { Date: Tue, 27 Aug 2024 21:41:56 -0700 Subject: [PATCH 25/36] style(DocumentsMobile): Small tweak to actions button. Move slightly to the right to match Contacts. --- src/components/Documents/DocumentCard.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index f216717b9..ca43456c7 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -105,7 +105,8 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { flex: '0 1 10%', maxWidth: '54px', justifyContent: 'center', - alignItems: 'flex-end' + alignItems: 'flex-end', + marginRight: '-10px' }; const cardStyling = { From 059c856268f96c4d97255b81fe28f84b15c349af Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Tue, 27 Aug 2024 21:47:30 -0700 Subject: [PATCH 26/36] style(DocumentsMobile): Small tweak to header. Reintroduced margin to match Contacts. --- src/components/Documents/DocumentsMobile.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 2812f71e0..f43e317be 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -48,7 +48,8 @@ const DocumentsMobile = ({ documents, handlers }) => { Date: Tue, 27 Aug 2024 21:51:04 -0700 Subject: [PATCH 27/36] style(DocumentsMobile): Small tweak to description. Add slight margin to be more distinct from data. --- src/components/Documents/DocumentCard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index ca43456c7..c3ab42110 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -165,7 +165,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { {document.description && ( From e4bcf2f6faec9e9bd52a884787881008e432b7b6 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Thu, 29 Aug 2024 22:51:10 -0700 Subject: [PATCH 28/36] chore(Documents): Remove inline comments on imports. Move Desktop styling into constants like in other components. --- src/components/Documents/DocumentCard.jsx | 4 ++-- src/components/Documents/DocumentTable.jsx | 4 ++-- src/components/Documents/DocumentsDesktop.jsx | 20 ++++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index c3ab42110..5bae6387b 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -1,5 +1,5 @@ // React Imports -import React, { useState } from 'react'; // Used to keep track of card menu state +import React, { useState } from 'react'; // Material UI Imports import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -139,7 +139,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { component="div" noWrap > - {document.name || '[No Name provided]'} + {truncateText(document.name) || '[No Name]'} ( @@ -146,15 +156,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { slots={{ toolbar: CustomToolbar }} - sx={{ - '.MuiDataGrid-columnHeader': { - background: theme.palette.primary.light, - color: 'white' - }, - '.MuiDataGrid-columnSeparator': { - display: 'none' - } - }} + sx={MUIDataGridStyling} disableColumnMenu disableRowSelectionOnClick /> From 9f53b0d3353a0ab0be28bfe11ddbbce81222e141 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Fri, 30 Aug 2024 23:59:40 -0700 Subject: [PATCH 29/36] [chore,style](Documents): Match header-card margin with card-card margin. Extract sx data to constants (where apt). --- src/components/Documents/DocumentCard.jsx | 8 ++++++-- src/components/Documents/DocumentsMobile.jsx | 20 +++++++++---------- .../Documents/DocumentTable.test.jsx | 17 ++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 5bae6387b..e96a249a7 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -110,6 +110,10 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { }; const cardStyling = { + marginBottom: '15px' + }; + + const cardContentStyling = { ...rowStyling, justifyContent: 'space-between' }; @@ -126,8 +130,8 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { return ( - - + + { justifyContent: 'space-between' }; + const tableStyling = { + my: '15px', + p: '16px', + background: theme.palette.primary.main, + color: '#fff', + borderRadius: '8px', + position: 'relative' + }; + return ( - + { + const randomSpaces = Array.from({ length: Math.floor(Math.random() * 5) }, () => + Math.floor(Math.random() * this.length) + ); + return this.split('').reduce((acc, char, i) => { + if (randomSpaces.includes(i)) { + return `${acc} ${char}`; + } + return acc + char; + }, ''); +}; + const mockedDocumentContext = { documentListObject: { docList: [ { + id: uuidv4(), name: 'test.pdf', type: 'Other', description: 'test description', @@ -18,6 +34,7 @@ const mockedDocumentContext = { fileUrl: 'http://localhost:3000/pod/PASS/Documents/test.pdf' }, { + id: uuidv4(), name: 'test2.pdf', type: 'Passport', description: 'test description 2', From 1dc61aa7f8f14ce9befd6c7402e3779e147cd758 Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Sat, 31 Aug 2024 05:56:46 -0700 Subject: [PATCH 30/36] test(Documents): Implement all tests for Documents. Ensure all tests pass. --- src/components/Documents/DocumentCard.jsx | 1 + src/components/Documents/DocumentTable.jsx | 15 +- src/components/Documents/DocumentsDesktop.jsx | 5 + src/components/Documents/DocumentsMobile.jsx | 2 +- src/components/Documents/index.js | 10 +- .../Notification/EmptyListNotification.jsx | 1 + .../Notification/LoadingAnimation.jsx | 1 + src/pages/Profile.jsx | 2 +- .../Documents/DocumentCard.test.jsx | 91 ++++++++++ .../Documents/DocumentTable.test.jsx | 161 ++++++++++-------- .../Documents/DocumentsDesktop.test.jsx | 146 ++++++++++++++++ .../Documents/DocumentsMobile.test.jsx | 138 +++++++++++++++ 12 files changed, 499 insertions(+), 74 deletions(-) create mode 100644 test/components/Documents/DocumentCard.test.jsx create mode 100644 test/components/Documents/DocumentsDesktop.test.jsx create mode 100644 test/components/Documents/DocumentsMobile.test.jsx diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index e96a249a7..4ee025d7a 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -184,6 +184,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { aria-controls={openMenu === document.id ? 'actions-menu' : undefined} aria-haspopup="true" aria-expanded={openMenu === document.id ? 'true' : undefined} + aria-label="open-actions-menu" onClick={(event) => handleClick(event, document)} > diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx index ce430f5c0..ddeaefbdf 100644 --- a/src/components/Documents/DocumentTable.jsx +++ b/src/components/Documents/DocumentTable.jsx @@ -84,8 +84,10 @@ const DocumentTable = ({ handleAclPermissionsModal, handleSelectDeleteDoc }) => onPreview: handleShowDocumentLocal }; - if (!documents?.length) return ; - if (loadingDocuments) return ; + if (!documents?.length) + return ; + if (loadingDocuments) + return ; return ( width: '95vw', height: '100%' }} + data-testid="document-table" > {isMobile ? ( - + ) : ( - + )} ); diff --git a/src/components/Documents/DocumentsDesktop.jsx b/src/components/Documents/DocumentsDesktop.jsx index 7badd20cd..ded8e357a 100644 --- a/src/components/Documents/DocumentsDesktop.jsx +++ b/src/components/Documents/DocumentsDesktop.jsx @@ -87,6 +87,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { icon={} onClick={() => handlers.onPreview(fileUrl)} label="Preview" + data-testid={`preview-button-${id}`} /> ); } @@ -107,6 +108,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { icon={} onClick={() => handlers.onShare('document', name, type)} label="Share" + data-testid={`share-button-${id}`} /> ); } @@ -127,6 +129,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { icon={} onClick={() => handlers.onDelete(document)} label="Delete" + data-testid={`delete-button-${document.id}`} /> ); } @@ -135,6 +138,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { return ( ({ id: document.id, @@ -159,6 +163,7 @@ const DocumentsDesktop = ({ documents, handlers }) => { sx={MUIDataGridStyling} disableColumnMenu disableRowSelectionOnClick + data-testid="documents-desktop" /> ); }; diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 26e29d9d7..c9f7ff2a6 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -54,7 +54,7 @@ const DocumentsMobile = ({ documents, handlers }) => { }; return ( - + { diff --git a/src/components/Notification/LoadingAnimation.jsx b/src/components/Notification/LoadingAnimation.jsx index b14108be3..430750d7e 100644 --- a/src/components/Notification/LoadingAnimation.jsx +++ b/src/components/Notification/LoadingAnimation.jsx @@ -32,6 +32,7 @@ const LoadingAnimation = ({ loadingItem, children }) => ( textAlign: 'center', padding: '20px' }} + data-testid="loading-animation" > diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index 2527444cd..cb45b1a57 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -15,7 +15,7 @@ import { useTheme } from '@mui/material/styles'; import { DocumentListContext } from '@contexts'; // Component Imports import { ConfirmationModal, UploadDocumentModal, SetAclPermissionsModal } from '@components/Modals'; -import DocumentTable from '@components/Documents'; +import { DocumentTable } from '@components/Documents'; import { ProfileComponent } from '@components/Profile'; import { LoadingAnimation } from '@components/Notification'; // Util Imports diff --git a/test/components/Documents/DocumentCard.test.jsx b/test/components/Documents/DocumentCard.test.jsx new file mode 100644 index 000000000..1783db7f2 --- /dev/null +++ b/test/components/Documents/DocumentCard.test.jsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { cleanup, render, screen, fireEvent } from '@testing-library/react'; +import { afterEach, describe, it, vi, expect } from 'vitest'; +import { DocumentCard } from '@components/Documents'; +import { SessionContext } from '@contexts'; +import { BrowserRouter } from 'react-router-dom'; + +// Clear created DOM after each test +afterEach(() => { + cleanup(); +}); + +const MockCardComponent = ({ + document, + previewDocument = vi.fn(), + shareDocument = vi.fn(), + deleteDocument = vi.fn(), + isLoggedIn = true +}) => { + const sessionObj = { + session: { info: { isLoggedIn } } + }; + + return render( + + + previewDocument(document.fileUrl)} + onShare={() => shareDocument('document', document.name, document.type)} + onDelete={() => deleteDocument(document)} + /> + + + ); +}; + +const document = { + id: '1', + name: 'Document 1', + type: 'Other', + description: 'This is a test document', + uploadDate: new Date('2023-08-01'), + endDate: new Date('2024-08-01'), + fileUrl: 'https://example.com/doc1' +}; + +describe('DocumentCard Component', () => { + it('renders without crashing', () => { + MockCardComponent({ document }); + expect(screen.getByText('Document 1')).toBeInTheDocument(); + }); + + it('displays document details correctly', () => { + MockCardComponent({ document }); + expect(screen.getByText('Other')).toBeInTheDocument(); + expect(screen.getByText('Uploaded: 8/1/2023')).toBeInTheDocument(); + expect(screen.getByText('Expires: 8/1/2024')).toBeInTheDocument(); + expect(screen.getByText('This is a test document')).toBeInTheDocument(); + }); + + it('handles preview action correctly', () => { + const previewDocument = vi.fn(); + MockCardComponent({ document, previewDocument }); + + fireEvent.click(screen.getByRole('button', { name: 'open-actions-menu' })); + + fireEvent.click(screen.getByText('Preview')); + expect(previewDocument).toHaveBeenCalledWith(document.fileUrl); + }); + + it('handles share action correctly', () => { + const shareDocument = vi.fn(); + MockCardComponent({ document, shareDocument }); + + fireEvent.click(screen.getByRole('button', { name: 'open-actions-menu' })); + + fireEvent.click(screen.getByText('Share')); + expect(shareDocument).toHaveBeenCalledWith('document', document.name, document.type); + }); + + it('handles delete action correctly', () => { + const deleteDocument = vi.fn(); + MockCardComponent({ document, deleteDocument }); + + fireEvent.click(screen.getByRole('button', { name: 'open-actions-menu' })); + + fireEvent.click(screen.getByText('Delete')); + expect(deleteDocument).toHaveBeenCalledWith(document); + }); +}); diff --git a/test/components/Documents/DocumentTable.test.jsx b/test/components/Documents/DocumentTable.test.jsx index d3419a0af..925276499 100644 --- a/test/components/Documents/DocumentTable.test.jsx +++ b/test/components/Documents/DocumentTable.test.jsx @@ -1,80 +1,107 @@ import React from 'react'; -import { render } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; import { BrowserRouter } from 'react-router-dom'; -import DocumentTable from '@components/Documents'; +import { cleanup, render, screen } from '@testing-library/react'; +import { afterEach, describe, it, vi, expect } from 'vitest'; +import { DocumentListContext } from '@contexts'; +import { DocumentTable } from '@components/Documents'; +import createMatchMedia from '../../helpers/createMatchMedia'; -import { v4 as uuidv4 } from 'uuid'; +// Clear created DOM after each test +afterEach(() => { + cleanup(); +}); -import { DocumentListContext } from '@contexts'; +const MockTableComponent = ({ + documentListObject, + loadingDocuments, + isMobile, + isLoggedIn = true +}) => { + window.matchMedia = createMatchMedia(isMobile ? 500 : 1200); -// eslint-disable-next-line no-extend-native -String.prototype.withSpaces = () => { - const randomSpaces = Array.from({ length: Math.floor(Math.random() * 5) }, () => - Math.floor(Math.random() * this.length) + return render( + + + + + ); - return this.split('').reduce((acc, char, i) => { - if (randomSpaces.includes(i)) { - return `${acc} ${char}`; - } - return acc + char; - }, ''); }; -const mockedDocumentContext = { - documentListObject: { - docList: [ - { - id: uuidv4(), - name: 'test.pdf', - type: 'Other', - description: 'test description', - endDate: new Date(), - uploadDate: new Date(), - fileUrl: 'http://localhost:3000/pod/PASS/Documents/test.pdf' - }, - { - id: uuidv4(), - name: 'test2.pdf', - type: 'Passport', - description: 'test description 2', - endDate: null, - uploadDate: new Date(), - fileUrl: 'http://localhost:3000/pod/PASS/Documents/test2.pdf' - } - ], - dataset: {}, - containerURL: 'foo' - }, - loadingDocuments: false +const documentListObject = { + docList: [ + { + id: '1', + name: 'test.pdf', + type: 'Other', + description: 'test description', + uploadDate: new Date(), + endDate: new Date(), + fileUrl: 'http://localhost:3000/pod/PASS/Documents/test.pdf' + }, + { + id: '2', + name: 'test2.pdf', + type: 'Passport', + description: 'test description 2', + uploadDate: new Date(), + endDate: null, + fileUrl: 'http://localhost:3000/pod/PASS/Documents/test2.pdf' + } + ] }; -const MockTableComponent = () => ( - - - - - -); - describe('DocumentTable Component', () => { - it('renders with documents', () => { - const { getAllByRole, queryByRole } = render(); - const allRows = getAllByRole('row'); - expect(allRows.length).toBe(3); - - const row1FileName = queryByRole('cell', { name: 'test.pdf' }); - const row1FileType = queryByRole('cell', { name: 'Other' }); - const row1Description = queryByRole('cell', { name: 'test description' }); - const row2FileName = queryByRole('cell', { name: 'test2.pdf' }); - const row2FileType = queryByRole('cell', { name: 'Passport' }); - const row2Description = queryByRole('cell', { name: 'test description 2' }); - - expect(row1FileName).not.toBeNull(); - expect(row1FileType).not.toBeNull(); - expect(row1Description).not.toBeNull(); - expect(row2FileName).not.toBeNull(); - expect(row2FileType).not.toBeNull(); - expect(row2Description).not.toBeNull(); + it('renders without crashing', () => { + MockTableComponent({ documentListObject, loadingDocuments: false }); + expect(screen.getByText('test.pdf')).toBeInTheDocument(); + }); + + it('displays loading animation when documents are loading', () => { + MockTableComponent({ documentListObject, loadingDocuments: true }); + expect(screen.getByTestId('loading-animation')).toBeInTheDocument(); + }); + + it('renders DocumentsDesktop for larger screens', () => { + MockTableComponent({ documentListObject, loadingDocuments: false, isMobile: false }); + expect(screen.getByTestId('documents-desktop')).toBeInTheDocument(); + }); + + it('renders DocumentsMobile for smaller screens', () => { + MockTableComponent({ documentListObject, loadingDocuments: false, isMobile: true }); + expect(screen.getByTestId('documents-mobile')).toBeInTheDocument(); + }); + + it('displays empty list notification when no documents are present', () => { + MockTableComponent({ documentListObject: { docList: [] }, loadingDocuments: false }); + expect(screen.getByTestId('empty-list')).toBeInTheDocument(); + }); + + it('renders all documents from documentListContext', () => { + MockTableComponent({ documentListObject, loadingDocuments: false }); + + const allRows = screen.getAllByRole('row'); + expect(allRows.length).toBe(documentListObject.docList.length + 1); + + documentListObject.docList.forEach((document) => { + expect(screen.getByRole('cell', { name: document.name })).not.toBeNull(); + expect(screen.getByRole('cell', { name: document.description })).not.toBeNull(); + }); + }); + + it('renders DocumentsListTableDesktop when user is logged in on larger screen device', () => { + MockTableComponent({ documentListObject, loadingDocuments: false, isMobile: false }); + + expect(screen.getByTestId('documents-desktop')).not.toBeNull(); + }); + + it('renders DocumentsListTableMobile when user is logged in on smaller screen device', () => { + MockTableComponent({ documentListObject, loadingDocuments: false, isMobile: true }); + + expect(screen.getByTestId('documents-mobile')).not.toBeNull(); }); }); diff --git a/test/components/Documents/DocumentsDesktop.test.jsx b/test/components/Documents/DocumentsDesktop.test.jsx new file mode 100644 index 000000000..58c69afef --- /dev/null +++ b/test/components/Documents/DocumentsDesktop.test.jsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { cleanup, render, screen, within, fireEvent } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { SessionContext } from '@contexts'; +import { DocumentsDesktop } from '@components/Documents'; +import { getTypeText } from '@utils'; + +// Clear created DOM after each test +afterEach(() => { + cleanup(); +}); + +const queryClient = new QueryClient(); + +const MockTableComponent = ({ + documents, + deleteDocument = vi.fn(), + previewDocument = vi.fn(), + shareDocument = vi.fn(), + 'data-testid': dataTestId = 'documents-desktop', + sessionObj +}) => ( + + + + + + + +); + +const documents = [ + { + id: '1', + name: 'Document 1', + type: 'Other', + description: 'Description 1', + uploadDate: new Date('2023-08-01'), + endDate: new Date('2024-08-01'), + fileUrl: 'https://example.com/doc1' + }, + { + id: '2', + name: 'Document 2', + type: 'Other', + description: 'Description 2', + uploadDate: new Date('2023-07-01'), + endDate: new Date('2024-07-01'), + fileUrl: 'https://example.com/doc2' + } +]; + +describe('DocumentsDesktop Component', () => { + const sessionObj = { + login: vi.fn(), + fetch: vi.fn(), + podUrl: 'https://example.com', + session: { + fetch: vi.fn(), + info: { + webId: 'https://example.com/profile/', + isLoggedIn: true + } + } + }; + + it('renders', () => { + render(); + }); + + it('renders documents in the grid with all expected values', () => { + render(); + + const dataGrid = screen.getByTestId('documents-desktop'); + + documents.forEach((document) => { + expect(within(dataGrid).getByText(document.name)).not.toBeNull(); + + const typeElements = within(dataGrid).getAllByText(getTypeText(document.type)); + expect(typeElements.length).toBeGreaterThan(0); + + expect(within(dataGrid).getByText(document.description)).not.toBeNull(); + + expect(within(dataGrid).getByText(document.uploadDate.toLocaleDateString())).not.toBeNull(); + + const endDateText = document.endDate + ? document.endDate.toLocaleDateString() + : '[Not Provided]'; + expect(within(dataGrid).getByText(endDateText)).not.toBeNull(); + }); + }); + + it('handles preview button click', () => { + const previewDocument = vi.fn(); + render( + + ); + + fireEvent.click(screen.getByTestId(`preview-button-${documents[0].id}`)); + + expect(previewDocument).toHaveBeenCalledWith(documents[0].fileUrl); + }); + + it('handles share button click', () => { + const shareDocument = vi.fn(); + render( + + ); + + fireEvent.click(screen.getByTestId(`share-button-${documents[0].id}`)); + expect(shareDocument).toHaveBeenCalledWith('document', documents[0].name, documents[0].type); + }); + + it('handles delete button click', () => { + const deleteDocument = vi.fn(); + render( + + ); + + fireEvent.click(screen.getByTestId(`delete-button-${documents[0].id}`)); + expect(deleteDocument).toHaveBeenCalledWith(documents[0]); + }); +}); diff --git a/test/components/Documents/DocumentsMobile.test.jsx b/test/components/Documents/DocumentsMobile.test.jsx new file mode 100644 index 000000000..c22714511 --- /dev/null +++ b/test/components/Documents/DocumentsMobile.test.jsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { cleanup, render, screen, fireEvent } from '@testing-library/react'; +import { afterEach, describe, it, vi, expect } from 'vitest'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { SessionContext } from '@contexts'; +import { DocumentsMobile } from '@components/Documents'; + +// Clear created DOM after each test +afterEach(() => { + cleanup(); +}); + +const queryClient = new QueryClient(); + +const MockTableComponent = ({ + documents, + deleteDocument = vi.fn(), + previewDocument = vi.fn(), + shareDocument = vi.fn(), + 'data-testid': dataTestId = 'documents-mobile', + sessionObj +}) => ( + + + + + + + +); + +const documents = [ + { + id: '1', + name: 'Document 1', + type: 'Other', + description: 'Description 1', + uploadDate: new Date('2023-08-01'), + endDate: new Date('2024-08-01'), + fileUrl: 'https://example.com/doc1' + }, + { + id: '2', + name: 'Document 2', + type: 'Other', + description: 'Description 2', + uploadDate: new Date('2023-07-01'), + endDate: new Date('2024-07-01'), + fileUrl: 'https://example.com/doc2' + } +]; + +describe('DocumentsMobile Component', () => { + const sessionObj = { + login: vi.fn(), + fetch: vi.fn(), + podUrl: 'https://example.com', + session: { + fetch: vi.fn(), + info: { + webId: 'https://example.com/profile/', + isLoggedIn: true + } + } + }; + + it('renders', () => { + render(); + }); + + it('renders document cards', () => { + render(); + + documents.forEach((doc) => { + const name = screen.getByText(doc.name); + const types = screen.getAllByText(doc.type); + expect(name).toBeInTheDocument(); + expect(types).toHaveLength(2); + }); + }); + + it('handles preview action', () => { + const previewDocument = vi.fn(); + render( + + ); + + fireEvent.click(screen.getAllByLabelText('open-actions-menu')[0]); + + fireEvent.click(screen.getByText('Preview')); + expect(previewDocument).toHaveBeenCalledWith(documents[0].fileUrl); + }); + + it('handles share action', () => { + const shareDocument = vi.fn(); + render( + + ); + + fireEvent.click(screen.getAllByLabelText('open-actions-menu')[0]); + + fireEvent.click(screen.getByText('Share')); + expect(shareDocument).toHaveBeenCalledWith('document', documents[0].name, documents[0].type); + }); + + it('handles delete action', () => { + const deleteDocument = vi.fn(); + render( + + ); + + fireEvent.click(screen.getAllByLabelText('open-actions-menu')[0]); + + fireEvent.click(screen.getByText('Delete')); + expect(deleteDocument).toHaveBeenCalledWith(documents[0]); + }); +}); From d2093cb7c271dc795826400e1c7bcba522be990c Mon Sep 17 00:00:00 2001 From: Joshua Cornett Date: Sun, 1 Sep 2024 01:16:29 -0700 Subject: [PATCH 31/36] [chore, style, test](Documents): Remove document name truncation from cards. Remove redundant test cases. Align test names with convention. Align CSS units with convention. --- src/components/Documents/DocumentCard.jsx | 9 +++++---- test/components/Documents/DocumentCard.test.jsx | 2 +- test/components/Documents/DocumentTable.test.jsx | 14 +------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 4ee025d7a..7257b77f0 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -80,7 +80,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const dataStyling = { ...rowStyling, color: 'text.secondary', - fontSize: '.875rem', + fontSize: '14px', justifyContent: 'flex-start' }; @@ -94,6 +94,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { const dataColumnStyling = { ...columnStyling, flex: '1 1 90%', + maxWidth: '90%', rowGap: '5px', '& div': { ...dataSectionStyling @@ -137,13 +138,13 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { - {truncateText(document.name) || '[No Name]'} + {document.name || '[No Name]'} { {document.description && ( diff --git a/test/components/Documents/DocumentCard.test.jsx b/test/components/Documents/DocumentCard.test.jsx index 1783db7f2..e00517fb9 100644 --- a/test/components/Documents/DocumentCard.test.jsx +++ b/test/components/Documents/DocumentCard.test.jsx @@ -46,7 +46,7 @@ const document = { }; describe('DocumentCard Component', () => { - it('renders without crashing', () => { + it('renders', () => { MockCardComponent({ document }); expect(screen.getByText('Document 1')).toBeInTheDocument(); }); diff --git a/test/components/Documents/DocumentTable.test.jsx b/test/components/Documents/DocumentTable.test.jsx index 925276499..00759b15d 100644 --- a/test/components/Documents/DocumentTable.test.jsx +++ b/test/components/Documents/DocumentTable.test.jsx @@ -56,7 +56,7 @@ const documentListObject = { }; describe('DocumentTable Component', () => { - it('renders without crashing', () => { + it('renders crashing', () => { MockTableComponent({ documentListObject, loadingDocuments: false }); expect(screen.getByText('test.pdf')).toBeInTheDocument(); }); @@ -92,16 +92,4 @@ describe('DocumentTable Component', () => { expect(screen.getByRole('cell', { name: document.description })).not.toBeNull(); }); }); - - it('renders DocumentsListTableDesktop when user is logged in on larger screen device', () => { - MockTableComponent({ documentListObject, loadingDocuments: false, isMobile: false }); - - expect(screen.getByTestId('documents-desktop')).not.toBeNull(); - }); - - it('renders DocumentsListTableMobile when user is logged in on smaller screen device', () => { - MockTableComponent({ documentListObject, loadingDocuments: false, isMobile: true }); - - expect(screen.getByTestId('documents-mobile')).not.toBeNull(); - }); }); From 65ff06da28c1373ec056cd939df8ed03c929d6fd Mon Sep 17 00:00:00 2001 From: Andy Date: Sun, 1 Sep 2024 17:14:30 -0700 Subject: [PATCH 32/36] Matching Contact and Document card styling --- src/components/Documents/DocumentCard.jsx | 95 +++++++---------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index 7257b77f0..3588afcb0 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -65,61 +65,7 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { handleClose(); }; - // ... (Styling definitions below) - - const rowStyling = { - display: 'flex', - columnGap: '10px' - }; - - const columnStyling = { - display: 'flex', - flexDirection: 'column' - }; - - const dataStyling = { - ...rowStyling, - color: 'text.secondary', - fontSize: '14px', - justifyContent: 'flex-start' - }; - - const dataSectionStyling = { - ...columnStyling, - '& div': { - ...dataStyling - } - }; - - const dataColumnStyling = { - ...columnStyling, - flex: '1 1 90%', - maxWidth: '90%', - rowGap: '5px', - '& div': { - ...dataSectionStyling - } - }; - - const actionsColumnStyling = { - ...columnStyling, - flex: '0 1 10%', - maxWidth: '54px', - justifyContent: 'center', - alignItems: 'flex-end', - marginRight: '-10px' - }; - - const cardStyling = { - marginBottom: '15px' - }; - - const cardContentStyling = { - ...rowStyling, - justifyContent: 'space-between' - }; - - // icon styles + // Icon styles const iconSize = { height: '24px', width: '24px' @@ -131,20 +77,26 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { return ( - - - + + + - {document.name || '[No Name]'} + {document.name || '[Untitled document]'} { component="div" noWrap > - {document.type ? getTypeText(document.type) : 'Unknown Type'} + {document.type ? getTypeText(document.type) : 'Other'} {document.uploadDate ? `Uploaded: ${document.uploadDate.toLocaleDateString()}` - : 'Upload Date Unset'} + : 'Upload date: N/A'} {document.endDate ? `Expires: ${document.endDate.toLocaleDateString()}` - : 'No expiration given'} + : 'Expires: Never'} {document.description && ( - + @@ -179,7 +135,14 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { )} - + Date: Tue, 3 Sep 2024 13:29:41 -0700 Subject: [PATCH 33/36] More style matching --- .../Contacts/ContactListTableMobile.jsx | 24 +++--- src/components/Documents/DocumentCard.jsx | 79 +++++++------------ 2 files changed, 38 insertions(+), 65 deletions(-) diff --git a/src/components/Contacts/ContactListTableMobile.jsx b/src/components/Contacts/ContactListTableMobile.jsx index 300ea1b89..037fd9da0 100644 --- a/src/components/Contacts/ContactListTableMobile.jsx +++ b/src/components/Contacts/ContactListTableMobile.jsx @@ -140,20 +140,16 @@ const ContactListTableMobile = ({ > - - {contact.givenName || contact.familyName - ? `${contact.givenName || ''}\u00A0${contact.familyName || ''}` - : '[No name given]'} - - - {contact.webId} - + + + {contact.givenName || contact.familyName + ? `${contact.givenName || ''}\u00A0${contact.familyName || ''}` + : '[No name given]'} + + + {contact.webId} + + { - const theme = useTheme(); - const [anchorEl, setAnchorEl] = useState(null); const [openMenu, setOpenMenu] = useState(null); @@ -84,55 +81,35 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { }} > - - - - {document.name || '[Untitled document]'} - - - {document.type ? getTypeText(document.type) : 'Other'} - - - - - {document.uploadDate - ? `Uploaded: ${document.uploadDate.toLocaleDateString()}` - : 'Upload date: N/A'} - - - {document.endDate - ? `Expires: ${document.endDate.toLocaleDateString()}` - : 'Expires: Never'} - - + + + {document.name || '[Untitled document]'} + + + {document.type ? `Type: ${getTypeText(document.type)}` : 'Type: Other'} + + + + {document.uploadDate + ? `Uploaded: ${document.uploadDate.toLocaleDateString()}` + : 'Uploaded: N/A'} + + + {document.endDate + ? `Expires: ${document.endDate.toLocaleDateString()}` + : 'Expires: Never'} + {document.description && ( - - - {truncateText(document.description, 140)} - - + + {truncateText(document.description, 140)} + )} Date: Tue, 3 Sep 2024 18:17:59 -0700 Subject: [PATCH 34/36] More tweaks --- .../Contacts/ContactListTableMobile.jsx | 56 +++++++++---------- src/components/Documents/DocumentTable.jsx | 4 +- src/components/Documents/DocumentsDesktop.jsx | 2 +- src/components/Documents/DocumentsMobile.jsx | 4 +- src/components/Modals/AddContactModal.jsx | 2 +- src/pages/Signup.jsx | 8 +-- 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/components/Contacts/ContactListTableMobile.jsx b/src/components/Contacts/ContactListTableMobile.jsx index 037fd9da0..a3b19a2d6 100644 --- a/src/components/Contacts/ContactListTableMobile.jsx +++ b/src/components/Contacts/ContactListTableMobile.jsx @@ -139,36 +139,34 @@ const ContactListTableMobile = ({ }} > - - - - {contact.givenName || contact.familyName - ? `${contact.givenName || ''}\u00A0${contact.familyName || ''}` - : '[No name given]'} - - - {contact.webId} - - - + + {contact.givenName || contact.familyName + ? `${contact.givenName || ''}\u00A0${contact.familyName || ''}` + : '[No name given]'} + + + {contact.webId} + + + + handleClick(event, contact)} > - handleClick(event, contact)} - > - - - + + { diff --git a/src/components/Modals/AddContactModal.jsx b/src/components/Modals/AddContactModal.jsx index 8d13eebba..e61062924 100644 --- a/src/components/Modals/AddContactModal.jsx +++ b/src/components/Modals/AddContactModal.jsx @@ -21,7 +21,7 @@ import useMediaQuery from '@mui/material/useMediaQuery'; import { useTheme } from '@mui/material/styles'; // Custom Hooks Imports import useNotification from '@hooks/useNotification'; -// Constant Imports +// Constants Imports import { ENV } from '@constants'; // Util Imports import { saveToClipboard, truncateText } from '@utils'; diff --git a/src/pages/Signup.jsx b/src/pages/Signup.jsx index 76f4d464a..dc79533c8 100644 --- a/src/pages/Signup.jsx +++ b/src/pages/Signup.jsx @@ -13,7 +13,7 @@ import Paper from '@mui/material/Paper'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; import Typography from '@mui/material/Typography'; -// Constant Imports +// Constants Imports import { ENV } from '@constants'; // Signup Form Imports import { @@ -71,7 +71,7 @@ const PassRegistrationTab = ({ register, caseManagerName, previousInfo }) => { */ const Signup = () => { const [oidcIssuer] = useState(ENV.VITE_SOLID_IDENTITY_PROVIDER); - const [storredIssuer, setStorredIssuer] = useState(null); + const [storedIssuer, setStoredIssuer] = useState(null); const [searchParams] = useSearchParams(); const caseManagerWebId = decodeURIComponent(searchParams.get('webId')); const [caseManagerName, setCaseManagerName] = useState(); @@ -135,7 +135,7 @@ const Signup = () => { } const storedOidcIssuer = localStorage.getItem('oidcIssuer', oidcIssuer); - setStorredIssuer(storedOidcIssuer); + setStoredIssuer(storedOidcIssuer); }, [session.info.isLoggedIn, window.location.href]); return ( @@ -168,7 +168,7 @@ const Signup = () => { {step === 'done' && ( From 91a816df1faebbcc7b247afe93eb6af85eac71b1 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 3 Sep 2024 18:25:19 -0700 Subject: [PATCH 35/36] Fixing tests --- test/components/Documents/DocumentCard.test.jsx | 2 +- test/components/Documents/DocumentsMobile.test.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/components/Documents/DocumentCard.test.jsx b/test/components/Documents/DocumentCard.test.jsx index e00517fb9..af3ef2c82 100644 --- a/test/components/Documents/DocumentCard.test.jsx +++ b/test/components/Documents/DocumentCard.test.jsx @@ -53,7 +53,7 @@ describe('DocumentCard Component', () => { it('displays document details correctly', () => { MockCardComponent({ document }); - expect(screen.getByText('Other')).toBeInTheDocument(); + expect(screen.getByText('Type: Other')).toBeInTheDocument(); expect(screen.getByText('Uploaded: 8/1/2023')).toBeInTheDocument(); expect(screen.getByText('Expires: 8/1/2024')).toBeInTheDocument(); expect(screen.getByText('This is a test document')).toBeInTheDocument(); diff --git a/test/components/Documents/DocumentsMobile.test.jsx b/test/components/Documents/DocumentsMobile.test.jsx index c22714511..d7569cd34 100644 --- a/test/components/Documents/DocumentsMobile.test.jsx +++ b/test/components/Documents/DocumentsMobile.test.jsx @@ -82,7 +82,7 @@ describe('DocumentsMobile Component', () => { documents.forEach((doc) => { const name = screen.getByText(doc.name); - const types = screen.getAllByText(doc.type); + const types = screen.getAllByText(`Type: ${doc.type}`); expect(name).toBeInTheDocument(); expect(types).toHaveLength(2); }); From a0972c0286afa884d0c932ccd3e65801bc11795f Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Tue, 3 Sep 2024 19:02:43 -0700 Subject: [PATCH 36/36] Final styling adjustments to cards in Contacts and Documents --- .../Contacts/ContactListTableMobile.jsx | 216 +++++++++--------- src/components/Documents/DocumentCard.jsx | 4 +- src/components/Documents/DocumentsMobile.jsx | 26 ++- 3 files changed, 131 insertions(+), 115 deletions(-) diff --git a/src/components/Contacts/ContactListTableMobile.jsx b/src/components/Contacts/ContactListTableMobile.jsx index a3b19a2d6..48ece5b3d 100644 --- a/src/components/Contacts/ContactListTableMobile.jsx +++ b/src/components/Contacts/ContactListTableMobile.jsx @@ -130,113 +130,121 @@ const ContactListTableMobile = ({ - {contacts?.map((contact) => ( - - - - - - {contact.givenName || contact.familyName - ? `${contact.givenName || ''}\u00A0${contact.familyName || ''}` - : '[No name given]'} - - - {contact.webId} - - - - handleClick(event, contact)} - > - - - - - + {contacts?.map((contact) => ( + + - } - sx={iconStyling} - > - Profile - - - } - sx={iconStyling} - > - Message - - {/* TODO: Keep copy function? */} - {/* If so, also add to Desktop table? */} - {/* Maybe without any icon. Simply click on the web ID and it will copy? */} - - saveToClipboard(contact.webId, 'webId copied to clipboard', addNotification), - contact - )} - startIcon={} - sx={iconStyling} - > - Copy WebId - - } - sx={iconStyling} - > - Edit - - } - sx={iconStyling} + + + + {contact.givenName || contact.familyName + ? `${contact.givenName || ''}\u00A0${contact.familyName || ''}` + : '[No name given]'} + + + {contact.webId} + + + + handleClick(event, contact)} + > + + + + + - Delete - - - - - ))} + } + sx={iconStyling} + > + Profile + + + } + sx={iconStyling} + > + Message + + {/* TODO: Keep copy function? */} + {/* If so, also add to Desktop table? */} + {/* Maybe without any icon. Simply click on the web ID and it will copy? */} + + saveToClipboard(contact.webId, 'webId copied to clipboard', addNotification), + contact + )} + startIcon={} + sx={iconStyling} + > + Copy WebId + + } + sx={iconStyling} + > + Edit + + } + sx={iconStyling} + > + Delete + + + + + ))} + ); }; diff --git a/src/components/Documents/DocumentCard.jsx b/src/components/Documents/DocumentCard.jsx index decc7c55d..af2b6a474 100644 --- a/src/components/Documents/DocumentCard.jsx +++ b/src/components/Documents/DocumentCard.jsx @@ -104,11 +104,11 @@ const DocumentCard = ({ document, onShare, onDelete, onPreview }) => { {document.endDate ? `Expires: ${document.endDate.toLocaleDateString()}` - : 'Expires: Never'} + : 'Expires: N/A'} {document.description && ( - {truncateText(document.description, 140)} + {truncateText(document.description, 140)} )} diff --git a/src/components/Documents/DocumentsMobile.jsx b/src/components/Documents/DocumentsMobile.jsx index 12ba6c9f5..ec9157900 100644 --- a/src/components/Documents/DocumentsMobile.jsx +++ b/src/components/Documents/DocumentsMobile.jsx @@ -76,15 +76,23 @@ const DocumentsMobile = ({ documents, handlers }) => { - {documents.map((document) => ( - handlers.onShare('document', document.name, document.type)} - onDelete={() => handlers.onDelete(document)} - onPreview={() => handlers.onPreview(document.fileUrl)} - /> - ))} + + {documents.map((document) => ( + handlers.onShare('document', document.name, document.type)} + onDelete={() => handlers.onDelete(document)} + onPreview={() => handlers.onPreview(document.fileUrl)} + /> + ))} + ); };