From d77ffef62d9d499e4251a82349f3441975603bbc Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 19 Mar 2025 16:37:19 -0500 Subject: [PATCH 01/18] ui/dev: Refactor IntegrationKeyList and UniversalKeyDefaultActions to use CompList and enhance mobile responsiveness --- web/src/app/lists/CompList.tsx | 9 ++- web/src/app/services/IntegrationKeyList.tsx | 76 ++++++++++--------- .../UniversalKeyDefaultActions.tsx | 30 ++++---- 3 files changed, 62 insertions(+), 53 deletions(-) diff --git a/web/src/app/lists/CompList.tsx b/web/src/app/lists/CompList.tsx index 6a5dadc9fa..152393658e 100644 --- a/web/src/app/lists/CompList.tsx +++ b/web/src/app/lists/CompList.tsx @@ -2,10 +2,14 @@ import { List, ListItem, ListItemText, Typography } from '@mui/material' import React from 'react' import ReorderGroup from './ReorderGroup' import { has } from 'lodash' +import { useIsWidthDown } from '../util/useWidth' export type CompListProps = { note?: React.ReactNode action?: React.ReactNode + + /* If true, the list will only show the action on wide screens. */ + hideActionOnMobile?: boolean emptyMessage?: string children?: React.ReactNode @@ -25,6 +29,7 @@ function isReorderGroup(child: React.ReactNode): child is React.ReactElement { export default function CompList(props: CompListProps): React.ReactNode { const children = React.Children.toArray(props.children) let hasNoChildren = children.length === 0 + const isMobile = useIsWidthDown('md') // Special case: ReorderGroup with no contents/children as a child if ( @@ -52,7 +57,9 @@ export default function CompList(props: CompListProps): React.ReactNode { sx={{ fontStyle: 'italic', pr: 2 }} /> )} - {props.action &&
{props.action}
} + {props.action && (props.hideActionOnMobile ? !isMobile : true) && ( +
{props.action}
+ )} )} {!hasNoChildren diff --git a/web/src/app/services/IntegrationKeyList.tsx b/web/src/app/services/IntegrationKeyList.tsx index 3f15766afb..107953c337 100644 --- a/web/src/app/services/IntegrationKeyList.tsx +++ b/web/src/app/services/IntegrationKeyList.tsx @@ -5,7 +5,6 @@ import Grid from '@mui/material/Grid' import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' import CreateFAB from '../lists/CreateFAB' -import FlatList, { FlatListListItem } from '../lists/FlatList' import IconButton from '@mui/material/IconButton' import { Trash } from '../icons' import IntegrationKeyCreateDialog from './IntegrationKeyCreateDialog' @@ -27,6 +26,8 @@ import { Typography, } from '@mui/material' import { ChevronDown } from 'mdi-material-ui' +import CompList from '../lists/CompList' +import { CompListItemText } from '../lists/CompListItems' const query = gql` query ($serviceID: ID!) { @@ -117,18 +118,19 @@ export default function IntegrationKeyList(props: { .slice() .sort(sortItems) .filter((key: IntegrationKey) => !key.externalSystemName) - .map( - (key: IntegrationKey): FlatListListItem => ({ - title: key.name, - subText: ( + .map((key: IntegrationKey) => ( + - ), - secondaryAction: ( + } + action={ {key.type === 'universal' && ( @@ -153,19 +155,20 @@ export default function IntegrationKeyList(props: { - ), - }), - ) + } + /> + )) const extItems = (data.service.integrationKeys || []) .slice() .sort(sortItems) .filter((key: IntegrationKey) => !!key.externalSystemName) - .map( - (key: IntegrationKey): FlatListListItem => ({ - title: key.name, - subText: , - secondaryAction: ( + .map((key: IntegrationKey) => ( + } + action={ setDeleteDialog(key.id)} size='large' @@ -173,38 +176,38 @@ export default function IntegrationKeyList(props: { > - ), - }), - ) + } + /> + )) return ( - API Documentation is available{' '} here. } emptyMessage='No integration keys exist for this service.' - items={items} - headerAction={ - isMobile ? undefined : ( - - ) + hideActionOnMobile + action={ + } - /> + > + {items} + {!!extItems.length && ( Externally Managed Keys - + + {extItems} + )} diff --git a/web/src/app/services/UniversalKey/UniversalKeyDefaultActions.tsx b/web/src/app/services/UniversalKey/UniversalKeyDefaultActions.tsx index 6af0870d0e..b47dafb550 100644 --- a/web/src/app/services/UniversalKey/UniversalKeyDefaultActions.tsx +++ b/web/src/app/services/UniversalKey/UniversalKeyDefaultActions.tsx @@ -1,11 +1,12 @@ import { Button, Card } from '@mui/material' import React, { Suspense, useState } from 'react' -import FlatList from '../../lists/FlatList' import UniversalKeyActionsList from './UniversalKeyActionsList' import { gql, useQuery } from 'urql' import { IntegrationKey } from '../../../schema' import { Add } from '../../icons' import { UniversalKeyActionDialog } from './UniversalKeyActionDialog' +import CompList from '../../lists/CompList' +import { CompListItemText } from '../../lists/CompListItems' interface UniversalKeyDefaultActionProps { serviceID: string @@ -42,9 +43,9 @@ export default function UniversalKeyDefaultActions( return ( - } @@ -53,18 +54,17 @@ export default function UniversalKeyDefaultActions( Add Action } - headerNote='Default actions are performed when zero rules match.' - items={[ - { - title: ( - setEditActionIndex(index)} - /> - ), - }, - ]} - /> + note='Default actions are performed when zero rules match.' + > + setEditActionIndex(index)} + /> + } + /> + {editActionIndex > -1 && ( From ea054c8c7abec12c563ebea013b9c340563a130c Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Thu, 20 Mar 2025 11:21:49 -0500 Subject: [PATCH 02/18] ui/dev: Refactor HeartbeatMonitorList to use CompList --- web/src/app/services/HeartbeatMonitorList.tsx | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/web/src/app/services/HeartbeatMonitorList.tsx b/web/src/app/services/HeartbeatMonitorList.tsx index cfa3821c71..7b98a171d2 100644 --- a/web/src/app/services/HeartbeatMonitorList.tsx +++ b/web/src/app/services/HeartbeatMonitorList.tsx @@ -5,7 +5,6 @@ import Grid from '@mui/material/Grid' import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' import CreateFAB from '../lists/CreateFAB' -import FlatList from '../lists/FlatList' import HeartbeatMonitorCreateDialog from './HeartbeatMonitorCreateDialog' import makeStyles from '@mui/styles/makeStyles' import HeartbeatMonitorEditDialog from './HeartbeatMonitorEditDialog' @@ -19,6 +18,8 @@ import { HeartbeatMonitor } from '../../schema' import { useIsWidthDown } from '../util/useWidth' import { Add } from '@mui/icons-material' import { Time } from '../util/Time' +import CompList from '../lists/CompList' +import { CompListItemText } from '../lists/CompListItems' // generates a single alert if a POST is not received before the timeout const HEARTBEAT_MONITOR_DESCRIPTION = @@ -84,61 +85,64 @@ export default function HeartbeatMonitorList(props: { const items = (monitors || []) .slice() .sort(sortItems) - .map((monitor) => ({ - icon: ( - - ), - title: monitor.name, - subText: ( - - - ), - secondaryAction: ( - setShowEditDialogByID(monitor.id), - }, - { - label: 'Delete', - onClick: () => setShowDeleteDialogByID(monitor.id), - }, - ]} - /> - ), - })) + } + title={monitor.name} + subText={ + + + } + action={ + setShowEditDialogByID(monitor.id), + }, + { + label: 'Delete', + onClick: () => setShowDeleteDialogByID(monitor.id), + }, + ]} + /> + } + /> + )) return ( - setShowCreateDialog(true)} - startIcon={} - data-testid='create-monitor' - > - Create Heartbeat Monitor - - ) + note={HEARTBEAT_MONITOR_DESCRIPTION} + hideActionOnMobile + action={ + } - /> + > + {items} + ) } From a3df46b682e7d981e9b140b17bebeac3a583037d Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Thu, 20 Mar 2025 11:43:31 -0500 Subject: [PATCH 03/18] ui/dev: Refactor UniversalKeyRuleList to use CompList and ReorderGroup for enhanced rule management --- .../UniversalKey/UniversalKeyRuleConfig.tsx | 153 +++++++++--------- 1 file changed, 81 insertions(+), 72 deletions(-) diff --git a/web/src/app/services/UniversalKey/UniversalKeyRuleConfig.tsx b/web/src/app/services/UniversalKey/UniversalKeyRuleConfig.tsx index 52dd659685..7f6fa63055 100644 --- a/web/src/app/services/UniversalKey/UniversalKeyRuleConfig.tsx +++ b/web/src/app/services/UniversalKey/UniversalKeyRuleConfig.tsx @@ -1,7 +1,6 @@ import React, { Suspense, useState } from 'react' import { Button, Card, Grid, Typography } from '@mui/material' import { Add } from '@mui/icons-material' -import FlatList, { FlatListListItem } from '../../lists/FlatList' import UniversalKeyRuleDialog from './UniversalKeyRuleDialog' import UniversalKeyRuleRemoveDialog from './UniversalKeyRuleRemoveDialog' import OtherActions from '../../util/OtherActions' @@ -11,6 +10,10 @@ import Spinner from '../../loading/components/Spinner' import { GenericError } from '../../error-pages' import UniversalKeyActionsList from './UniversalKeyActionsList' import { UniversalKeyActionDialog } from './UniversalKeyActionDialog' +import CompList from '../../lists/CompList' +import ReorderGroup from '../../lists/ReorderGroup' +import { ReorderableItem } from '../../lists/ReorderableItem' +import { CompListItemText } from '../../lists/CompListItems' interface UniversalKeyRuleListProps { serviceID: string @@ -83,66 +86,68 @@ export default function UniversalKeyRuleList( if (fetching && !data) return if (error) return - const items: FlatListListItem[] = ( - data?.integrationKey.config.rules ?? [] - ).map((rule) => ({ - title: ( - - {rule.name}: -   - {rule.description} - - ), - subText: ( - - - If - - {truncateCond(rule.conditionExpr)} - - - - Then - - - - setEditAction({ ruleID: rule.id, actionIndex: index }) - } + const items = (data?.integrationKey.config.rules ?? []).map((rule) => ( + + + {rule.name}: +   + {rule.description} + + } + subText={ + + + If + + {truncateCond(rule.conditionExpr)} + + + + Then + + + + setEditAction({ ruleID: rule.id, actionIndex: index }) + } + /> + + + Finally {rule.continueAfterMatch ? 'continue' : 'stop'} + + + } + action={ + setAddAction({ ruleID: rule.id }), + }, + { + label: 'Edit Rule', + onClick: () => setEdit(rule.id), + }, + { + label: 'Delete Rule', + onClick: () => setRemove(rule.id), + }, + ]} /> - - - Finally {rule.continueAfterMatch ? 'continue' : 'stop'} - - - ), - secondaryAction: ( - setAddAction({ ruleID: rule.id }), - }, - { - label: 'Edit Rule', - onClick: () => setEdit(rule.id), - }, - { - label: 'Delete Rule', - onClick: () => setRemove(rule.id), - }, - ]} + } /> - ), - })) + + )) return ( - } @@ -151,25 +156,29 @@ export default function UniversalKeyRuleList( Create Rule } - headerNote='Rules are a set of filters that allow notifications to be sent to a specific destination. ' - items={items} - onReorder={(oldIdx, newIdx) => { - const ruleIDs = - data?.integrationKey.config.rules.map((r) => r.id) || [] - const tmp = ruleIDs[oldIdx] - ruleIDs[oldIdx] = ruleIDs[newIdx] - ruleIDs[newIdx] = tmp - commit( - { - input: { - keyID: props.keyID, - setRuleOrder: ruleIDs, + note='Rules are a set of filters that allow notifications to be sent to a specific destination. ' + > + { + const ruleIDs = + data?.integrationKey.config.rules.map((r) => r.id) || [] + const tmp = ruleIDs[oldIdx] + ruleIDs[oldIdx] = ruleIDs[newIdx] + ruleIDs[newIdx] = tmp + commit( + { + input: { + keyID: props.keyID, + setRuleOrder: ruleIDs, + }, }, - }, - { additionalTypenames: ['IntegrationKey'] }, - ) - }} - /> + { additionalTypenames: ['IntegrationKey'] }, + ) + }} + > + {items} + + From 3ac894441b9c09cb9cf9ac77d0268bcef0784c8c Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Thu, 20 Mar 2025 13:41:40 -0500 Subject: [PATCH 04/18] ui/dev: Update CompListItemText to make title prop optional and enhance icon handling --- web/src/app/lists/CompListItems.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/src/app/lists/CompListItems.tsx b/web/src/app/lists/CompListItems.tsx index fb3ea30e46..080c4eb28f 100644 --- a/web/src/app/lists/CompListItems.tsx +++ b/web/src/app/lists/CompListItems.tsx @@ -12,8 +12,11 @@ import AppLink from '../util/AppLink' import { ExpandLess, ExpandMore } from '@mui/icons-material' export type CompListItemTextProps = { - title: React.ReactNode + title?: React.ReactNode icon?: React.ReactNode + /* Set to true to always create space for the icon, even if it is not set */ + alwaysShowIcon?: boolean + subText?: React.ReactNode action?: React.ReactNode disableTypography?: boolean @@ -25,7 +28,9 @@ export function CompListItemText( ): React.ReactNode { return ( - {props.icon && {props.icon}} + {(props.icon || props.alwaysShowIcon) && ( + {props.icon} + )} Date: Thu, 20 Mar 2025 13:41:54 -0500 Subject: [PATCH 05/18] ui/dev: Refactor UserCalendarSubscriptionList to use CompList and CompListItem components --- .../users/UserCalendarSubscriptionList.tsx | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/web/src/app/users/UserCalendarSubscriptionList.tsx b/web/src/app/users/UserCalendarSubscriptionList.tsx index 1e1b2ad5c9..ca6ceaad1d 100644 --- a/web/src/app/users/UserCalendarSubscriptionList.tsx +++ b/web/src/app/users/UserCalendarSubscriptionList.tsx @@ -1,7 +1,6 @@ import React, { Suspense, useState } from 'react' import { useQuery, gql } from 'urql' import { Card, Alert } from '@mui/material' -import FlatList, { FlatListListItem } from '../lists/FlatList' import OtherActions from '../util/OtherActions' import CreateFAB from '../lists/CreateFAB' import CalendarSubscribeCreateDialog from '../schedules/calendar-subscribe/CalendarSubscribeCreateDialog' @@ -11,9 +10,10 @@ import CalendarSubscribeEditDialog from '../schedules/calendar-subscribe/Calenda import { GenericError, ObjectNotFound } from '../error-pages' import _ from 'lodash' import { useConfigValue } from '../util/RequireConfig' -import AppLink from '../util/AppLink' import { UserCalendarSubscription } from '../../schema' import { Time } from '../util/Time' +import CompList from '../lists/CompList' +import { CompListItemNav, CompListItemText } from '../lists/CompListItems' export const calendarSubscriptionsQuery = gql` query calendarSubscriptions($id: ID!) { @@ -71,7 +71,7 @@ export default function UserCalendarSubscriptionList(props: { }) const subheaderDict: { [key: string]: boolean } = {} - const items: FlatListListItem[] = [] + const items: React.ReactNode[] = [] function renderOtherActions(id: string): JSX.Element { return ( @@ -94,29 +94,32 @@ export default function UserCalendarSubscriptionList(props: { subs.forEach((sub: UserCalendarSubscription) => { if (!subheaderDict[sub?.schedule?.name ?? '']) { subheaderDict[sub?.schedule?.name ?? ''] = true - items.push({ - subHeader: ( - - {sub.schedule?.name} - - ), - }) + items.push( + , + ) } // push subscriptions under relevant schedule subheaders - items.push({ - title: sub.name, - subText: ( -