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

View File

@@ -12,17 +12,8 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
& .user-card { & button,
display: flex; & a {
gap: 0.5em;
align-items: center;
& img {
border-radius: 50%;
}
}
& button {
cursor: pointer; cursor: pointer;
color: $blue; color: $blue;
display: flex; 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 Navbar from 'components/Navbar/Navbar';
import PageTransition from 'components/PageTransition'; import PageTransition from 'components/PageTransition';
import RoundedImage from 'components/RoundedImage/RoundedImage'; import RoundedImage from 'components/RoundedImage/RoundedImage';
import { DATE_FORMAT } from 'constants/date';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import { getServerSideTranslation } from 'i18n'; import { getServerSideTranslation } from 'i18n';
import getUsers from 'lib/user/getUsers'; import getUsers from 'lib/user/getUsers';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import 'react-tabs/style/react-tabs.css'; import 'react-tabs/style/react-tabs.css';
import styles from 'styles/admin.module.scss'; import styles from 'styles/admin.module.scss';
import { withAuthentication } from 'utils/session'; import { withAuthentication } from 'utils/session';
@@ -19,103 +19,114 @@ dayjs.extend(relativeTime);
type UserExtended = User & { _count: { categories: number; links: number } }; type UserExtended = User & { _count: { categories: number; links: number } };
export default function AdminDashboard({ users }: { users: UserExtended[] }) { 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 ( return (
<PageTransition className={styles['admin']}> <PageTransition className={styles['admin']}>
<Navbar /> <Navbar />
<Tabs> <div style={{ overflow: 'auto', marginTop: '1em' }}>
<TabList> <table>
<Tab>{t('common:users')}</Tab> <thead>
<Tab>{t('common:stats')}</Tab> <tr>
</TabList> <TableCell type='th'>#</TableCell>
<TableCell type='th'>{t('common:name')}</TableCell>
<TabPanel> <TableCell type='th'>{t('common:email')}</TableCell>
<p> <TableCell type='th'>
{users.length} {t('admin:users')} {t('common:category.categories')} <b>({totalCategories})</b>
</p> </TableCell>
<table> <TableCell type='th'>
<thead> {t('common:link.links')} <b>({totalLinks})</b>
<tr> </TableCell>
<TH>#</TH> <TableCell type='th'>{t('admin:role')}</TableCell>
<TH>{t('common:name')}</TH> <TableCell type='th'>{t('admin:created_at')}</TableCell>
<TH>{t('common:email')}</TH> <TableCell type='th'>{t('admin:updated_at')}</TableCell>
<TH>{t('common:category.categories')}</TH> </tr>
<TH>{t('common:link.links')}</TH> </thead>
<TH>{t('admin:role')}</TH> <tbody>
<TH>{t('admin:created_at')}</TH> {users.length !== 0 &&
<TH>{t('admin:updated_at')}</TH> users.map((user) => (
</tr> <TableUserRow
</thead> user={user}
<tbody> key={user.id}
{users.length !== 0 && />
users.map( ))}
({ </tbody>
id, </table>
image, </div>
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>
</PageTransition> </PageTransition>
); );
} }
type TableItem = { children: ReactNode; fixed?: boolean }; function TableUserRow({ user }: { user: UserExtended }) {
function TH({ children, fixed = false }: TableItem) { const { t } = useTranslation();
const { id, image, name, email, _count, is_admin, createdAt, updatedAt } =
user;
return ( return (
<th> <tr>
<div className={clsx('cell', fixed && 'fixed')}>{children}</div> <TableCell type='td'>{id}</TableCell>
</th> <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) { type TableItem = {
return ( children: ReactNode;
<td> fixed?: boolean;
<div className={clsx('cell', fixed && 'fixed')}>{children}</div> column?: boolean;
</td> 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( export const getServerSideProps = withAuthentication(

View File

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

View File

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