39948-vm/frontend/src/pages/dashboard.tsx

204 lines
6.0 KiB
TypeScript

import * as icon from '@mdi/js';
import Head from 'next/head';
import React from 'react';
import type { ReactElement } from 'react';
import LayoutAuthenticated from '../layouts/Authenticated';
import SectionMain from '../components/SectionMain';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
import BaseIcon from '../components/BaseIcon';
import { getPageTitle } from '../config';
import Link from 'next/link';
import { hasPermission } from '../helpers/userPermissions';
import { fetchWidgets } from '../stores/roles/rolesSlice';
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
import {
useDashboardCounts,
EntityCountValue,
} from '../hooks/useDashboardCounts';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
/**
* Dashboard card component for entity counts
*/
interface DashboardCardProps {
href: string;
label: string;
count: EntityCountValue;
iconKey: string;
corners: string;
cardsStyle: string;
iconsColor: string;
}
const DashboardCard = ({
href,
label,
count,
iconKey,
corners,
cardsStyle,
iconsColor,
}: DashboardCardProps) => {
const iconPath =
(icon as Record<string, string>)[iconKey] ??
(icon as Record<string, string>)['mdiFormatListBulleted'];
return (
<Link href={href}>
<div
className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className='flex justify-between align-center'>
<div>
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
{label}
</div>
<div className='text-3xl leading-tight font-semibold'>{count}</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w='w-16'
h='h-16'
size={48}
path={iconPath}
/>
</div>
</div>
</div>
</Link>
);
};
const Dashboard = () => {
const dispatch = useAppDispatch();
const iconsColor = useAppSelector((state) => state.style.iconsColor);
const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const { currentUser } = useAppSelector((state) => state.auth);
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
// Use the centralized dashboard counts hook
const { getCount, getVisibleEntities } = useDashboardCounts(currentUser);
const [widgetsRole, setWidgetsRole] = React.useState({
role: { value: '', label: '' },
});
const { rolesWidgets, loading } = useAppSelector((state) => state.roles) as {
rolesWidgets: Array<{ id: string; [key: string]: unknown }>;
loading: boolean;
};
async function getWidgets(roleId: string) {
await dispatch(fetchWidgets(roleId));
}
React.useEffect(() => {
if (!currentUser) return;
setWidgetsRole({
role: {
value: currentUser?.app_role?.id,
label: currentUser?.app_role?.name,
},
});
}, [currentUser]);
React.useEffect(() => {
if (!currentUser || !widgetsRole?.role?.value) return;
getWidgets(widgetsRole?.role?.value || '').then();
}, [widgetsRole?.role?.value]);
// Get entities visible to current user
const visibleEntities = getVisibleEntities();
return (
<>
<Head>
<title>{getPageTitle('Overview')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={icon.mdiChartTimelineVariant}
title='Overview'
main
>
{''}
</SectionTitleLineWithButton>
{hasPermission(currentUser, 'CREATE_ROLES') && (
<WidgetCreator
currentUser={currentUser}
isFetchingQuery={isFetchingQuery}
setWidgetsRole={setWidgetsRole}
widgetsRole={widgetsRole}
/>
)}
{!!rolesWidgets.length &&
hasPermission(currentUser, 'CREATE_ROLES') && (
<p className='text-gray-500 dark:text-gray-400 mb-4'>
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
</p>
)}
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
{(isFetchingQuery || loading) && (
<div
className={` ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}
>
<BaseIcon
className={`${iconsColor} animate-spin mr-5`}
w='w-16'
h='h-16'
size={48}
path={icon.mdiLoading}
/>{' '}
Loading widgets...
</div>
)}
{rolesWidgets &&
rolesWidgets.map((widget) => (
<SmartWidget
key={widget.id}
userId={currentUser?.id}
widget={widget}
roleId={widgetsRole?.role?.value || ''}
admin={hasPermission(currentUser, 'CREATE_ROLES')}
/>
))}
</div>
{!!rolesWidgets.length && <hr className='my-6' />}
<div
id='dashboard'
className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'
>
{visibleEntities.map((entity) => (
<DashboardCard
key={entity.key}
href={entity.href}
label={entity.label}
count={getCount(entity.key)}
iconKey={entity.icon}
corners={corners}
cardsStyle={cardsStyle}
iconsColor={iconsColor}
/>
))}
</div>
</SectionMain>
</>
);
};
Dashboard.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default Dashboard;