Merge pull request #29 from twentyhq/cbo-add-table

Add simple styled table
This commit is contained in:
Anders Borch 2023-04-13 10:50:08 +02:00 committed by GitHub
commit c6a60824b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 7558 additions and 41540 deletions

View File

@ -1,84 +0,0 @@
version: 2.1
orbs:
aws-ecr: circleci/aws-ecr@8.2.1
aws-ecs: circleci/aws-ecs@03.2.0
slack: circleci/slack@4.12.0
node: circleci/node@5.0.3
jobs:
tests-front:
executor: node/default
steps:
- checkout
- run:
command: cd front && npm install
name: install node dependencies
- run:
command: cd front && npm run test
name: tests
workflows:
build-and-deploy:
jobs:
- tests-front
- aws-ecr/build-and-push-image:
name: build-image
filters:
branches:
only: main
requires:
- tests-front
dockerfile: ./infra/prod/twenty/Dockerfile
registry-id: AWS_ACCOUNT_ID
aws-access-key-id: AWS_ACCESS_KEY_ID
aws-secret-access-key: AWS_SECRET_ACCESS_KEY
region: $AWS_REGION
repo: $AWS_ECR_REPO
tag: $CIRCLE_SHA1
extra-build-args: >
--build-arg REACT_APP_API_URL=$REACT_APP_API_URL
- aws-ecs/deploy-service-update:
name: deploy-canary
requires:
- build-image
family: $AWS_ECS_CONTAINER_NAME_CANARY
cluster: $AWS_ECS_CLUSTER
container-image-name-updates: "container=$AWS_ECS_CONTAINER_NAME_CANARY,tag=${CIRCLE_SHA1}"
- slack/on-hold:
name: slack-notification
context: slack-secrets
requires:
- deploy-canary
- hold:
type: approval
requires:
- slack-notification
- aws-ecs/deploy-service-update:
name: deploy-default
requires:
- hold
family: $AWS_ECS_CONTAINER_NAME_DEFAULT
cluster: $AWS_ECS_CLUSTER
container-image-name-updates: "container=$AWS_ECS_CONTAINER_NAME_DEFAULT,tag=${CIRCLE_SHA1}"
post-steps:
- slack/notify:
event: pass
template: basic_success_1
- slack/notify:
event: fail
template: basic_fail_1
- aws-ecr/build-and-push-image:
name: build-image-latest
requires:
- deploy-default
dockerfile: ./infra/prod/twenty/Dockerfile
registry-id: AWS_ACCOUNT_ID
aws-access-key-id: AWS_ACCESS_KEY_ID
aws-secret-access-key: AWS_SECRET_ACCESS_KEY
region: $AWS_REGION
repo: $AWS_ECR_REPO
tag: latest
extra-build-args: >
--build-arg REACT_APP_API_URL=$REACT_APP_API_URL

View File

@ -1,46 +1,33 @@
module.exports = { module.exports = {
webpackFinal: (config) => { webpackFinal: config => {
config.module.rules.push({ config.module.rules.push({
test: /\.tsx?$/, test: /\.tsx?$/,
exclude: /node_modules/, exclude: /node_modules/,
use: [ use: [{
{ loader: require.resolve('babel-loader'),
loader: require.resolve('babel-loader'), options: {
options: { presets: [require('@babel/preset-typescript').default, [require('@babel/preset-react').default, {
presets: [ runtime: 'automatic'
require('@babel/preset-typescript').default, }], require('@babel/preset-env').default]
[require('@babel/preset-react').default, { runtime: 'automatic' }], }
require('@babel/preset-env').default, }]
], });
}, config.resolve.extensions.push('.ts', '.tsx');
},
],
})
config.resolve.extensions.push('.ts', '.tsx')
config.module.rules.push({ config.module.rules.push({
test: /\.mjs$/, test: /\.mjs$/,
include: /node_modules/, include: /node_modules/,
type: 'javascript/auto', type: 'javascript/auto'
}) });
config.resolve.extensions.push('.mjs');
config.resolve.extensions.push('.mjs') return config;
return config
}, },
stories: [ stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
"../src/**/*.stories.mdx", addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions", "@storybook/preset-create-react-app", '@storybook/addon-mdx-gfm'],
"../src/**/*.stories.@(js|jsx|ts|tsx)" framework: {
], name: '@storybook/react-webpack5',
addons: [ options: {}
"@storybook/addon-links", },
"@storybook/addon-essentials", docs: {
"@storybook/addon-interactions", autodocs: true
"@storybook/preset-create-react-app"
],
framework: "@storybook/react",
core: {
builder: "@storybook/builder-webpack5"
} }
} };

48666
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
"@fortawesome/free-regular-svg-icons": "^6.2.1", "@fortawesome/free-regular-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@tanstack/react-table": "^8.8.5",
"@types/node": "^16.18.4", "@types/node": "^16.18.4",
"@types/react": "^18.0.25", "@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.9",
@ -18,7 +19,6 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.4.4", "react-router-dom": "^6.4.4",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {
@ -26,8 +26,8 @@
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"storybook": "start-storybook -p 6006 -s public", "storybook": "storybook dev -p 6006 -s public",
"build-storybook": "build-storybook -s public", "build-storybook": "storybook build -s public",
"coverage": "react-scripts test --coverage --watchAll" "coverage": "react-scripts test --coverage --watchAll"
}, },
"eslintConfig": { "eslintConfig": {
@ -50,7 +50,7 @@
"react-refresh": "0.14.0" "react-refresh": "0.14.0"
}, },
"jest": { "jest": {
"coveragePathIgnorePatterns" : [ "coveragePathIgnorePatterns": [
".stories.tsx$" ".stories.tsx$"
] ]
}, },
@ -67,19 +67,19 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-actions": "^6.5.14", "@storybook/addon-actions": "^7.0.2",
"@storybook/addon-essentials": "^6.5.14", "@storybook/addon-essentials": "^7.0.2",
"@storybook/addon-interactions": "^6.5.14", "@storybook/addon-interactions": "^7.0.2",
"@storybook/addon-links": "^6.5.14", "@storybook/addon-links": "^7.0.2",
"@storybook/builder-webpack5": "^6.5.14", "@storybook/node-logger": "^7.0.2",
"@storybook/manager-webpack5": "^6.5.14", "@storybook/preset-create-react-app": "^7.0.2",
"@storybook/node-logger": "^6.5.14", "@storybook/react": "^7.0.2",
"@storybook/preset-create-react-app": "^4.1.2", "@storybook/react-webpack5": "^7.0.2",
"@storybook/react": "^6.5.14", "@storybook/testing-library": "^0.1.0",
"@storybook/testing-library": "^0.0.13",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"react-scripts": "5.0.1",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"babel-plugin-named-exports-order": "^0.0.2", "babel-plugin-named-exports-order": "^0.0.2",
@ -91,9 +91,10 @@
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.31.11",
"eslint-plugin-storybook": "^0.6.7", "eslint-plugin-storybook": "^0.6.11",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"storybook": "^7.0.2",
"typescript": "^4.9.3", "typescript": "^4.9.3",
"webpack": "^5.75.0" "webpack": "^5.75.0"
} }

View File

@ -0,0 +1,99 @@
import * as React from 'react';
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table';
import TableHeader from './TableHeader';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import styled from '@emotion/styled';
type OwnProps = {
data: Array<any>;
columns: Array<ColumnDef<any, any>>;
viewName: string;
viewIcon?: IconProp;
};
const StyledTable = styled.table`
min-width: 100%;
border-radius: 4px;
border: 1px solid #f5f5f5;
border-spacing: 0;
td,
th {
border: 1px solid #f5f5f5;
font-size: 12px;
text-align: left;
padding: 11px 0 11px 4px;
}
`;
function Table({ data, columns, viewName, viewIcon }: OwnProps) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return (
<div>
<TableHeader viewName={viewName} viewIcon={viewIcon} />
<StyledTable>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
{table
.getFooterGroups()
.flatMap((group) => group.headers)
.filter((header) => !!header.column.columnDef.footer).length > 0 && (
<tfoot>
{table.getFooterGroups().map((footerGroup) => (
<tr key={footerGroup.id}>
{footerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.footer,
header.getContext(),
)}
</th>
))}
</tr>
))}
</tfoot>
)}
</StyledTable>
</div>
);
}
export default Table;

View File

@ -0,0 +1,28 @@
import styled from '@emotion/styled';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
type OwnProps = {
viewName: string;
viewIcon?: IconProp;
};
const StyledTitle = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
color: ${(props) => props.theme.text60};
font-weight: 500;
`;
function TableHeader({ viewName, viewIcon }: OwnProps) {
return (
<StyledTitle>
{viewIcon && <FontAwesomeIcon icon={viewIcon} />}
{viewName}
</StyledTitle>
);
}
export default TableHeader;

View File

@ -0,0 +1,5 @@
export interface Company {
id: number;
name: string;
logo: string;
}

View File

@ -0,0 +1,5 @@
export interface Pipe {
id: number;
name: string;
icon: string;
}

View File

@ -1,10 +1,125 @@
import { faUser } from '@fortawesome/free-regular-svg-icons'; import {
faBuilding,
faCalendar,
faEnvelope,
faRectangleList,
faUser,
} from '@fortawesome/free-regular-svg-icons';
import { faList, faMapPin, faPhone } from '@fortawesome/free-solid-svg-icons';
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer'; import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
import Table from '../../components/table/Table';
import { Company } from '../../interfaces/company.interface';
import { Pipe } from '../../interfaces/pipe.interface';
import { createColumnHelper } from '@tanstack/react-table';
import styled from '@emotion/styled';
import TableHeader from '../../components/table/TableHeader';
type People = {
fullName: string;
email: string;
company: Company;
phone: string;
creationDate: string;
pipe: Pipe;
city: string;
};
const StyledPeopleContainer = styled.div`
padding: 8px;
width: 100%;
table {
margin-top: 8px;
}
`;
const defaultData: Array<People> = [
{
fullName: 'Alexandre Prot',
email: 'alexandre@qonto.com',
company: { id: 1, name: 'Qonto', logo: 'https://qonto.eu/logo.png' },
phone: '06 12 34 56 78',
creationDate: 'Feb 23, 2018',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris',
},
{
fullName: 'Alexandre Prot',
email: 'alexandre@qonto.com',
company: { id: 1, name: 'Qonto', logo: 'https://qonto.eu/logo.png' },
phone: '06 12 34 56 78',
creationDate: 'Feb 23, 2018',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris',
},
{
fullName: 'Alexandre Prot',
email: 'alexandre@qonto.com',
company: { id: 1, name: 'Qonto', logo: 'https://qonto.eu/logo.png' },
phone: '06 12 34 56 78',
creationDate: 'Feb 23, 2018',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris',
},
{
fullName: 'Alexandre Prot',
email: 'alexandre@qonto.com',
company: { id: 1, name: 'Qonto', logo: 'https://qonto.eu/logo.png' },
phone: '06 12 34 56 78',
creationDate: 'Feb 23, 2018',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris',
},
{
fullName: 'Alexandre Prot',
email: 'alexandre@qonto.com',
company: { id: 1, name: 'Qonto', logo: 'https://qonto.eu/logo.png' },
phone: '06 12 34 56 78',
creationDate: 'Feb 23, 2018',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris',
},
];
const columnHelper = createColumnHelper<People>();
const columns = [
columnHelper.accessor('fullName', {
header: () => <TableHeader viewName="People" viewIcon={faUser} />,
}),
columnHelper.accessor('email', {
header: () => <TableHeader viewName="Email" viewIcon={faEnvelope} />,
}),
columnHelper.accessor('company', {
cell: (props) => <span>{props.row.original.company.name}</span>,
header: () => <TableHeader viewName="Company" viewIcon={faBuilding} />,
}),
columnHelper.accessor('phone', {
header: () => <TableHeader viewName="Phone" viewIcon={faPhone} />,
}),
columnHelper.accessor('creationDate', {
header: () => <TableHeader viewName="Creation" viewIcon={faCalendar} />,
}),
columnHelper.accessor('pipe', {
cell: (props) => <span>{props.row.original.pipe.name}</span>,
header: () => <TableHeader viewName="Pipe" viewIcon={faRectangleList} />,
}),
columnHelper.accessor('city', {
header: () => <TableHeader viewName="City" viewIcon={faMapPin} />,
}),
];
function People() { function People() {
return ( return (
<WithTopBarContainer title="People" icon={faUser}> <WithTopBarContainer title="People" icon={faUser}>
<></> <StyledPeopleContainer>
<Table
data={defaultData}
columns={columns}
viewName="All People"
viewIcon={faList}
/>
</StyledPeopleContainer>
</WithTopBarContainer> </WithTopBarContainer>
); );
} }