Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(novui,web): V2 flow list #5682

Merged
merged 15 commits into from
Jun 6, 2024
3 changes: 2 additions & 1 deletion apps/web/src/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { TenantsPage } from './pages/tenants/TenantsPage';
import { UpdateTenantPage } from './pages/tenants/UpdateTenantPage';
import { TranslationRoutes } from './pages/TranslationPages';
import { useSettingsRoutes } from './SettingsRoutes';
import { WorkflowsListPage } from './studio/components/workflows/WorkflowsListPage';

export const AppRoutes = () => {
const isImprovedOnboardingEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_IMPROVED_ONBOARDING_ENABLED);
Expand Down Expand Up @@ -140,7 +141,7 @@ export const AppRoutes = () => {
)}
<Route path={ROUTES.STUDIO}>
<Route path="" element={<Navigate to={ROUTES.STUDIO_FLOWS} replace />} />
<Route path={ROUTES.STUDIO_FLOWS} element={<WorkflowListPage />} />
<Route path={ROUTES.STUDIO_FLOWS} element={<WorkflowsListPage />} />
</Route>

<Route path="/translations/*" element={<TranslationRoutes />} />
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/studio/components/workflows/WorkflowsListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SettingsPageContainer } from '../../../pages/settings/SettingsPageContainer';
import { WorkflowsTable } from './table';

export const WorkflowsListPage = () => {
return (
<SettingsPageContainer title="Workflows">
<WorkflowsTable />
</SettingsPageContainer>
);
};
1 change: 1 addition & 0 deletions apps/web/src/studio/components/workflows/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './WorkflowsListPage';
110 changes: 110 additions & 0 deletions apps/web/src/studio/components/workflows/table/WorkflowsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* cspell:disable */

import { createColumnHelper, Table } from '@novu/novui';
import { css } from '@novu/novui/css';
import format from 'date-fns/format';
import { FC } from 'react';
import { WorkflowTableRow } from './WorkflowsTable.types';
import { GroupCell, NameCell, StatusCell } from './WorkflowsTableCellRenderers';

// TODO: remove this test data
const TEST_FLOW = {
_id: 'adaewr',
name: 'test naming',
active: false,
type: 'REGULAR',
draft: true,
critical: false,
isBlueprint: false,
_notificationGroupId: 'dsasdfasdf',
tags: [],
triggers: [
{
type: 'event',
identifier: 'test-naming',
variables: [],
reservedVariables: [],
subscriberVariables: [],
_id: 'sdfsdf',
},
],
steps: [],
preferenceSettings: {
email: true,
sms: true,
in_app: true,
chat: true,
push: true,
},
_environmentId: 'sdfsdf',
_organizationId: 'sdf',
_creatorId: 'sdfsdfsdf',
deleted: false,
createdAt: '2024-06-04T19:11:11.600Z',
updatedAt: '2024-06-05T17:55:39.022Z',
__v: 0,
notificationGroup: {
_id: 'sdfsdf',
name: 'General',
_organizationId: 'sdfsdf',
_environmentId: 'sdfdfsdfsf',
createdAt: '2024-05-17T22:26:08.177Z',
updatedAt: '2024-05-17T22:26:08.177Z',
__v: 0,
},
workflowIntegrationStatus: {
hasActiveIntegrations: true,
channels: {
in_app: {
hasActiveIntegrations: false,
},
email: {
hasActiveIntegrations: true,
hasPrimaryIntegrations: true,
},
sms: {
hasActiveIntegrations: true,
hasPrimaryIntegrations: true,
},
chat: {
hasActiveIntegrations: false,
},
push: {
hasActiveIntegrations: false,
},
},
},
};

interface IWorkflowsTableProps {
temp?: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗒 note (non-blocking): This is a placeholder for other props‏

}

const columnHelper = createColumnHelper<WorkflowTableRow>();

const WORKFLOW_COLUMNS = [
columnHelper.accessor('name', {
header: 'Name & Trigger ID',
cell: NameCell,
}),
columnHelper.accessor('notificationGroup.name', {
header: 'Group',
cell: GroupCell,
}),
columnHelper.accessor('createdAt', {
header: 'Created at',
cell: ({ getValue }) => format(new Date(getValue() ?? ''), 'dd/MM/yyyy HH:mm'),
}),
columnHelper.accessor('active', {
header: 'Status',
cell: StatusCell,
}),
];

export const WorkflowsTable: FC<IWorkflowsTableProps> = () => {
return (
<div className={css({ display: 'flex', flex: '1' })}>
<Table<typeof TEST_FLOW> columns={WORKFLOW_COLUMNS} data={[TEST_FLOW]} className={css({ w: '100%' })} />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { INotificationTemplateExtended } from '../../../../hooks/useTemplates';

export type WorkflowTableRow = INotificationTemplateExtended;
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { CellRendererComponent } from '@novu/novui';
import { css } from '@novu/novui/css';
import { IconBolt, IconCable, IconFlashOff } from '@novu/novui/icons';
import { Center, Flex, HStack, styled } from '@novu/novui/jsx';
import { text } from '@novu/novui/recipes';
import { ColorToken } from '@novu/novui/tokens';
import { WorkflowTableRow } from './WorkflowsTable.types';

export const GroupCell: CellRendererComponent<WorkflowTableRow, string> = (props) => {
return (
<Center
className={css({
color: 'typography.text.main',
rounded: '50',
border: 'solid',
py: '25',
px: '75',
borderColor: 'badge.border',
width: '[fit-content]',
bg: '[transparent]',
})}
>
{props.getValue()}
</Center>
);
};

const Text = styled('p', text);

export const NameCell: CellRendererComponent<WorkflowTableRow, string> = ({ getValue, row: { original } }) => {
return (
<HStack gap="50">
{
<IconCable
className={css({ width: '200', height: '200', color: 'icon.secondary' })}
title="workflow-row-label"
/>
}
<Flex direction={'column'}>
<Text variant={'main'}>{getValue()}</Text>
<Text variant={'secondary'}>{original.triggers ? original.triggers[0].identifier : 'Unknown'}</Text>
</Flex>
</HStack>
);
};

export const StatusCell: CellRendererComponent<WorkflowTableRow, boolean> = ({ getValue }) => {
const isActive = getValue();
getValue();
const color: ColorToken = isActive ? 'status.active' : 'status.inactive';

return (
<HStack gap="0">
{isActive ? (
<IconBolt size="16" className={css({ color })} title="workflow-status-indicator" />
) : (
<IconFlashOff size="16" className={css({ color })} title="workflow-status-indicator" />
)}
<Text variant={'main'} color={color}>
{isActive ? 'Active' : 'Inactive'}
</Text>
</HStack>
);
};
1 change: 1 addition & 0 deletions apps/web/src/studio/components/workflows/table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './WorkflowsTable';
1 change: 1 addition & 0 deletions libs/novui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"dependencies": {
"@mantine/core": "^7.10.0",
"@mantine/hooks": "^7.10.0",
"@tanstack/react-table": "^8.17.3",
"react-icons": "^5.0.1"
}
}
2 changes: 2 additions & 0 deletions libs/novui/panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ export default defineConfig({

// Enables JSX util generation!
jsxFramework: 'react',

validation: 'error',
});
3 changes: 2 additions & 1 deletion libs/novui/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './Test';
export * from './NovuiProvider';
export * from './table';
export * from './Test';
60 changes: 60 additions & 0 deletions libs/novui/src/components/table/Table.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useState } from 'react';
import { StoryFn, Meta } from '@storybook/react';
import { Badge, Switch } from '@mantine/core';

import { Table } from './Table';
import { ColumnDef } from '@tanstack/react-table';

export default {
title: 'Components/Table',
component: Table,
argTypes: {
data: {
control: false,
},
columns: {
control: false,
},
},
} as Meta<typeof Table>;

const SwitchCell = (props) => {
const [status, setStatus] = useState(props.status);
const switchHandler = () => {
setStatus((prev) => (prev === 'Enabled' ? 'Disabled' : 'Enabled'));
};

return <Switch label={status} onChange={switchHandler} checked={status === 'Enabled'} />;
};

const BadgeCell = (props) => {
return (
<Badge variant="outline" size="md" radius="xs">
{props.getValue()}
</Badge>
);
};

interface IExampleData {
name: string;
category: string;
creationDate: string;
status: string;
}

const columns: ColumnDef<IExampleData>[] = [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'category', header: 'Category', cell: BadgeCell },
{ accessorKey: 'creationDate', header: 'Date Created' },
{ accessorKey: 'status', header: 'Status', cell: SwitchCell },
];

const data: IExampleData[] = [
{ name: 'Great', category: 'Fun', status: 'Disabled', creationDate: '01/01/2021 16:36' },
{ name: 'Whats up?', category: 'Done', status: 'Enabled', creationDate: '01/01/2021 16:36' },
];

const Template: StoryFn<typeof Table> = ({ ...args }) => <Table columns={columns} data={data} {...args} />;

export const PrimaryUse = Template.bind({});
PrimaryUse.args = {};
69 changes: 69 additions & 0 deletions libs/novui/src/components/table/Table.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { type TableStylesNames } from '@mantine/core';
import { css } from '../../../styled-system/css';

const tableStyles: Partial<Record<TableStylesNames, string>> = {
tr: css({
'& td': {
textOverflow: 'ellipsis',
},
}),
table: css({
borderCollapse: 'collapse',
borderSpacingX: '125',
textStyle: 'text.main',
'& tr td:first-of-type': {
pl: '200',
pr: '200',
},
'& tr th:first-of-type': {
pl: '200',
pr: '200',
},
'& tr td:last-child': {
pr: '200',
},
'& tr th:last-child': {
pr: '200',
},
'& thead tr': {
borderBottom: 'solid',
borderColor: 'table.header.border',
},
'& thead tr th': {
fontWeight: 'regular',
// height: '17px',
textAlign: 'left',
color: 'typography.text.tertiary',
borderBottom: 'none',
borderSpacing: '0',
pb: '100',
},
'& tbody tr td': {
// TODO: replace with token value
maxWidth: '[100px]',
// TODO: replace with token value
height: '[80px]',

color: 'typography.text.main',
borderBottom: 'solid',
borderColor: 'table.row.border',
},
'& tbody tr[data-disabled="true"]:hover': {
cursor: 'default',
},
'& tbody tr[data-disabled="false"]:hover': {
cursor: 'pointer',
},
'& tbody tr:last-of-type td': {
borderBottom: 'solid',
borderColor: 'table.bottom.border',
},
_hover: {
'& tbody tr:hover': {
bg: 'table.row.surface.hover',
},
},
}),
};

export default tableStyles;
Loading
Loading