diff --git a/.github/workflows/ci.yaml b/.github/workflows/docs-publish.yaml similarity index 67% rename from .github/workflows/ci.yaml rename to .github/workflows/docs-publish.yaml index 3ba434c8..fbc0bf9d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/docs-publish.yaml @@ -1,4 +1,4 @@ -name: ci +name: docs-publish on: workflow_dispatch: pull_request: @@ -9,10 +9,6 @@ on: release: types: - published - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true permissions: contents: write jobs: @@ -29,26 +25,17 @@ jobs: - uses: pre-commit/action@v3.0.0 with: extra_args: --hook-stage manual --all-files - - deploy: + docs: runs-on: ubuntu-latest - if: github.event.repository.fork == false steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.x - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - - uses: actions/cache@v3 - with: - key: mkdocs-material-${{ env.cache_id }} - path: .cache - restore-keys: | - mkdocs-material- - - run: - pip install - git+https://${GH_TOKEN}@github.com/squidfunk/mkdocs-material.git - mkdocs-table-reader-plugin + - name: "Upgrade pip" + run: pip install --upgrade pip + - name: "Install requirements" + run: pip install -r site/requirements.txt - name: Build and Deploy working-directory: ./site run: | @@ -56,3 +43,6 @@ jobs: mkdocs gh-deploy --force env: GH_TOKEN: ${{ secrets.GH_TOKEN }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f8d5847..7bc86da3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.1.14" + rev: "v0.2.1" hooks: - id: ruff args: ["--fix", "--show-fixes"] diff --git a/HOWTO_reset_the_database.md b/HOWTO_reset_the_database.md deleted file mode 100644 index 76e6ed43..00000000 --- a/HOWTO_reset_the_database.md +++ /dev/null @@ -1,49 +0,0 @@ -There are a couple of reasons why one might wish to remove the contents of the -AssurancePlatform database and recreate a fresh one: - -- After a workshop or demo session, may want to remove all users and created - cases. -- You (or someone) has made some non-migratable schema changes in the code. How - to do this depends on whether you are running locally, or on a - cloud-deployment (such as having followed the instructions for deploying on - Azure [here](HOWTO_deploy_to_Azure.md)). - -## Running on your local machine - -Unless you have changed some settings, you will likely be using a local `sqlite` -file for your database. To remove it, simply remove the file -`eap_backend/db.sqlite3`. Then run (having setup the environment for the backend -following the instructions [here](README.md)) the commands: - -``` -python manage.py makemigrations && python manage.py migrate -``` - -and next time you run the backend, you should have a new sqlite file with an -empty database. - -## Running on Azure - -To delete and recreate the database on an Azure deployment, it is necessary to -connect to it using something like `psql` (as described -[here](HOWTO_deploy_to_Azure.md)). - -1. If you haven't already done this when setting up, ensure that your IP address - can connect to the database server. Via the Azure portal, find your database - resource, and click on "Connection security" in the left sidebar, click "Add - current client IP address", and save. -2. If you don't already have it, install `psql`. For Mac/Homebrew, - `brew install postgresql` should work. For other OSs, Google is available :) -3. drop and recreate the database via psql. - -```psql --host=SERVER_NAME.postgres.database.azure.com --port=5432 --username=ADMIN_USERNAME@SERVER_NAME --dbname=postgres -postgres=> DROP DATABASE eap; -postgres=> CREATE DATABASE eap; -postgres=> \c eap; -postgres=> \q -``` - -where `SERVER_NAME`, `ADMIN_USERNAME`, and the admin password should all be -available via the Azure portal (password may be stored in a keyvault). 4. In the -Azure portal, find the Web app for the backend, and restart it. After a few -minutes, everything should be back up and running. diff --git a/eap_backend/eap_api/view_utils.py b/eap_backend/eap_api/view_utils.py index b7b42ad3..a17f9f4a 100644 --- a/eap_backend/eap_api/view_utils.py +++ b/eap_backend/eap_api/view_utils.py @@ -26,13 +26,24 @@ "serializer": AssuranceCaseSerializer, "model": AssuranceCase, "children": ["goals"], - "fields": ("name", "description", "lock_uuid", "owner", "color_profile"), + "fields": ( + "name", + "description", + "lock_uuid", + "owner", + "color_profile", + ), }, "goal": { "serializer": TopLevelNormativeGoalSerializer, "model": TopLevelNormativeGoal, "children": ["context", "property_claims", "strategies"], - "fields": ("name", "short_description", "long_description", "keywords"), + "fields": ( + "name", + "short_description", + "long_description", + "keywords", + ), "parent_types": [("assurance_case", False)], }, "context": { @@ -259,6 +270,9 @@ def get_case_permissions(case, user): "view": if user is a member of a group that has view rights on the case None otherwise. """ + if isinstance(case, (int, str)): + case = AssuranceCase.objects.get(pk=int(case)) + if (not case.owner) or (case.owner == user): # case has no owner - anyone can view it, or user is owner return "manage" diff --git a/frontend/src/Theming.jsx b/frontend/src/Theming.jsx index 60c3650c..32c122ba 100644 --- a/frontend/src/Theming.jsx +++ b/frontend/src/Theming.jsx @@ -1,11 +1,12 @@ import * as React from "react"; import { createTheme, ThemeProvider } from "@mui/material/styles"; import { CssBaseline } from "@mui/material"; +import configData from "./config.json"; function Theming({ children }) { const theme = createTheme({ typography: { - fontFamily: '"Plus Jakarta Sans", sans-serif', + fontFamily: configData.styling.mainFont, fontSize: 16, h1: { fontSize: "2.625rem", diff --git a/frontend/src/components/CaseAccessibilityModal.jsx b/frontend/src/components/CaseAccessibilityModal.jsx index eebb50a1..39a87ada 100644 --- a/frontend/src/components/CaseAccessibilityModal.jsx +++ b/frontend/src/components/CaseAccessibilityModal.jsx @@ -18,6 +18,23 @@ import configData from "../config.json"; import { Wheelchair } from "./common/Icons.jsx"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * CaseAccessibilityModal provides a modal dialog for editing the accessibility options, + * specifically the colour profile, of an assurance case diagram. Users can select from + * predefined colour schemes configured in `config.json` to enhance diagram accessibility. + * + * @param {Object} props The component props. + * @param {boolean} props.isOpen Indicates if the modal is open. + * @param {Function} props.onClose Function to call when closing the modal. + * @param {Function} props.onSuccess Function to call upon successful update of the case. + * @param {string} props.caseId The ID of the case being edited. + * @param {string} props.currentColour The current colour profile of the case diagram. + * + * This component utilizes the `editCase` API to submit the selected colour profile update, + * handling loading states, success, and error feedback within the modal dialog. The colour + * selection is made through a radio button group, offering a user-friendly way to enhance + * diagram accessibility for users with visual impairments. + */ function CaseAccessibilityModal({ isOpen, onClose, diff --git a/frontend/src/components/CaseContainer.js b/frontend/src/components/CaseContainer.js index 2f15efe6..25c5e602 100644 --- a/frontend/src/components/CaseContainer.js +++ b/frontend/src/components/CaseContainer.js @@ -15,6 +15,14 @@ import { ColumnFlow, RowFlow } from "./common/Layout.jsx"; import { Add, Subtract, Target } from "./common/Icons.jsx"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * CaseContainer serves as the main component for displaying and interacting with an assurance case. + * It integrates various components such as MermaidChart for visual representation, ItemEditor for editing case items, + * and CaseTopBar for additional case management functions. This component handles loading of case data, + * user authentication, and provides zoom and pan functionality for the case diagram. + * + * @returns {JSX.Element} Renders the assurance case container with editing capabilities and visualization controls. + */ function CaseContainer() { const { caseSlug } = useParams(); const theme = useTheme(); @@ -120,13 +128,16 @@ function CaseContainer() { } }, [assuranceCase]); + /** + * Generates a unique identifier for new elements within the assurance case based on the type and its parents. + * It prefixes the identifier based on the type and ensures uniqueness within the case. + * + * @param {string} type - The type of the element for which the ID is being generated. + * @param {string} parentId - The ID of the parent element. + * @param {string} parentType - The type of the parent element. + * @returns {string} A unique identifier for the new element. + */ const getIdForNewElement = useCallback( - /** - * @param {string} type - * @param {string} parentId - * @param {string} parentType - * @returns {string} - */ (type, parentId, parentType) => { let prefix = configData.navigation[type].db_name .substring(0, 1) @@ -154,6 +165,11 @@ function CaseContainer() { [assuranceCase, identifiers], ); + /** + * Updates all identifiers within the assurance case to ensure they are unique. + * This function might be necessary when there are changes to the case structure + * or to correct any identifier conflicts. + */ const updateAllIdentifiers = useCallback(() => { setIsLoading(true); @@ -381,7 +397,13 @@ function CaseContainer() { ); } -/** @returns {string[]} */ +/** + * Generates a list of identifiers from the assurance case. It recursively visits + * each item in the case structure, collecting the names to form a set of identifiers. + * + * @param {Object} assuranceCase - The assurance case object from which to generate the list. + * @returns {string[]} A list of unique identifiers derived from the assurance case items. + */ function updateIdList(assuranceCase) { const set = []; assuranceCase.goals.forEach((goal) => { diff --git a/frontend/src/components/CaseCreator.js b/frontend/src/components/CaseCreator.js index 59a61c21..88c9b7fa 100644 --- a/frontend/src/components/CaseCreator.js +++ b/frontend/src/components/CaseCreator.js @@ -6,6 +6,18 @@ import CaseImporterFlow from "./CaseImporterFlow.jsx"; import ModalDialog from "./common/ModalDialog.jsx"; import useId from "@mui/utils/useId"; +/** + * CaseCreator component that toggles between the CaseCreatorFlow and CaseImporterFlow based on user action. + * It presents a modal dialog which either guides the user through creating a new assurance case + * or importing an existing one. The component ensures that a user is logged in before allowing case creation + * or importation. + * + * @param {Object} props - Component props. + * @param {boolean} props.isOpen - Determines if the modal dialog is open. + * @param {Function} props.onClose - Handler called when the modal dialog is requested to close. + * @param {boolean} props.isImport - Flag determining which flow to show: true for import, false for creation. + * @returns {JSX.Element|null} The rendered component if the user is logged in, otherwise null. + */ function CaseCreator({ isOpen, onClose, isImport }) { const titleId = useId(); diff --git a/frontend/src/components/CaseCreatorFlow.jsx b/frontend/src/components/CaseCreatorFlow.jsx index 5d7d9ec5..992766dd 100644 --- a/frontend/src/components/CaseCreatorFlow.jsx +++ b/frontend/src/components/CaseCreatorFlow.jsx @@ -10,10 +10,23 @@ import LoadingSpinner from "./common/LoadingSpinner.jsx"; import { ArrowRight } from "./common/Icons.jsx"; import ErrorMessage from "./common/ErrorMessage.jsx"; -// see models.py +/** + * Maximum allowed lengths for title and description, + * see /eap_backend/eap_api/models.py + */ const titleMaxLength = 200; const descriptionMaxLength = 1000; + +/** + * CaseCreatorFlow component guides the user through the process of creating a new assurance case. + * It consists of two stages: entering basic case details (title and description), + * and selecting a template for the case. Upon completion, the case is posted to the server. + * + * @param {Object} props Component props. + * @param {string} props.titleId A unique ID for the title element, used for accessibility. + * @param {Function} props.onClose Function to call when the user chooses to close the modal. + */ function CaseCreatorFlow({ titleId, onClose }) { const [stage, setStage] = useState(0); diff --git a/frontend/src/components/CaseImporterFlow.jsx b/frontend/src/components/CaseImporterFlow.jsx index 061ef7d1..3384ae78 100644 --- a/frontend/src/components/CaseImporterFlow.jsx +++ b/frontend/src/components/CaseImporterFlow.jsx @@ -17,6 +17,14 @@ import FileInput from "./common/FileInput.jsx"; import { ArrowRight } from "./common/Icons.jsx"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * CaseImporterFlow allows users to import an assurance case into the TEA Platform from either a file or a URL. + * It supports importing from SVG files with embedded JSON metadata or directly from JSON files. + * + * @param {Object} props The component props. + * @param {string} props.titleId A unique identifier for the title element, used for accessibility. + * @param {Function} props.onClose A function to call when closing the import modal. + */ function CaseImporterFlow({ titleId, onClose }) { const [uploadType, setUploadType] = useState("file"); const [url, setUrl] = useState(""); @@ -33,6 +41,11 @@ function CaseImporterFlow({ titleId, onClose }) { const baseURL = `${getBaseURL()}`; const navigate = useNavigate(); + /** Parses SVG text to extract embedded JSON metadata + * @param {string} text The SVG text to parse + * @returns {Promise} The parsed JSON metadata + * @throws {Error} If the metadata cannot be parsed + */ const parseSvg = useCallback(async (text) => { const parser = new DOMParser(); const svgDoc = parser.parseFromString(text, "image/svg+xml"); @@ -48,8 +61,13 @@ function CaseImporterFlow({ titleId, onClose }) { } }, []); + /** Fetches content from a URL and tries to parse it as JSON or SVG + * + * @param {string} url + * @returns {Promise} The parsed JSON or SVG content + * @throws {Error} If the content cannot be loaded or parsed + */ const getUrlContent = useCallback( - /** @param {string} url */ async (url) => { try { const response = await fetch(url); @@ -75,6 +93,13 @@ function CaseImporterFlow({ titleId, onClose }) { [parseSvg] ); + /** + * Posts the JSON representation of a case to the backend + * + * @param {Object} json The case JSON to post + * @returns {Promise} + * @throws {Error} If the JSON cannot be posted + */ const postCaseJSON = useCallback( (json) => { const requestOptions = { @@ -108,6 +133,13 @@ function CaseImporterFlow({ titleId, onClose }) { [token, baseURL, navigate] ); + /** + * Handles form submission for importing a case + * + * @param {Event} e The form submission event + * @returns {void} + * @throws {Error} If the form submission fails + */ const onSubmit = useCallback( (e) => { e.preventDefault(); @@ -133,10 +165,21 @@ function CaseImporterFlow({ titleId, onClose }) { [uploadType, url, fileJson, getUrlContent, postCaseJSON] ); + /** + * Handles changes in the selected upload type (file or URL) + * @param {Event} e The change event + * @returns {void} + * @throws {Error} If the change event fails + */ const onTypeChange = useCallback((e) => { setUploadType(e.target.value); }, []); + /** + * Processes the selected file to extract JSON data. + * + * @returns {void} + */ useEffect(() => { if (!file) { setFileJson(null); @@ -200,7 +243,7 @@ function CaseImporterFlow({ titleId, onClose }) { control={} label="File upload" /> - } label="Url upload" /> + } label="URL upload" /> {uploadType === "file" ? ( { const theme = useTheme(); const [token] = useLoginToken(); const [assuranceCase, setAssuranceCase] = useState(); const [isLoading, setIsLoading] = useState(true); + /** + * Fetch the detailed assurance case data when the component mounts. + * + * @param {string} token - The user's login token. + * @param {Object} caseObj - The case object containing information such as the case ID. + * @returns {void} + */ useEffect(() => { if (token) { let isMounted = true; @@ -31,12 +48,14 @@ export const CaseMediaPreview = ({ caseObj }) => { // TODO show error to user }); + // Cleanup function to handle component unmount return () => { isMounted = false; }; } }, [token, caseObj]); + // Conditionally render the Mermaid chart or a static image based on configuration if (configData.use_case_preview_svg) { return ( <> diff --git a/frontend/src/components/CasePermissionsManager.js b/frontend/src/components/CasePermissionsManager.js index 4f1c1bcf..6070642f 100644 --- a/frontend/src/components/CasePermissionsManager.js +++ b/frontend/src/components/CasePermissionsManager.js @@ -12,6 +12,17 @@ import { getCase } from "./caseApi.js"; import { useLoginToken } from "../hooks/useAuth.js"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * CasePermissionsManager serves as a modal dialog for managing group permissions for a specific assurance case within the TEA Platform. It provides an interface for viewing and editing the access levels (view or edit) of various groups associated with the case. + * + * @param {Object} props - Component props. + * @param {string} props.caseId - The unique identifier of the assurance case. + * @param {Object} props.assuranceCase - The assurance case object. + * @param {boolean} props.isOpen - Controls the visibility of the modal dialog. + * @param {Function} props.onClose - Callback function that is called when the modal is requested to be closed. + * @param {Function} props.onSuccess - Callback function that is called after successfully updating permissions. + * @returns {JSX.Element} A modal dialog for managing group permissions. + */ function CasePermissionsManager({ caseId, assuranceCase, @@ -38,6 +49,16 @@ function CasePermissionsManager({ ); } +/** + * CasePermissionsManagerInner is the core component within the CasePermissionsManager modal dialog, responsible for the actual management of group permissions. It allows users to assign 'view' or 'edit' permissions to groups for the specified assurance case, or remove their access entirely. + * + * @param {Object} props - Component props. + * @param {string} props.caseId - The unique identifier of the assurance case for which permissions are being managed. + * @param {Object} props.assuranceCase - The assurance case object, used for preloading existing permissions. + * @param {Function} props.afterSubmit - Callback function that is called after permissions are successfully updated. + * @param {Function} props.onClose - Callback function to close the permissions manager. + * @returns {JSX.Element} The interface for managing group permissions, including a list of groups with selectable permissions and action buttons to submit changes or cancel the operation. + */ function CasePermissionsManagerInner({ caseId, assuranceCase, diff --git a/frontend/src/components/CaseTopBar.jsx b/frontend/src/components/CaseTopBar.jsx index 2b5007c1..0315d89a 100644 --- a/frontend/src/components/CaseTopBar.jsx +++ b/frontend/src/components/CaseTopBar.jsx @@ -20,6 +20,20 @@ import CommentSection from "./CommentSection.js"; import CasePermissionsManager from "./CasePermissionsManager.js"; import { DisguisedTextInput } from "./common/TextInput.jsx"; +/** + * CaseTopBar provides a top navigation bar for the assurance case page within the TEA Platform, offering various functionalities such as editing the case title, adding goals, managing accessibility settings, exporting, adding notes, managing permissions, and deleting the case. It enhances user interaction by providing quick access to these features directly from the case view. + * + * @param {Object} props - Component props. + * @param {Object} props.sx - The style properties applied to the component. + * @param {Object} props.assuranceCase - The current assurance case object, containing case details. + * @param {string} props.caseId - The unique identifier for the current assurance case. + * @param {Function} props.onRefresh - Callback function to refresh the case view after certain actions. + * @param {Function} props.setErrors - Callback function to handle errors and display them to the user. + * @param {Function} props.getIdForNewElement - Function to generate IDs for new case elements. + * @param {Function} props.updateAllIdentifiers - Function to update all identifiers/names within the case, ensuring uniqueness. + * @param {Function} props.setSelected - Function to set the currently selected item in the case. + * @returns {JSX.Element} The top navigation bar with case management features. + */ function CaseTopBar({ sx, assuranceCase, @@ -41,6 +55,12 @@ function CaseTopBar({ const [permissionsOpen, setPermissionsOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); + /** + * Set the case name to the provided value. + * + * @param {string} name - The new name for the case. + * @returns {void} + */ const setCaseName = useCallback( (name) => { editCase(token, caseId, { name }) @@ -53,6 +73,12 @@ function CaseTopBar({ [token, caseId, onRefresh] ); + /** + * Add a new goal to the assurance case. + * + * @returns {void} + * @throws {Error} If the goal cannot be added. + */ const addGoal = useCallback(() => { createItem( token, diff --git a/frontend/src/components/CommentSection.js b/frontend/src/components/CommentSection.js index 6ff3eeb7..02da9e1c 100644 --- a/frontend/src/components/CommentSection.js +++ b/frontend/src/components/CommentSection.js @@ -18,6 +18,15 @@ import { visuallyHidden } from "@mui/utils"; import TableSortLabel from "@mui/material/TableSortLabel"; import Box from "@mui/material/Box"; +/** + * CommentSection provides an interface for users to view and add comments to an assurance case. It presents a modal dialog with a form to submit new comments and a list of existing comments. + * + * @param {Object} props - Component props. + * @param {string} props.caseId - The unique identifier of the assurance case to which comments are related. + * @param {boolean} props.isOpen - Boolean state to control the visibility of the comment section modal. + * @param {Function} props.onClose - Function to call when closing the comment section modal. + * @returns {JSX.Element} A modal dialog component that allows users to manage comments for an assurance case. + */ function CommentSection({ caseId, isOpen, onClose }) { const titleId = useId(); @@ -33,6 +42,14 @@ function CommentSection({ caseId, isOpen, onClose }) { ); } +/** + * CommentSectionInner handles the display and management of comments for a specific assurance case, including posting new comments and sorting existing ones. + * + * @param {Object} props - Component props. + * @param {string} props.assuranceCaseId - The unique identifier of the assurance case. + * @param {Function} props.onClose - Function to call when the user wishes to close the comment section. + * @returns {JSX.Element} The inner content of the comment section, including a form for new comments and a list of existing comments. + */ function CommentSectionInner({ assuranceCaseId, onClose }) { const [comments, setComments] = useState([]); const [error, setError] = useState(""); @@ -45,6 +62,11 @@ function CommentSectionInner({ assuranceCaseId, onClose }) { const [token] = useLoginToken(); + /** + * Fetch comments from the server. + * + * @returns {undefined} + */ const fetchComments = useCallback(async () => { const url = `${getBaseURL()}/comments/${assuranceCaseId}/`; const requestOptions = { @@ -58,10 +80,21 @@ function CommentSectionInner({ assuranceCaseId, onClose }) { setComments(data); }, [assuranceCaseId, token]); + /** + * Fetch comments when the component mounts. + * + * @returns {undefined} + */ useEffect(() => { fetchComments(); }, [fetchComments]); + /** + * Post a new comment to the server. + * + * @param {Event} e - The form submission event. + * @returns {undefined} + */ const onSubmit = useCallback( async (e) => { e.preventDefault(); @@ -100,6 +133,12 @@ function CommentSectionInner({ assuranceCaseId, onClose }) { [assuranceCaseId, token, fetchComments, newComment], ); + /** + * Sort comments by a given property. + * + * @param {string} property - The property to sort by. + * @returns {undefined} + */ const onSort = (property) => { const opositeDir = sort.direction === "asc" ? "desc" : "asc"; const direction = sort.property === property ? opositeDir : sort.direction; diff --git a/frontend/src/components/CreateGroup.js b/frontend/src/components/CreateGroup.js index c1f75f68..56920ba4 100644 --- a/frontend/src/components/CreateGroup.js +++ b/frontend/src/components/CreateGroup.js @@ -4,6 +4,15 @@ import { ColumnFlow, RowFlow } from "./common/Layout.jsx"; import { Button } from "@mui/material"; import TextInput from "./common/TextInput.jsx"; +/** + * CreateGroup is a form component used for creating a new group within the TEA Platform. It provides a simple interface for entering the name of the new group and submitting it to the server. Upon successful submission, the form invokes a callback function to reflect the change. + * + * @param {Object} props - Component props. + * @param {Function} props.afterSubmit - Callback function to be called after successfully creating a group. It is used to trigger any necessary updates in the parent component, such as refreshing the list of groups. + * @returns {JSX.Element} A form that allows users to input a name for a new group and create it. + * + * This component includes error handling to provide feedback to the user in case of an unsuccessful group creation attempt. It leverages the `TextInput` component for inputting the group's name and validates the input before submission to ensure that a name is provided. + */ function CreateGroup({ afterSubmit }) { const [name, setName] = useState(""); const [error, setError] = useState(); diff --git a/frontend/src/components/DeleteCaseModal.jsx b/frontend/src/components/DeleteCaseModal.jsx index 82b44005..98376bd2 100644 --- a/frontend/src/components/DeleteCaseModal.jsx +++ b/frontend/src/components/DeleteCaseModal.jsx @@ -9,6 +9,18 @@ import LoadingSpinner from "./common/LoadingSpinner.jsx"; import { Bin } from "./common/Icons.jsx"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * DeleteCaseModal presents a confirmation dialog for deleting an assurance case. It informs the user of the permanent consequences of this action and provides options to either cancel or proceed with the deletion. This component is critical for ensuring that users consciously acknowledge the deletion of an assurance case, preventing accidental data loss. + * + * @param {Object} props - Component props. + * @param {boolean} props.isOpen - Controls the visibility of the modal dialog. + * @param {Function} props.onClose - Callback function to be called when the modal is closed without deletion. + * @param {string} props.caseId - The unique identifier of the assurance case to be deleted. + * @param {Function} props.onDelete - Callback function to be called after the case has been successfully deleted. + * @returns {JSX.Element} A modal dialog that prompts users to confirm or cancel the deletion of an assurance case. + * + * The component handles the deletion process internally, including API communication and error handling. It displays a loading indicator while the deletion is in progress and provides feedback in case of errors. The use of `ModalDialog`, `Typography`, and `Button` components from Material UI ensures a consistent and accessible user interface. + */ function DeleteCaseModal({ isOpen, onClose, caseId, onDelete }) { const [loading, setLoading] = useState(false); const [errors, setErrors] = useState([]); @@ -18,6 +30,12 @@ function DeleteCaseModal({ isOpen, onClose, caseId, onDelete }) { const titleId = useId(); const descriptionId = useId(); + /** + * Handle the deletion of the assurance case. + * + * @returns {void} + * @throws {Error} If the deletion process fails. + */ const onDeleteClick = useCallback(() => { setLoading(true); setErrors([]); diff --git a/frontend/src/components/DeleteItemModal.jsx b/frontend/src/components/DeleteItemModal.jsx index 48e69ed6..8420104c 100644 --- a/frontend/src/components/DeleteItemModal.jsx +++ b/frontend/src/components/DeleteItemModal.jsx @@ -9,6 +9,20 @@ import LoadingSpinner from "./common/LoadingSpinner.jsx"; import { Bin } from "./common/Icons.jsx"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * DeleteItemModal presents a confirmation dialog for deleting an item (e.g., goal, context, claim) within an assurance case. This component is crucial for ensuring that users are fully aware of the permanent deletion of the item and any associated links. It provides users with a clear choice to either proceed with the deletion or cancel the action to prevent accidental data loss. + * + * @param {Object} props - Component props. + * @param {boolean} props.isOpen - Controls the visibility of the modal dialog. + * @param {Function} props.onClose - Callback function to be called when the modal is closed without deletion. + * @param {string} props.id - The unique identifier of the item to be deleted. + * @param {string} props.type - The type of the item to be deleted (e.g., "TopLevelNormativeGoal", "Context"). + * @param {string} props.name - The name of the item to be deleted, displayed in the modal for clarity. + * @param {Function} props.onDelete - Callback function to be called after the item has been successfully deleted. + * @returns {JSX.Element} A modal dialog that prompts users to confirm or cancel the deletion of an item within an assurance case. + * + * The component manages the deletion process, including API communication and error handling, and displays a loading indicator while the deletion is in progress. Through the use of Material UI components like `ModalDialog`, `Typography`, and `Button`, it ensures a consistent and accessible user interface, while the `ErrorMessage` component provides feedback in case of errors. + */ function DeleteItemModal({ isOpen, onClose, id, type, name, onDelete }) { const [loading, setLoading] = useState(false); const [errors, setErrors] = useState([]); @@ -18,6 +32,12 @@ function DeleteItemModal({ isOpen, onClose, id, type, name, onDelete }) { const titleId = useId(); const descriptionId = useId(); + /** + * Handle the deletion of the item. + * + * @returns {void} + * @throws {Error} If the deletion process fails. + */ const onDeleteClick = useCallback(() => { setLoading(true); setErrors([]); diff --git a/frontend/src/components/ExportCaseModal.jsx b/frontend/src/components/ExportCaseModal.jsx index 0d239452..1efd29b8 100644 --- a/frontend/src/components/ExportCaseModal.jsx +++ b/frontend/src/components/ExportCaseModal.jsx @@ -20,6 +20,18 @@ import { getCase } from "./caseApi.js"; import { useLoginToken } from "../hooks/useAuth.js"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * ExportCaseModal provides a user interface for exporting an assurance case in either JSON or SVG format. It allows users to select their preferred export format and initiates the download process. This component ensures that users can easily export and save their work for external use or archival purposes. + * + * @param {Object} props - Component props. + * @param {boolean} props.isOpen - Controls the visibility of the export modal. + * @param {Function} props.onClose - Callback function to close the export modal. + * @param {string} props.caseId - The unique identifier of the assurance case to be exported. + * @param {Object} props.assuranceCase - The loaded assurance case object. If not provided, the case will be fetched using the provided caseId. + * @returns {JSX.Element} A modal dialog that provides options to export the assurance case in selected format. + * + * The component handles the export process based on the selected format: for JSON, it uses `neatJSON` to format the case data and `file-saver` library to initiate the download; for SVG, it utilizes a custom SVGDownloader class to generate and download the SVG representation of the case. It supports dynamic loading of the assurance case if not provided and handles errors during the export process. + */ function ExportCaseModal({ isOpen, onClose, caseId, assuranceCase }) { const [format, setFormat] = useState("json"); const [isLoading, setIsLoading] = useState(false); @@ -34,6 +46,13 @@ function ExportCaseModal({ isOpen, onClose, caseId, assuranceCase }) { setFormat(e.target.value); }, []); + /** + * Handle the export of the assurance case. + * + * @param {Event} e - The form submit event. + * @returns {void} + * @throws {Error} If the export process fails. + */ const onSubmit = useCallback( async (e) => { e.preventDefault(); diff --git a/frontend/src/components/Home.js b/frontend/src/components/Home.js index 07849a41..ab5ef337 100644 --- a/frontend/src/components/Home.js +++ b/frontend/src/components/Home.js @@ -6,6 +6,15 @@ import Login from "./Login"; import { ColumnFlow } from "./common/Layout"; import splashImage from "../images/building-an-assurance-case-adjusted-aspect-ratio.png"; +/** + * Splash presents a visually engaging splash screen for users not logged in or when a page is not found. It displays a background image with an option for user login or a not found message. This component is the initial view for users accessing the platform, guiding them to login for further interaction. + * + * @param {Object} props - Component props. + * @param {boolean} props.notFound - Indicates whether the splash screen is shown as a result of a 404 not found error. + * @returns {JSX.Element} A layout with a background image on one side and a login component or not found message on the other. + * + * The splash screen is designed to be responsive and visually appealing, setting the tone of the application. It utilizes the Material UI Box and Typography components for layout and text display. The choice between showing a login form and a not found message is determined by the `notFound` prop. This component plays a crucial role in user experience by providing a clear entry point for authentication or notifying users when a requested page is unavailable. + */ export const Splash = ({ notFound }) => { // TODO #302 add content to splash screen const theme = useTheme(); @@ -57,11 +66,21 @@ export const Splash = ({ notFound }) => { ); }; +/** + * Home serves as the main entry point for users, directing them to either manage their cases if logged in or to the splash screen if not. It checks the authentication status and dynamically renders the appropriate component based on the user's login state. + * + * @returns {JSX.Element} Either the ManageCases component for logged-in users or the Splash component for guests. + * + * This component uses the `useLoginToken` hook to determine if a user is authenticated. If a token is present, indicating the user is logged in, the ManageCases component is rendered, allowing the user to interact with their assurance cases. If no token is found, the Splash component is displayed, prompting the user to log in or indicating that the page is not found. This component orchestrates the primary user flow of the application based on authentication status. + */ export const Home = () => { const [isLoggedIn, setIsLoggedIn] = useState(false); const [token] = useLoginToken(); + /** + * Update the login status when the token changes. + */ useEffect(() => { setIsLoggedIn(token != null); }, [token]); diff --git a/frontend/src/components/ItemEditor.js b/frontend/src/components/ItemEditor.js index 50e5b907..4d703bf6 100644 --- a/frontend/src/components/ItemEditor.js +++ b/frontend/src/components/ItemEditor.js @@ -14,6 +14,12 @@ import TextInput from "./common/TextInput.jsx"; import SelectInput from "./common/SelectInput.jsx"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * niceNameforType returns a human-readable name for the given type. + * + * @param {string} type - The type of the item. + * @returns {string} A human-readable name for the given type. + */ function niceNameforType(type) { switch (type) { case "TopLevelNormativeGoal": @@ -30,6 +36,21 @@ function niceNameforType(type) { } } +/** + * AddItemButton provides a button interface for adding a new item (Goal, Context, Claim, or Evidence) as a child to a specified parent within an assurance case. It creates a new item of the specified type and links it to the parent item, then refreshes the view and selects the newly created item. + * + * @param {Object} props - Component props. + * @param {string} props.childType - The type of item to be created. + * @param {string} props.parentId - The ID of the parent item to which the new item will be linked. + * @param {string} props.parentType - The type of the parent item. + * @param {Function} props.onRefresh - Function to refresh the view after the item is added. + * @param {Function} props.setErrors - Function to display errors. + * @param {Function} props.getIdForNewElement - Function to generate a new ID for the item being created. + * @param {Function} props.setSelected - Function to set the newly created item as selected in the UI. + * @returns {JSX.Element} A button that triggers the creation of a new item when clicked. + * + * This component simplifies the process of adding new items to the assurance case by handling the API call and subsequent UI updates. + */ function AddItemButton({ childType, parentId, @@ -41,6 +62,11 @@ function AddItemButton({ }) { const [token] = useLoginToken(); + /** + * Handle the click event for adding a new item. + * + * @returns {void} + */ const onClick = useCallback(() => { createItem( token, @@ -80,6 +106,21 @@ function AddItemButton({ ); } +/** + * PropertyField allows editing of a single property (field) of an item within an assurance case. It supports text input and updates the backend upon change. It is used for editing properties like name, description, and URL of items. + * + * @param {Object} props - Component props. + * @param {string} props.id - The ID of the item being edited. + * @param {string} props.type - The type of the item being edited. + * @param {Object} props.item - The current state of the item being edited. + * @param {string} props.fieldName - The name of the field in the item to be edited. + * @param {Function} props.onRefresh - Function to refresh the parent view upon successful edit. + * @param {boolean} props.mermaidFocus - Indicates if the field is focused in the Mermaid diagram. + * @param {Object} [props...props] - Additional props passed to the TextInput component. + * @returns {JSX.Element} A text input field for editing a property of an item. + * + * This component abstracts the input field logic for editing item properties, handling validation, and API update calls. + */ function PropertyField({ id, type, @@ -93,6 +134,13 @@ function PropertyField({ const [token] = useLoginToken(); + /** + * Handle the change event for the input field. + * + * @param {string} value - The new value of the input field. + * @returns {void} + * @throws {Error} If the field cannot be changed. + */ const setValue = useCallback( (value) => { if (item[fieldName] !== value) { @@ -133,6 +181,23 @@ function PropertyField({ ); } +/** + * PropertySelect provides a dropdown selection interface for changing a specific property of an item within an assurance case. It is used for fields where a selection from predefined options is required, like the claim type of a PropertyClaim. + * + * @param {Object} props - Component props. + * @param {string} props.label - The label for the select field. + * @param {string} props.id - The ID of the item being edited. + * @param {string} props.type - The type of the item being edited. + * @param {Object} props.item - The current state of the item being edited. + * @param {Function} props.setItem - Function to set the updated item state. + * @param {string} props.fieldName - The name of the field in the item to be edited. + * @param {Function} props.onRefresh - Function to refresh the parent view upon successful edit. + * @param {Array} props.options - The options for the dropdown. + * @param {Object} [props...props] - Additional props passed to the SelectInput component. + * @returns {JSX.Element} A dropdown select field for editing a specific property of an item. + * + * This component simplifies the process of selecting from predefined options for a specific item property, handling the update logic and UI feedback. + */ function PropertySelect({ label, id, @@ -148,6 +213,13 @@ function PropertySelect({ const [token] = useLoginToken(); + /** + * Handle the change event for the select field. + * + * @param {string} value - The new value of the select field. + * @returns {void} + * @throws {Error} If the field cannot be changed. + */ const setValue = useCallback( (value) => { if (item[fieldName] !== value) { @@ -191,6 +263,23 @@ function PropertySelect({ ); } +/** + * ItemEditor provides an interface for editing the details of a specific item within an assurance case, such as goals, contexts, claims, or evidence. It allows for editing textual properties, linking to parents, and deleting the item. + * + * @param {Object} props - Component props. + * @param {string} props.caseId - The ID of the assurance case to which the item belongs. + * @param {string} props.id - The ID of the item being edited. + * @param {string} props.type - The type of the item being edited. + * @param {Function} props.onRefresh - Function to refresh the view after editing. + * @param {Function} props.onHide - Function to hide the editor. + * @param {Function} props.getIdForNewElement - Function to generate a new ID for linking items. + * @param {Function} props.setSelected - Function to select an item in the UI. + * @param {boolean} props.graphUpdate - Flag indicating if the graph should be updated. + * @param {boolean} props.mermaidFocus - Indicates if the item is focused in the Mermaid diagram. + * @returns {JSX.Element} An interface for editing an item's details. + * + * This component encapsulates the functionality required for editing items within an assurance case, providing fields for editing, options for linking to other items, and actions for deleting the item. + */ function ItemEditor({ caseId, id, @@ -213,6 +302,12 @@ function ItemEditor({ const [token] = useLoginToken(); + /** + * Fetch the item from the server. + * + * @returns {void} + * @throws {Error} If ... + */ const updateItem = useCallback(() => { if (token) { setLoading(true); @@ -249,9 +344,11 @@ function ItemEditor({ } }, [token, id, type]); - // Fetch item when selected node in graph changes, but **not** for every graph update. - // Graph updates occur on an interval, and updating the item on this interval re-renders the - // ItemEditor. + /** + * Fetch the item when the selected node in the graph changes, but **not** for every graph update. Graph updates occur on an interval, and updating the item on this interval re-renders the ItemEditor. + * + * @returns {void} + */ useEffect(() => { updateItem(); }, [token, id, type]); @@ -269,6 +366,11 @@ function ItemEditor({ onHide(); }, [onRefresh, onHide]); + /** + * Add a parent to the item. + * + * @returns {void} + */ const submitAddParent = useCallback(() => { if (!parentToAdd) { return; @@ -301,6 +403,11 @@ function ItemEditor({ } }, [parentToAdd, item, token, id, type, onRefresh]); + /** + * Remove a parent from the item. + * + * @returns {void} + */ const submitRemoveParent = useCallback(() => { if (!parentToRemove) { return; diff --git a/frontend/src/components/Login.js b/frontend/src/components/Login.js index ffe2b57e..4333e8ba 100644 --- a/frontend/src/components/Login.js +++ b/frontend/src/components/Login.js @@ -10,6 +10,11 @@ import { useEnforceLogout, useLoginToken } from "../hooks/useAuth.js"; import { Link } from "react-router-dom"; import ErrorMessage from "./common/ErrorMessage.jsx"; +/** + * Login is a form component used for authenticating users within the TEA Platform. It provides a simple interface for entering a username and password and submitting them to the server. Upon successful submission, the form sets a token in local storage and redirects the user to the home page. + * + * @returns {JSX.Element} A form that allows users to input their username and password and log in. + */ const Login = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); diff --git a/frontend/src/components/Logout.js b/frontend/src/components/Logout.js index 6bedc4f4..cbc8251b 100644 --- a/frontend/src/components/Logout.js +++ b/frontend/src/components/Logout.js @@ -6,11 +6,22 @@ import { ColumnFlow, ModalLikeLayout, RowFlow } from "./common/Layout"; import { Button, Typography } from "@mui/material"; import { useEnforceLogin, useLoginToken } from "../hooks/useAuth.js"; +/** + * Logout is a form component used for logging out of the TEA Platform. It provides a simple interface for logging out and redirects to the login page upon successful logout. + * + * @returns {JSX.Element} A form that allows users to log out of the platform. + */ const Logout = () => { const { sessionExpired } = useParams(); useEnforceLogin(); const [token, setToken] = useLoginToken(); + /** + * Handle logout request. + * + * @param {Event} e - The event object. + * @returns {void} + */ const handleLogout = useCallback( (e) => { e.preventDefault(); diff --git a/frontend/src/components/ManageCases.jsx b/frontend/src/components/ManageCases.jsx index b37576ba..3324e9b9 100644 --- a/frontend/src/components/ManageCases.jsx +++ b/frontend/src/components/ManageCases.jsx @@ -26,6 +26,12 @@ import DeleteCaseModal from "./DeleteCaseModal"; import ErrorMessage from "./common/ErrorMessage"; import { CaseMediaPreview } from "./CaseMediaPreview"; +/** + * ThemedCard is a wrapper component that standardizes the appearance of cards in the ManageCases view. It applies a theme-based styling to ensure consistency across different parts of the application. + * + * @param {Object} props - Component props including standard Card props and additional style overrides. + * @returns {JSX.Element} A Card component with applied theme styles. + */ const ThemedCard = ({ sx, ...props }) => { return ( { ); }; +/** + * CreateCard provides a UI element for initiating the creation of a new assurance case. It displays a card styled according to the application theme, with an action area that, when clicked, opens the case creation modal. + * + * @param {Function} onCreateClick - Callback function to be called when the card is clicked. + * @returns {JSX.Element} A card that triggers the case creation process when clicked. + */ const CreateCard = ({ onCreateClick }) => { const theme = useTheme(); @@ -72,12 +84,22 @@ const CreateCard = ({ onCreateClick }) => { ); }; +/** + * formatter is an instance of Intl.DateTimeFormat that formats dates in the "short" format, including the month, day, and year. + */ const formatter = new Intl.DateTimeFormat(undefined, { month: "short", day: "2-digit", year: "numeric", }); +/** + * CaseCard displays a single assurance case in card format within the ManageCases view. It includes a preview of the case, the case name, description, and action buttons for case operations like edit, delete, and more. + * + * @param {Object} caseObj - An object containing the details of the assurance case to display. + * @param {Function} reload - A function to reload the list of cases, called after certain operations like delete. + * @returns {JSX.Element} A card representing an assurance case with actionable items. + */ const CaseCard = ({ caseObj, reload }) => { const theme = useTheme(); @@ -241,6 +263,11 @@ const CaseCard = ({ caseObj, reload }) => { ); }; +/** + * LoadingCard displays a placeholder card with a loading indicator. It is used in the ManageCases view while assurance case data is being loaded from the backend. + * + * @returns {JSX.Element} A card with a loading spinner, indicating data is being loaded. + */ export const LoadingCard = () => { return ( @@ -249,6 +276,13 @@ export const LoadingCard = () => { ); }; +/** + * ManageCases is the main component for managing assurance cases. It displays a list of assurance cases each represented by a CaseCard, and provides options to create a new case or import cases from files. + * + * @returns {JSX.Element} A layout including a list of assurance cases, and options for creating or importing cases. + * + * This component integrates several functionalities including case creation, import, and display. It fetches and displays assurance cases from the backend, handling loading states and errors. It also provides entry points for case creation and import through modal dialogs. + */ const ManageCases = () => { const [isLoading, setIsLoading] = useState(true); const [cases, setCases] = useState([]); @@ -259,6 +293,12 @@ const ManageCases = () => { useEnforceLogin(); const [token] = useLoginToken(); + /** + * doLoad is a memoized callback function that fetches assurance cases from the backend and updates the component state with the loaded data. It handles loading states and errors, and is called when the component mounts or when the token changes. + * + * @returns {Function} A memoized callback function to fetch assurance cases from the backend and update the component state. + * @throws {Error} If the fetch process fails with a 401 status code. + */ const doLoad = useCallback(() => { let isMounted = true; @@ -278,10 +318,11 @@ const ManageCases = () => { case 401: unauthorized(); break; + // TODO: add default case? } }, - (reason) => { - console.log(reason); + (err) => { + console.log(err); setError("Something went wrong. Please try again later."); } ) @@ -312,7 +353,9 @@ const ManageCases = () => { }; }, [token]); - // initial load + /** + * Load the assurance cases from the backend when the component mounts. + */ useEffect(() => { doLoad(); }, [doLoad]); diff --git a/frontend/src/components/Mermaid.js b/frontend/src/components/Mermaid.js index 7455dede..a0cdf43e 100644 --- a/frontend/src/components/Mermaid.js +++ b/frontend/src/components/Mermaid.js @@ -3,6 +3,18 @@ import mermaid from "mermaid"; import "./Mermaid.scss"; import { jsonToMermaid } from "./utils"; +/** + * MermaidChart is a component for rendering assurance case diagrams using the Mermaid library. It takes a JSON representation of an assurance case and renders it as a flowchart. + * + * @param {Object} props - Component props. + * @param {string} props.caseId - The ID of the assurance case to render. + * @param {Object} props.assuranceCase - The JSON representation of the assurance case to render. + * @param {string} props.selectedId - The ID of the currently selected node in the assurance case. + * @param {string} props.selectedType - The type of the currently selected node in the assurance case. + * @param {Function} props.setSelected - A function to set the currently selected node in the assurance case. + * @param {Function} props.setMermaidFocus - A function to set the focus state of the Mermaid chart. + * @returns {JSX.Element} A Mermaid chart component. + */ function MermaidChart({ caseId, assuranceCase, @@ -13,6 +25,12 @@ function MermaidChart({ }) { const [collapsedNodes, setCollapsedNodes] = useState([]); + /** + * Convert the assurance case to a Mermaid markdown representation. + * + * @type {string} + * @returns {string} The Mermaid markdown representation of the assurance case. + */ const chartmd = useMemo(() => { return jsonToMermaid( assuranceCase, @@ -22,12 +40,20 @@ function MermaidChart({ ); }, [assuranceCase, selectedType, selectedId, collapsedNodes]); - // refresh state + /** + * Refresh the state of the collapsed nodes when the assurance case changes. + * + * @returns {void} + */ useEffect(() => { setCollapsedNodes([]); }, [caseId]); - // initialise mermaid + /** + * Initialize the Mermaid library. + * + * @returns {void} + */ useEffect(() => { mermaid.initialize({ theme: "base", @@ -47,7 +73,9 @@ function MermaidChart({ }); }, []); - // set click callback + /** + * Set the selected node when a node is clicked in the Mermaid chart. + */ useEffect(() => { window.callback = (e) => { setMermaidFocus((tog) => !tog); @@ -60,28 +88,37 @@ function MermaidChart({ }; }, [setSelected, setMermaidFocus]); - const onCollapseButtonClick = useCallback( - /** @param {MouseEvent} e */ (e) => { - const nodeKey = e.target?.dataset?.key; - if (nodeKey == null) { - return; - } + /** + * Handle the collapse/expand button click event. + * + * @param {MouseEvent} e - The click event. + * @returns {void} + */ + const onCollapseButtonClick = useCallback((e) => { + const nodeKey = e.target?.dataset?.key; + if (nodeKey == null) { + return; + } - setCollapsedNodes((collapsedNodes) => { - let newArray = collapsedNodes.filter((k) => k !== nodeKey); - if (newArray.length === collapsedNodes.length) { - newArray.push(nodeKey); - } - return newArray; - }); + setCollapsedNodes((collapsedNodes) => { + let newArray = collapsedNodes.filter((k) => k !== nodeKey); + if (newArray.length === collapsedNodes.length) { + newArray.push(nodeKey); + } + return newArray; + }); - // don't fire click event on node itself - e.stopPropagation(); - }, - [], - ); + // don't fire click event on node itself + e.stopPropagation(); + }, []); - // trigger mermaid reload + /** + * Trigger a re-render of the Mermaid chart when the markdown content changes. + * + * @returns {void} + * @throws {Error} If the Mermaid div is not found. + * @throws {Error} If there is an error rendering the Mermaid chart. + */ useEffect(() => { try { const mermaidDiv = document.querySelector(`.mermaid-${caseId}`); diff --git a/frontend/src/components/Navigation.js b/frontend/src/components/Navigation.js index 366713d4..f0121bc2 100644 --- a/frontend/src/components/Navigation.js +++ b/frontend/src/components/Navigation.js @@ -6,6 +6,12 @@ import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import { useLoginToken } from "../hooks/useAuth"; +/** + * NavButton is a customized Button component designed to fit within the application's navigation bar. It applies specific styling to maintain visual consistency across all navigational buttons. + * + * @param {Object} props - Standard Button props with additional style overrides if needed. + * @returns {JSX.Element} A Button component styled for the application's navigation bar. + */ function NavButton({ sx, ...props }) { return (