feat ui: dashboards redesign start

This commit is contained in:
nick-delirium 2024-04-26 17:37:02 +02:00
parent c90b82e06f
commit e05f143812
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
6 changed files with 140 additions and 130 deletions

View file

@ -1,18 +1,75 @@
import { LockOutlined, TeamOutlined } from '@ant-design/icons';
import { Switch, Table, TableColumnsType, Tag, Tooltip } from 'antd';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { NoContent, Pagination } from 'UI';
import { useStore } from 'App/mstore';
import { sliceListPerPage } from 'App/utils';
import DashboardListItem from './DashboardListItem';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { Tooltip } from 'antd';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
function DashboardList() {
import { checkForRecent } from 'App/date';
import { useStore } from 'App/mstore';
import Dashboard from 'App/mstore/types/dashboard';
import { dashboardSelected, withSiteId } from 'App/routes';
import { NoContent } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
function DashboardList({ history, siteId }: { history: any; siteId: string }) {
const { dashboardStore } = useStore();
const list = dashboardStore.filteredList;
const dashboardsSearch = dashboardStore.filter.query;
const lenth = list.length;
const tableConfig: TableColumnsType<Dashboard> = [
{
title: 'Title',
dataIndex: 'name',
width: '25%',
render: (t) => <div className="link capitalize-first">{t}</div>,
},
{
title: 'Description',
ellipsis: {
showTitle: false,
},
width: '25%',
dataIndex: 'description',
},
{
title: 'Last Modified',
dataIndex: 'updatedAt',
width: '16.67%',
sorter: (a, b) => a.updatedAt.toMillis() - b.updatedAt.toMillis(),
sortDirections: ['ascend', 'descend'],
render: (date) => checkForRecent(date, 'LLL dd, yyyy, hh:mm a'),
},
{
title: 'Modified By',
dataIndex: 'updatedBy',
width: '16.67%',
sorter: (a, b) => a.updatedBy.localeCompare(b.updatedBy),
sortDirections: ['ascend', 'descend'],
},
{
title: (
<div className={'flex items-center justify-between'}>
<div>Visibility</div>
<Switch checked={!dashboardStore.filter.showMine} onChange={() =>
dashboardStore.updateKey('filter', {
...dashboardStore.filter,
showMine: !dashboardStore.filter.showMine,
})} checkedChildren={'Public'} unCheckedChildren={'Private'} />
</div>
),
width: '16.67%',
dataIndex: 'isPublic',
render: (isPublic: boolean) => (
<Tag icon={isPublic ? <TeamOutlined /> : <LockOutlined />}>
{isPublic ? 'Team' : 'Private'}
</Tag>
),
},
];
return (
<NoContent
show={lenth === 0}
@ -20,48 +77,53 @@ function DashboardList() {
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={ICONS.NO_DASHBOARDS} size={180} />
<div className="text-center mt-4">
{dashboardsSearch !== '' ? 'No matching results' : "You haven't created any dashboards yet"}
{dashboardsSearch !== ''
? 'No matching results'
: "You haven't created any dashboards yet"}
</div>
</div>
}
subtext={
<div>
A Dashboard is a collection of <Tooltip title={<div className="text-center">Utilize cards to visualize key user interactions or product performance metrics.</div>} className="text-center"><span className="underline decoration-dotted">Cards</span></Tooltip> that can be shared across teams.
A Dashboard is a collection of{' '}
<Tooltip
title={
<div className="text-center">
Utilize cards to visualize key user interactions or product
performance metrics.
</div>
}
className="text-center"
>
<span className="underline decoration-dotted">Cards</span>
</Tooltip>{' '}
that can be shared across teams.
</div>
}
>
<div className="mt-3 border-b">
<div className="grid grid-cols-12 py-2 font-medium px-6">
<div className="col-span-8">Title</div>
<div className="col-span-2">Visibility</div>
<div className="col-span-2 text-right">Last Modified</div>
</div>
{sliceListPerPage(list, dashboardStore.page - 1, dashboardStore.pageSize).map(
(dashboard: any) => (
<React.Fragment key={dashboard.dashboardId}>
<DashboardListItem dashboard={dashboard} />
</React.Fragment>
)
)}
</div>
<div className="w-full flex items-center justify-between pt-4 px-6">
<div className="text-disabled-text">
Showing{' '}
<span className="font-semibold">{Math.min(list.length, dashboardStore.pageSize)}</span>{' '}
out of <span className="font-semibold">{list.length}</span> Dashboards
</div>
<Pagination
page={dashboardStore.page}
total={lenth}
onPageChange={(page) => dashboardStore.updateKey('page', page)}
limit={dashboardStore.pageSize}
debounceRequest={100}
/>
</div>
<Table
dataSource={list}
columns={tableConfig}
pagination={{
showTotal: (total, range) =>
`Showing ${range[0]}-${range[1]} of ${total} items`,
size: 'small',
}}
onRow={(record) => ({
onClick: () => {
dashboardStore.selectDashboardById(record.dashboardId);
const path = withSiteId(
dashboardSelected(record.dashboardId),
siteId
);
history.push(path);
},
})}
/>
</NoContent>
);
}
export default observer(DashboardList);
export default connect((state: any) => ({
siteId: state.getIn(['site', 'siteId']),
}))(withRouter(observer(DashboardList)));

View file

@ -1,48 +0,0 @@
import React from 'react';
import { Icon } from 'UI';
import { connect } from 'react-redux';
import { IDashboard } from 'App/mstore/types/dashboard';
import { checkForRecent } from 'App/date';
import { withSiteId, dashboardSelected } from 'App/routes';
import { useStore } from 'App/mstore';
import { withRouter, RouteComponentProps } from 'react-router-dom';
interface Props extends RouteComponentProps {
dashboard: IDashboard;
siteId: string;
}
function DashboardListItem(props: Props) {
const { dashboard, siteId, history } = props;
const { dashboardStore } = useStore();
const onItemClick = () => {
dashboardStore.selectDashboardById(dashboard.dashboardId);
const path = withSiteId(dashboardSelected(dashboard.dashboardId), siteId);
history.push(path);
};
return (
<div className="hover:bg-active-blue cursor-pointer border-t px-6" onClick={onItemClick}>
<div className="grid grid-cols-12 py-4 select-none items-center">
<div className="col-span-8 flex items-start">
<div className="flex items-center capitalize-first">
<div className="w-9 h-9 rounded-full bg-tealx-lightest flex items-center justify-center mr-2">
<Icon name="columns-gap" size="16" color="tealx" />
</div>
<div className="link capitalize-first">{dashboard.name}</div>
</div>
</div>
<div className="col-span-2">
<div className="flex items-center">
<Icon name={dashboard.isPublic ? 'user-friends' : 'person-fill'} className="mr-2" />
<span>{dashboard.isPublic ? 'Team' : 'Private'}</span>
</div>
</div>
<div className="col-span-2 text-right">{checkForRecent(dashboard.createdAt, 'LLL dd, yyyy, hh:mm a')}</div>
</div>
{dashboard.description ? <div className="color-gray-medium px-2 pb-2">{dashboard.description}</div> : null}
</div>
);
}
// @ts-ignore
export default connect((state) => ({ siteId: state.getIn(['site', 'siteId']) }))(withRouter(DashboardListItem));

View file

@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { Icon } from 'UI';
import { debounce } from 'App/utils';
import { Input } from 'antd';
let debounceUpdate: any = () => {};
@ -24,16 +24,15 @@ function DashboardSearch() {
};
return (
<div className="relative">
<Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" />
<input
value={query}
name="dashboardsSearch"
className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10"
placeholder="Filter by title or description"
onChange={write}
/>
</div>
<Input.Search
value={query}
allowClear
name="dashboardsSearch"
className="w-full"
placeholder="Filter by title or description"
onChange={write}
onSearch={(value) => dashboardStore.updateKey('filter', { ...dashboardStore.filter, query: value })}
/>
);
}

View file

@ -1,10 +1,11 @@
import React from 'react';
import { Button, PageTitle, Toggler, Icon } from 'UI';
import Select from 'Shared/Select';
import { PageTitle } from 'UI';
import DashboardSearch from './DashboardSearch';
import { useStore } from 'App/mstore';
import { observer, useObserver } from 'mobx-react-lite';
import { withSiteId } from 'App/routes';
import { Button } from 'antd'
import { PlusOutlined } from "@ant-design/icons";
function Header({ history, siteId }: { history: any; siteId: string }) {
const { dashboardStore } = useStore();
@ -19,46 +20,32 @@ function Header({ history, siteId }: { history: any; siteId: string }) {
};
return (
<>
<div className="flex items-center justify-between px-6">
<div className="flex items-center justify-between px-4 pb-2">
<div className="flex items-baseline mr-3">
<PageTitle title="Dashboards" />
</div>
<div className="ml-auto flex items-center">
<Button variant="primary" onClick={onAddDashboardClick}>
New Dashboard
<Button
icon={<PlusOutlined />}
type="primary" onClick={onAddDashboardClick}>
Create Dashboard
</Button>
<div className="mx-2"></div>
<div className="w-1/4" style={{ minWidth: 300 }}>
<DashboardSearch />
</div>
</div>
{/*<Select*/}
{/* options={[*/}
{/* { label: 'Newest', value: 'desc' },*/}
{/* { label: 'Oldest', value: 'asc' },*/}
{/* ]}*/}
{/* defaultValue={sort.by}*/}
{/* plain*/}
{/* onChange={({ value }) => dashboardStore.updateKey('sort', { by: value.value })}*/}
{/*/>*/}
</div>
<div className="border-y px-3 py-1 mt-2 flex items-center w-full justify-end gap-4">
<Toggler
label="Private Dashboards"
checked={dashboardStore.filter.showMine}
name="test"
className="font-medium mr-2"
onChange={() =>
dashboardStore.updateKey('filter', {
...dashboardStore.filter,
showMine: !dashboardStore.filter.showMine,
})
}
/>
<Select
options={[
{ label: 'Newest', value: 'desc' },
{ label: 'Oldest', value: 'asc' },
]}
defaultValue={sort.by}
plain
onChange={({ value }) => dashboardStore.updateKey('sort', { by: value.value })}
/>
</div>
</>
);
}

View file

@ -7,6 +7,7 @@ import { DateTime } from 'luxon';
export default class Dashboard {
public static get ID_KEY():string { return "dashboardId" }
dashboardId?: string = undefined
key: string = ""
name: string = "Untitled Dashboard"
description: string = ""
isPublic: boolean = true
@ -23,6 +24,14 @@ export default class Dashboard {
this.validate();
}
get updatedAt() {
return this.createdAt as unknown as DateTime
}
get updatedBy() {
return "no api"
}
update(data: any) {
runInAction(() => {
Object.assign(this, data)
@ -54,6 +63,7 @@ export default class Dashboard {
this.name = json.name
this.description = json.description
this.isPublic = json.isPublic
this.key = json.dashboardId
this.createdAt = DateTime.fromMillis(new Date(json.createdAt).getTime())
if (json.widgets) {
const smallWidgets: any[] = json.widgets.filter(wi => wi.config.col === 1)

View file

@ -26,7 +26,7 @@ button {
}
textarea:focus,
input:not(.ant-input-number-input, .nofocus):focus {
input:not(.ant-input-number-input, .ant-input, .nofocus):focus {
border: solid thin $teal !important;
}