1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-24 17:51:14 +02:00

add basic table structure

This commit is contained in:
Thomas Heartman 2025-09-10 13:22:51 +02:00
parent bb60356b4f
commit 244f103abd
No known key found for this signature in database
GPG Key ID: BD1F880DAED1EE78
2 changed files with 330 additions and 5 deletions

View File

@ -1,14 +1,270 @@
import { useMemo } from 'react';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; 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 = () => { 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<string>,
) => {
return row.features.find((feature) =>
values
.map((value) => value.toLowerCase())
.includes(feature.name.toLowerCase()),
);
},
Cell: ({
value,
row: {
original: { title, project },
},
}: any) => (
<FeaturesCell project={project} value={value} key={title} />
),
},
{
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 ( return (
<PageContent <PageContent
header={ isLoading={loading}
<PageHeader title="Change Requests" /> header={<PageHeader title='Change requests' />}
}
> >
<p>Change requests table will go here</p> <Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} />
<TableBody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
const { key, ...rowProps } = row.getRowProps();
return (
<TableRow hover key={key} {...rowProps}>
{row.cells.map((cell) => {
const { key, ...cellProps } =
cell.getCellProps();
return (
<TableCell key={key} {...cellProps}>
{cell.render('Cell')}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</PageContent> </PageContent>
); );
}; };

View File

@ -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) => (
<Link component={RouterLink} {...props}>
{children}
</Link>
))(({ 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 <TextCell />;
}
return (
<TextCell sx={{ minWidth: '300px' }}>
<LinkContainer>
<BaseLink to={projectPath}>{projectName}</BaseLink>
<span aria-hidden='true'> / </span>
<ChangeRequestLink to={crPath}>{title}</ChangeRequestLink>
</LinkContainer>
<UpdateText>
{`${totalChanges}`} {totalChanges === 1 ? `update` : 'updates'}
</UpdateText>
</TextCell>
);
};