refactor: admin dashboard page and improve style

This commit is contained in:
Sonny
2023-12-28 19:08:48 +01:00
committed by Sonny
parent cd7ad1f3d3
commit 48bf294f51
6 changed files with 152 additions and 110 deletions

View File

@@ -4,7 +4,7 @@ import PATHS from 'constants/paths';
import useUser from 'hooks/useUser';
import { useTranslation } from 'next-i18next';
import Link from 'next/link';
import { MdOutlineAdminPanelSettings } from 'react-icons/md';
import { MdAdminPanelSettings } from 'react-icons/md';
import styles from './user-card.module.scss';
export default function UserCard() {
@@ -26,12 +26,20 @@ export default function UserCard() {
/>
{user.name}
</div>
{user.is_admin && (
<Link href={PATHS.ADMIN}>
<MdOutlineAdminPanelSettings />
</Link>
)}
<SettingsModal />
<span className={styles['user-controls']}>
{user.is_admin && (
<Link
href={PATHS.ADMIN}
className='reset'
>
<MdAdminPanelSettings
color='red'
size={24}
/>
</Link>
)}
<SettingsModal />
</span>
</div>
);
}

View File

@@ -12,17 +12,8 @@
justify-content: space-between;
align-items: center;
& .user-card {
display: flex;
gap: 0.5em;
align-items: center;
& img {
border-radius: 50%;
}
}
& button {
& button,
& a {
cursor: pointer;
color: $blue;
display: flex;
@@ -33,3 +24,18 @@
}
}
}
.user-card {
display: flex;
gap: 0.5em;
align-items: center;
& img {
border-radius: 50%;
}
}
.user-controls {
display: flex;
gap: 0.35em;
}

1
src/constants/date.ts Normal file
View File

@@ -0,0 +1 @@
export const DATE_FORMAT = 'DD MMM YYYY (HH:mm)';

View File

@@ -3,13 +3,13 @@ import clsx from 'clsx';
import Navbar from 'components/Navbar/Navbar';
import PageTransition from 'components/PageTransition';
import RoundedImage from 'components/RoundedImage/RoundedImage';
import { DATE_FORMAT } from 'constants/date';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { getServerSideTranslation } from 'i18n';
import getUsers from 'lib/user/getUsers';
import { useTranslation } from 'next-i18next';
import { ReactNode } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
import styles from 'styles/admin.module.scss';
import { withAuthentication } from 'utils/session';
@@ -19,103 +19,114 @@ dayjs.extend(relativeTime);
type UserExtended = User & { _count: { categories: number; links: number } };
export default function AdminDashboard({ users }: { users: UserExtended[] }) {
const { t } = useTranslation('common');
const { t } = useTranslation();
const totalCategories = users.reduce(
(acc, user) => (acc = acc + user._count.categories),
0,
);
const totalLinks = users.reduce(
(acc, user) => (acc = acc + user._count.links),
0,
);
return (
<PageTransition className={styles['admin']}>
<Navbar />
<Tabs>
<TabList>
<Tab>{t('common:users')}</Tab>
<Tab>{t('common:stats')}</Tab>
</TabList>
<TabPanel>
<p>
{users.length} {t('admin:users')}
</p>
<table>
<thead>
<tr>
<TH>#</TH>
<TH>{t('common:name')}</TH>
<TH>{t('common:email')}</TH>
<TH>{t('common:category.categories')}</TH>
<TH>{t('common:link.links')}</TH>
<TH>{t('admin:role')}</TH>
<TH>{t('admin:created_at')}</TH>
<TH>{t('admin:updated_at')}</TH>
</tr>
</thead>
<tbody>
{users.length !== 0 &&
users.map(
({
id,
image,
name,
email,
_count,
is_admin,
createdAt,
updatedAt,
}) => (
<tr key={id}>
<TD>{id}</TD>
<TD fixed>
{image && (
<RoundedImage
src={image}
width={24}
height={24}
alt={name}
title={name}
/>
)}
{name ?? '-'}
</TD>
<TD>{email}</TD>
<TD>{_count.categories}</TD>
<TD>{_count.links}</TD>
<TD>
{is_admin ? (
<span style={{ color: 'red' }}>
{t('admin:admin')}
</span>
) : (
<span style={{ color: 'green' }}>
{t('admin:user')}
</span>
)}
</TD>
<TD>{dayjs(createdAt).fromNow()}</TD>
<TD>{dayjs(updatedAt).fromNow()}</TD>
</tr>
),
)}
</tbody>
</table>
</TabPanel>
<TabPanel>{/* // stats */}</TabPanel>
</Tabs>
<div style={{ overflow: 'auto', marginTop: '1em' }}>
<table>
<thead>
<tr>
<TableCell type='th'>#</TableCell>
<TableCell type='th'>{t('common:name')}</TableCell>
<TableCell type='th'>{t('common:email')}</TableCell>
<TableCell type='th'>
{t('common:category.categories')} <b>({totalCategories})</b>
</TableCell>
<TableCell type='th'>
{t('common:link.links')} <b>({totalLinks})</b>
</TableCell>
<TableCell type='th'>{t('admin:role')}</TableCell>
<TableCell type='th'>{t('admin:created_at')}</TableCell>
<TableCell type='th'>{t('admin:updated_at')}</TableCell>
</tr>
</thead>
<tbody>
{users.length !== 0 &&
users.map((user) => (
<TableUserRow
user={user}
key={user.id}
/>
))}
</tbody>
</table>
</div>
</PageTransition>
);
}
type TableItem = { children: ReactNode; fixed?: boolean };
function TH({ children, fixed = false }: TableItem) {
function TableUserRow({ user }: { user: UserExtended }) {
const { t } = useTranslation();
const { id, image, name, email, _count, is_admin, createdAt, updatedAt } =
user;
return (
<th>
<div className={clsx('cell', fixed && 'fixed')}>{children}</div>
</th>
<tr>
<TableCell type='td'>{id}</TableCell>
<TableCell
type='td'
fixed
>
{image && (
<RoundedImage
src={image}
width={32}
height={32}
alt={name}
title={name}
/>
)}
{name ?? '-'}
</TableCell>
<TableCell type='td'>{email}</TableCell>
<TableCell type='td'>{_count.categories}</TableCell>
<TableCell type='td'>{_count.links}</TableCell>
<TableCell type='td'>
{is_admin ? (
<span style={{ color: 'red' }}>{t('admin:admin')}</span>
) : (
<span style={{ color: 'green' }}>{t('admin:user')}</span>
)}
</TableCell>
<TableCell
type='td'
column
>
<span>{dayjs(createdAt).fromNow()}</span>
<span>{dayjs(createdAt).format(DATE_FORMAT)}</span>
</TableCell>
<TableCell type='td'>{dayjs(updatedAt).fromNow()}</TableCell>
</tr>
);
}
function TD({ children, fixed = false }: TableItem) {
return (
<td>
<div className={clsx('cell', fixed && 'fixed')}>{children}</div>
</td>
type TableItem = {
children: ReactNode;
fixed?: boolean;
column?: boolean;
type: 'td' | 'th';
};
function TableCell({
children,
fixed = false,
column = false,
type,
}: TableItem) {
const child = (
<div className={clsx('cell', fixed && 'fixed', column && 'column')}>
{children}
</div>
);
return type === 'td' ? <td>{child}</td> : <th>{child}</th>;
}
export const getServerSideProps = withAuthentication(

View File

@@ -37,7 +37,7 @@ body {
flex-direction: column;
}
a {
a:not(.reset) {
width: fit-content;
color: $blue;
border-bottom: 1px solid transparent;
@@ -51,6 +51,12 @@ a {
}
}
a.reset {
color: $blue;
text-decoration: none;
transition: 0;
}
h1,
h2,
h3,

View File

@@ -4,16 +4,21 @@ table {
height: auto;
width: 100%;
border-collapse: collapse;
width: 100%;
}
th {
font-weight: 400;
background-color: $lightest-blue;
}
td,
th {
padding: 1em;
padding: 0.45em;
}
th {
background-color: $lightest-blue;
th,
td {
white-space: nowrap;
}
tr:nth-child(even) {
@@ -24,9 +29,14 @@ table .cell {
width: 100%;
display: flex;
align-items: center;
gap: 0.25em;
gap: 0.35em;
&:not(.fixed) {
justify-content: center;
}
&.column {
gap: 0;
flex-direction: column;
}
}