From 244f103abdf9ed98ce02e3d66debcaff0ae39e90 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 10 Sep 2025 13:22:51 +0200 Subject: [PATCH] add basic table structure --- .../ChangeRequests/ChangeRequests.tsx | 266 +++++++++++++++++- .../GlobalChangeRequestTitleCell.tsx | 69 +++++ 2 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 frontend/src/component/changeRequest/ChangeRequests/GlobalChangeRequestTitleCell.tsx diff --git a/frontend/src/component/changeRequest/ChangeRequests/ChangeRequests.tsx b/frontend/src/component/changeRequest/ChangeRequests/ChangeRequests.tsx index 29d64a8ec6..353241646f 100644 --- a/frontend/src/component/changeRequest/ChangeRequests/ChangeRequests.tsx +++ b/frontend/src/component/changeRequest/ChangeRequests/ChangeRequests.tsx @@ -1,14 +1,270 @@ +import { useMemo } from 'react'; import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import { + SortableTableHeader, + Table, + TableBody, + TableCell, + TableRow, +} from 'component/common/Table'; +import { useSortBy, useTable } from 'react-table'; +import { sortTypes } from 'utils/sortTypes'; +import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; +import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; +import { ChangeRequestStatusCell } from 'component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/ChangeRequestStatusCell'; +import { AvatarCell } from 'component/changeRequest/ProjectChangeRequests/ChangeRequestsTabs/AvatarCell'; +import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; +import { GlobalChangeRequestTitleCell } from './GlobalChangeRequestTitleCell.js'; +import { FeaturesCell } from '../ProjectChangeRequests/ChangeRequestsTabs/FeaturesCell.js'; + +// Mock data based on the image +const mockChangeRequests = [ + { + id: 24, + title: 'change request #024', + project: 'auth-application', + projectName: 'Auth application', + features: [{ name: 'hubspotDebugLogging' }], + segments: [], + createdBy: { username: 'bruce', name: 'Bruce Wayne', imageUrl: null }, + createdAt: '2024-01-10T10:22:00Z', + environment: 'Production', + state: 'Review required', + }, + { + id: 24, + title: 'change request #024', + project: 'auth-application', + projectName: 'Auth application', + features: [{ name: 'strictSchemaValidation' }], + segments: [], + createdBy: { username: 'bruce', name: 'Bruce Wayne', imageUrl: null }, + createdAt: '2024-01-10T10:22:00Z', + environment: 'Production', + state: 'Approved', + }, + { + id: 24, + title: 'change request #024', + project: 'developer-experience', + projectName: 'Developer experience', + features: [{ name: 'debugFlag' }], + segments: [], + createdBy: { username: 'bruce', name: 'Bruce Wayne', imageUrl: null }, + createdAt: '2024-01-10T10:22:00Z', + environment: 'Production', + state: 'Review required', + }, + { + id: 24, + title: 'change request #024', + project: 'auth-application', + projectName: 'Auth application', + features: [{ name: 'consumptionSync' }], + segments: [], + createdBy: { username: 'bruce', name: 'Bruce Wayne', imageUrl: null }, + createdAt: '2024-01-10T10:22:00Z', + environment: 'Sandbox', + state: 'Review required', + }, + { + id: 24, + title: 'change request #024', + project: 'platform', + projectName: 'Platform', + features: [{ name: 'etagByEnv' }], + segments: [], + createdBy: { username: 'bruce', name: 'Bruce Wayne', imageUrl: null }, + createdAt: '2024-01-10T10:22:00Z', + environment: 'Sandbox', + state: 'Scheduled', + schedule: { + scheduledAt: '2024-01-12T09:46:51+05:30', + status: 'pending', + }, + }, + { + id: 24, + title: 'change request #024', + project: 'unleash-cloud', + projectName: 'Unleash Cloud', + features: [{ name: 'fetchMode' }], + segments: [], + createdBy: { username: 'jane', name: 'Jane Smith', imageUrl: null }, + createdAt: '2024-01-09T14:30:00Z', + environment: 'Testing', + state: 'Approved', + }, + { + id: 24, + title: 'change request #024', + project: 'unleash-cloud', + projectName: 'Unleash Cloud', + features: [{ name: 'consumptionModelUI' }], + segments: [], + createdBy: { username: 'jane', name: 'Jane Smith', imageUrl: null }, + createdAt: '2024-01-09T14:30:00Z', + environment: 'Testing', + state: 'Approved', + }, + { + id: 24, + title: 'change request #024', + project: 'enterprise-frontend', + projectName: 'Enterprise Front End', + features: [{ name: 'sso-provider-error-details' }], + segments: [], + createdBy: { username: 'bruce', name: 'Bruce Wayne', imageUrl: null }, + createdAt: '2024-01-10T10:22:00Z', + environment: 'Production', + state: 'Approved', + }, + { + id: 24, + title: 'change request #024', + project: 'auth-application', + projectName: 'Auth application', + features: [{ name: 'filterFlagsToArchive' }], + segments: [], + createdBy: { username: 'bruce', name: 'Bruce Wayne', imageUrl: null }, + createdAt: '2024-01-10T10:22:00Z', + environment: 'Production', + state: 'Approved', + }, +]; export const ChangeRequests = () => { + const loading = false; + const columns = useMemo( + () => [ + { + id: 'Title', + Header: 'Title', + // todo (globalChangeRequestList): sort out width calculation. It's configured both here with a min width down in the inner cell? + width: 300, + canSort: true, + accessor: 'title', + Cell: GlobalChangeRequestTitleCell, + }, + { + id: 'Updated feature flags', + Header: 'Updated feature flags', + canSort: false, + accessor: 'features', + searchable: true, + filterName: 'feature', + filterParsing: (values: Array<{ name: string }>) => { + return values?.map(({ name }) => name).join('\n') || ''; + }, + filterBy: ( + row: { features: Array<{ name: string }> }, + values: Array, + ) => { + return row.features.find((feature) => + values + .map((value) => value.toLowerCase()) + .includes(feature.name.toLowerCase()), + ); + }, + Cell: ({ + value, + row: { + original: { title, project }, + }, + }: any) => ( + + ), + }, + { + Header: 'By', + accessor: 'createdBy', + maxWidth: 180, + canSort: false, + Cell: AvatarCell, + align: 'left', + searchable: true, + filterName: 'by', + filterParsing: (value: { username?: string }) => + value?.username || '', + }, + { + Header: 'Submitted', + accessor: 'createdAt', + maxWidth: 100, + Cell: TimeAgoCell, + }, + { + Header: 'Environment', + accessor: 'environment', + searchable: true, + maxWidth: 100, + Cell: HighlightCell, + filterName: 'environment', + }, + { + Header: 'Status', + accessor: 'state', + searchable: true, + maxWidth: '170px', + Cell: ChangeRequestStatusCell, + filterName: 'status', + }, + ], + [], + ); + + const { headerGroups, rows, prepareRow, getTableProps, getTableBodyProps } = + useTable( + { + columns: columns as any[], + data: mockChangeRequests, + initialState: { + sortBy: [ + { + id: 'createdAt', + desc: true, + }, + ], + }, + sortTypes, + autoResetHiddenColumns: false, + disableSortRemove: true, + autoResetSortBy: false, + defaultColumn: { + Cell: TextCell, + }, + }, + useSortBy, + ); + return ( - } + isLoading={loading} + header={} > -

Change requests table will go here

+ + + + {rows.map((row) => { + prepareRow(row); + const { key, ...rowProps } = row.getRowProps(); + return ( + + {row.cells.map((cell) => { + const { key, ...cellProps } = + cell.getCellProps(); + return ( + + {cell.render('Cell')} + + ); + })} + + ); + })} + +
); -}; \ No newline at end of file +}; diff --git a/frontend/src/component/changeRequest/ChangeRequests/GlobalChangeRequestTitleCell.tsx b/frontend/src/component/changeRequest/ChangeRequests/GlobalChangeRequestTitleCell.tsx new file mode 100644 index 0000000000..678c2a0918 --- /dev/null +++ b/frontend/src/component/changeRequest/ChangeRequests/GlobalChangeRequestTitleCell.tsx @@ -0,0 +1,69 @@ +import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; +import { Link, styled, Typography } from '@mui/material'; +import { Link as RouterLink, type LinkProps } from 'react-router-dom'; + +type IGlobalChangeRequestTitleCellProps = { + value?: any; + row: { original: any }; +}; + +const LinkContainer = styled('div')(({ theme }) => ({ + color: theme.palette.text.secondary, +})); + +const BaseLink = styled(({ children, ...props }: LinkProps) => ( + + {children} + +))(({ theme }) => ({ + textDecoration: 'none', + color: 'inherit', + ':hover': { + textDecoration: 'underline', + }, +})); + +const ChangeRequestLink = styled(BaseLink)(({ theme }) => ({ + color: theme.palette.primary.main, + fontWeight: 'bold', +})); + +const UpdateText = styled(Typography)(({ theme }) => ({ + color: theme.palette.text.secondary, + fontSize: theme.typography.body2.fontSize, +})); + +export const GlobalChangeRequestTitleCell = ({ + value, + row: { original }, +}: IGlobalChangeRequestTitleCellProps) => { + const { + id, + title, + project, + projectName, + features: featureChanges, + segments: segmentChanges, + } = original; + const totalChanges = + (featureChanges || []).length + (segmentChanges || []).length; + const projectPath = `/projects/${project}`; + const crPath = `${projectPath}/change-requests/${id}`; + + if (!value) { + return ; + } + + return ( + + + {projectName} + + {title} + + + {`${totalChanges}`} {totalChanges === 1 ? `update` : 'updates'} + + + ); +};