Modernizing ui (#73)

Modernizing ui
This commit is contained in:
Christian Kellner
2023-03-20 08:52:13 +01:00
committed by GitHub
parent 2c5eceb0c1
commit d7c9c4bf76
45 changed files with 1233 additions and 1275 deletions

View File

@@ -1,12 +1,11 @@
import React from 'react';
import { Header } from 'semantic-ui-react';
import { Typography } from '@douyinfe/semi-ui';
import './Headline.less';
export default function Headline({ text, size = 'medium', className = '' } = {}) {
export default function Headline({ text, size = 3 } = {}) {
const { Title } = Typography;
return (
<Header className={`headline ${className}`} size={size}>
<Title heading={size} style={{ marginBottom: '1rem' }}>
{text}
</Header>
</Title>
);
}

View File

@@ -1,3 +0,0 @@
.headline{
color: #f1f1f1 !important;
}

View File

@@ -1,20 +1,20 @@
import React from 'react';
import { Button } from 'semantic-ui-react';
import { Button } from '@douyinfe/semi-ui';
import { xhrPost } from '../../services/xhr';
import { IconUser } from '@douyinfe/semi-icons';
const Logout = function Logout() {
return (
<Button
content="Logout"
labelPosition="left"
icon="user"
size="mini"
icon={<IconUser />}
type="danger"
theme="solid"
onClick={async () => {
await xhrPost('/api/login/logout');
location.reload();
}}
negative
/>
>
Logout
</Button>
);
};

View File

@@ -1,49 +1,54 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { Icon, Menu } from 'semantic-ui-react';
import { Tabs, TabPane } from '@douyinfe/semi-ui';
import './Menu.less';
import { useLocation } from 'react-router';
import { IconUser, IconTerminal, IconSetting } from '@douyinfe/semi-icons';
function parsePathName(name) {
const split = name.split('/').filter((s) => s.length !== 0);
return '/' + split[0];
}
const TopMenu = function TopMenu({ isAdmin }) {
const history = useHistory();
const location = useLocation();
const isActiveRoute = (name) => location.pathname.indexOf(name) !== -1;
return (
<Menu pointing secondary className="topMenu">
<Menu.Item
name="jobs"
active={isActiveRoute('jobs')}
className={isActiveRoute('jobs') ? 'topMenu__active' : 'topMenu__item'}
onClick={() => history.push('/jobs')}
>
<Icon name="search" /> Job Configuration
</Menu.Item>
<Tabs type="line" activeKey={parsePathName(location.pathname)} onTabClick={(key) => history.push(key)}>
<TabPane
itemKey="/jobs"
tab={
<span>
<IconTerminal />
Jobs
</span>
}
/>
{isAdmin && (
<Menu.Item
name="user"
active={isActiveRoute('users')}
className={isActiveRoute('users') ? 'topMenu__active' : 'topMenu__item'}
onClick={() => history.push('/users')}
>
<Icon name="user" /> User configuration
</Menu.Item>
<TabPane
itemKey="/users"
tab={
<span>
<IconUser />
User
</span>
}
/>
)}
{isAdmin && (
<Menu.Item
name="general"
active={isActiveRoute('general')}
className={isActiveRoute('general') ? 'topMenu__active' : 'topMenu__item'}
onClick={() => history.push('/generalSettings')}
>
<Icon name="cog" /> General Settings
</Menu.Item>
<TabPane
itemKey="/generalSettings"
tab={
<span>
<IconSetting />
General
</span>
}
/>
)}
</Menu>
</Tabs>
);
};

View File

@@ -1,15 +0,0 @@
.topMenu {
border-bottom: 1px solid #b7b7b7f2 !important;
&__active {
border-bottom: 1px solid #06dcfff2 !important;
font-weight: 550 !important;
color: #3ed7ff !important;
margin: 0 0 -1px !important;
}
&__item {
color: #fffffff2 !important;
border-color: transparent !important;
}
}

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { Header } from 'semantic-ui-react';
import insufficientPermission from '../../assets/insufficient_permission.png';
export default function InsufficientPermission() {
@@ -7,9 +6,7 @@ export default function InsufficientPermission() {
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column' }}>
<img src={insufficientPermission} height={250} />
<br />
<Header as="h4" inverted>
Insufficient permission :(
</Header>
<h4>Insufficient permission :(</h4>
</div>
);
}

View File

@@ -1,27 +1,18 @@
import React from 'react';
import { Header, Icon, Popup, Segment } from 'semantic-ui-react';
import { Card } from '@douyinfe/semi-ui';
import './SegmentParts.less';
export const SegmentPart = ({ name, icon = null, children, helpText }) => (
<Segment inverted>
<Header as="h5" inverted sub>
{icon && <Icon name={icon} inverted size="mini" />}
<Header.Content>{name}</Header.Content>
</Header>
export const SegmentPart = ({ name, Icon = null, children, helpText }) => {
const { Meta } = Card;
<Popup
content={helpText}
trigger={
<span className="generalSettings__help">
{' '}
<Icon name="help circle" inverted />
What is this?
</span>
return (
<Card
title={
<Meta title={name} description={helpText} avatar={Icon == null ? null : <Icon size="extra-extra-small" />} />
}
/>
<Segment inverted className="segmentParts">
>
{children}
</Segment>
</Segment>
);
</Card>
);
};

View File

@@ -1,66 +1,79 @@
import React, { Fragment } from 'react';
import React from 'react';
import { Table, Button } from 'semantic-ui-react';
import Switch from 'react-switch';
const emptyTable = () => {
return (
<Table.Row>
<Table.Cell collapsing colSpan={6} style={{ textAlign: 'center' }}>
No Data
</Table.Cell>
</Table.Row>
);
};
const content = (jobs, onJobRemoval, onJobStatusChanged, onJobEdit, onJobInsight) => {
return (
<Fragment>
{Object.keys(jobs).map((jobKey) => {
const job = jobs[jobKey];
return (
<Table.Row key={jobKey}>
<Table.Cell collapsing>
<Switch onChange={(checked) => onJobStatusChanged(job.id, checked)} checked={job.enabled} />
</Table.Cell>
<Table.Cell>{job.name}</Table.Cell>
<Table.Cell>{job.numberOfFoundListings || 0}</Table.Cell>
<Table.Cell>{job.provider.length || 0}</Table.Cell>
<Table.Cell>{job.notificationAdapter.length || 0}</Table.Cell>
<Table.Cell>
<div style={{ float: 'right' }}>
<Button circular color="teal" icon="chart line" onClick={() => onJobInsight(job.id)} />
<Button circular color="blue" icon="edit" onClick={() => onJobEdit(job.id)} />
<Button circular color="red" icon="trash" onClick={() => onJobRemoval(job.id)} />
</div>
</Table.Cell>
</Table.Row>
);
})}
</Fragment>
);
};
import { Button, Empty, Table, Switch } from '@douyinfe/semi-ui';
import { IconDelete, IconEdit, IconHistogram } from '@douyinfe/semi-icons';
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
const empty = (
<Empty
image={<IllustrationNoResult />}
darkModeImage={<IllustrationNoResultDark />}
description={'No jobs available'}
/>
);
export default function JobTable({ jobs = {}, onJobRemoval, onJobStatusChanged, onJobEdit, onJobInsight } = {}) {
return (
<Table singleLine inverted>
<Table.Header>
<Table.Row>
<Table.HeaderCell />
<Table.HeaderCell>Job Name</Table.HeaderCell>
<Table.HeaderCell>Number of findings</Table.HeaderCell>
<Table.HeaderCell>Active provider</Table.HeaderCell>
<Table.HeaderCell>Active notification adapter</Table.HeaderCell>
<Table.HeaderCell></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{Object.keys(jobs).length === 0
? emptyTable()
: content(jobs, onJobRemoval, onJobStatusChanged, onJobEdit, onJobInsight)}
</Table.Body>
</Table>
<Table
pagination={false}
empty={empty}
columns={[
{
title: '',
dataIndex: '',
render: (job) => {
return <Switch onChange={(checked) => onJobStatusChanged(job.id, checked)} checked={job.enabled} />;
},
},
{
title: 'Job Name',
dataIndex: 'name',
},
{
title: 'Number of findings',
dataIndex: 'numberOfFoundListings',
render: (value) => {
return value || 0;
},
},
{
title: 'Active provider',
dataIndex: 'provider',
render: (value) => {
return value.length || 0;
},
},
{
title: 'Active notification adapter',
dataIndex: 'notificationAdapter',
render: (value) => {
return value.length || 0;
},
},
{
title: '',
dataIndex: 'tools',
render: (_, job) => {
return (
<div style={{ float: 'right' }}>
<Button
type="primary"
icon={<IconHistogram />}
onClick={() => onJobInsight(job.id)}
style={{ marginRight: '1rem' }}
/>
<Button
type="secondary"
icon={<IconEdit />}
onClick={() => onJobEdit(job.id)}
style={{ marginRight: '1rem' }}
/>
<Button type="danger" icon={<IconDelete />} onClick={() => onJobRemoval(job.id)} />
</div>
);
},
},
]}
dataSource={jobs}
/>
);
}

View File

@@ -1,50 +1,38 @@
import React, { Fragment } from 'react';
import React from 'react';
import { Table, Button } from 'semantic-ui-react';
const emptyTable = () => {
return (
<Table.Row>
<Table.Cell collapsing colSpan="3" style={{ textAlign: 'center' }}>
No Data
</Table.Cell>
</Table.Row>
);
};
const content = (adapterData, onRemove, onEdit) => {
return (
<Fragment>
{adapterData.map((data) => {
return (
<Table.Row key={data.id}>
<Table.Cell>{data.name}</Table.Cell>
<Table.Cell>
<div style={{ float: 'right' }}>
<Button circular color="blue" icon="edit" onClick={() => onEdit(data.id)} />
<Button circular color="red" icon="trash" onClick={() => onRemove(data.id)} />
</div>
</Table.Cell>
</Table.Row>
);
})}
</Fragment>
);
};
import { Empty, Table, Button } from '@douyinfe/semi-ui';
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
export default function NotificationAdapterTable({ notificationAdapter = [], onRemove, onEdit } = {}) {
return (
<Table singleLine inverted>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Notification Adapter Name</Table.HeaderCell>
<Table.HeaderCell></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table
pagination={false}
empty={<Empty description="No Data" />}
columns={[
{
title: 'Notification Adapter Name',
dataIndex: 'name',
},
<Table.Body>
{notificationAdapter.length === 0 ? emptyTable() : content(notificationAdapter, onRemove, onEdit)}
</Table.Body>
</Table>
{
title: '',
dataIndex: 'tools',
render: (_, record) => {
return (
<div style={{ float: 'right' }}>
<Button
type="secondary"
icon={<IconEdit />}
onClick={() => onEdit(record.id)}
style={{ marginRight: '1rem' }}
/>
<Button type="danger" icon={<IconDelete />} onClick={() => onRemove(record.id)} />
</div>
);
},
},
]}
dataSource={notificationAdapter}
/>
);
}

View File

@@ -1,53 +1,42 @@
import React, { Fragment } from 'react';
import React from 'react';
import { Table, Button } from 'semantic-ui-react';
const emptyTable = () => {
return (
<Table.Row>
<Table.Cell collapsing colSpan="3" style={{ textAlign: 'center' }}>
No Data
</Table.Cell>
</Table.Row>
);
};
const content = (providerData, onRemove) => {
return (
<Fragment>
{providerData.map((data) => {
return (
<Table.Row key={data.id}>
<Table.Cell>{data.name}</Table.Cell>
<Table.Cell>
<a href={data.url} target="_blank" rel="noopener noreferrer">
Visit site
</a>
</Table.Cell>
<Table.Cell>
<div style={{ float: 'right' }}>
<Button circular color="red" icon="trash" onClick={() => onRemove(data.id)} />
</div>
</Table.Cell>
</Table.Row>
);
})}
</Fragment>
);
};
import { Empty, Table, Button } from '@douyinfe/semi-ui';
import { IconDelete } from '@douyinfe/semi-icons';
export default function ProviderTable({ providerData = [], onRemove } = {}) {
return (
<Table singleLine inverted>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Provider Name</Table.HeaderCell>
<Table.HeaderCell>Url</Table.HeaderCell>
<Table.HeaderCell></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>{providerData.length === 0 ? emptyTable() : content(providerData, onRemove)}</Table.Body>
</Table>
<Table
pagination={false}
empty={<Empty description="No Provider available" />}
columns={[
{
title: 'Provider Name',
dataIndex: 'name',
},
{
title: 'Provider Url',
dataIndex: 'url',
render: (_, data) => {
return (
<a href={data.url} target="_blank" rel="noopener noreferrer">
Visit site
</a>
);
},
},
{
title: '',
dataIndex: 'tools',
render: (_, record) => {
return (
<div style={{ float: 'right' }}>
<Button type="danger" icon={<IconDelete />} onClick={() => onRemove(record.id)} />
</div>
);
},
},
]}
dataSource={providerData}
/>
);
}

View File

@@ -1,49 +1,58 @@
import React from 'react';
import { Table, Button } from 'semantic-ui-react';
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
import { format } from '../../services/time/timeService';
import { Table, Button, Empty } from '@douyinfe/semi-ui';
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
const emptyTable = () => {
return (
<Table.Row>
<Table.Cell collapsing colSpan={4} style={{ textAlign: 'center' }}>
No Data
</Table.Cell>
</Table.Row>
);
};
const content = (user, onUserRemoval, onUserEdit) => {
return user.map((user) => {
return (
<Table.Row key={user.id}>
<Table.Cell>{user.username}</Table.Cell>
<Table.Cell>{user.lastLogin == null ? '---' : format(user.lastLogin)}</Table.Cell>
<Table.Cell>{user.numberOfJobs}</Table.Cell>
<Table.Cell>
<div style={{ float: 'right' }}>
<Button circular color="red" icon="trash" onClick={() => onUserRemoval(user.id)} />
<Button circular color="blue" icon="edit" onClick={() => onUserEdit(user.id)} />
</div>
</Table.Cell>
</Table.Row>
);
});
};
const empty = (
<Empty
image={<IllustrationNoResult />}
darkModeImage={<IllustrationNoResultDark />}
description={'No user available'}
/>
);
export default function UserTable({ user = [], onUserRemoval, onUserEdit } = {}) {
return (
<Table inverted>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Username</Table.HeaderCell>
<Table.HeaderCell>Last login</Table.HeaderCell>
<Table.HeaderCell>Number of jobs</Table.HeaderCell>
<Table.HeaderCell></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>{user.length === 0 ? emptyTable() : content(user, onUserRemoval, onUserEdit)}</Table.Body>
</Table>
<Table
pagination={false}
empty={empty}
columns={[
{
title: 'Username',
dataIndex: 'username',
},
{
title: 'Last login',
dataIndex: 'lastLogin',
render: (value) => {
return format(value);
},
},
{
title: 'Number of jobs',
dataIndex: 'numberOfJobs',
},
{
title: '',
dataIndex: 'tools',
render: (value, user) => {
return (
<div style={{ float: 'right' }}>
<Button
type="danger"
icon={<IconDelete />}
onClick={() => onUserRemoval(user.id)}
style={{ marginRight: '1rem' }}
/>
<Button type="primary" icon={<IconEdit />} onClick={() => onUserEdit(user.id)} />
</div>
);
},
},
]}
dataSource={user}
/>
);
}

View File

@@ -1,27 +0,0 @@
import React from 'react';
import './Toasts.css';
export default function Toast({ id, delay = 5500, message, onHide, backgroundColor, color, title }) {
const [className, setClassname] = React.useState('toast-container show-toast');
React.useEffect(() => {
let hideTimeout = null;
const timeout = setTimeout(() => {
setClassname('toast-container hide-toast');
hideTimeout = setTimeout(() => {
onHide && onHide(id);
}, 500);
}, delay);
return () => {
clearTimeout(timeout);
clearTimeout(hideTimeout);
};
}, [id, delay, onHide]);
return (
<div className={className} style={{ backgroundColor, color }}>
<h5>{title}</h5>
{message}
</div>
);
}

View File

@@ -1,12 +0,0 @@
import Toast from './Toast';
import React from 'react';
export default function ToastsContainer({ toasts, onToastFinished }) {
return (
<div className="toasts-container">
{toasts.map((toast, index) => (
<Toast key={index} {...toast} onHide={onToastFinished} />
))}
</div>
);
}

View File

@@ -1,7 +0,0 @@
import { createContext } from 'react';
const CheckoutDrawerContext = createContext({
showToast: () => {},
});
export default CheckoutDrawerContext;

View File

@@ -1,63 +0,0 @@
.toasts-container {
position: fixed;
z-index: 65535;
right: 0;
max-width: 250px;
display: flex;
flex-direction: column-reverse;
}
.toasts-container > .toast-container {
margin-bottom: 10px;
}
.toasts-container:last-child {
margin-bottom: 0;
}
.toast-container {
visibility: hidden;
position: relative;
z-index: 65535;
right: -1000px;
background-color: skyblue;
border-radius: 10px;
padding: 10px;
min-width: 10rem;
min-height: 3rem;
}
.toast-container.show-toast {
visibility: visible;
right: 24px;
animation: slidein 0.5s;
}
.toast-container.hide-toast {
visibility: visible;
animation: slideout 0.5s;
}
@keyframes slidein {
from {
right: -1000px;
opacity: 0;
}
to {
right: 24px;
opacity: 1;
}
}
@keyframes slideout {
from {
right: 24px;
opacity: 1;
}
to {
right: -1000px;
opacity: 0;
}
}

View File

@@ -1,23 +0,0 @@
import React from 'react';
export default function useToast() {
const [toasts, setToasts] = React.useState([]);
const showToast = ({ message, delay, color, backgroundColor, title }) => {
const toast = {
id: toasts.length,
message,
delay,
backgroundColor,
color,
title,
};
setToasts([...toasts, toast].reverse());
};
const onToastFinished = (id) => {
setToasts(toasts.filter((toast) => toast.id !== id));
};
return [showToast, onToastFinished, toasts];
}