mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
refactor: admin dashboard page and improve style
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
1
src/constants/date.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const DATE_FORMAT = 'DD MMM YYYY (HH:mm)';
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user