Revert to version 9bb4dbe

This commit is contained in:
Flatlogic Bot 2026-01-08 12:36:10 +00:00
parent 251c57f6f7
commit b9c795278d
6 changed files with 346 additions and 214 deletions

View File

@ -45,17 +45,7 @@ const ListComments = ({ comments, loading, onDelete, currentPage, numPages, onPa
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
> >
<div className="flex flex-col ml-4">
<div className="text-lg font-semibold text-gray-800 dark:text-white">
{item.author?.fullName || 'Anonymous'}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{dataFormatter.dateFormatter(item.created)}
</div>
<div className="mt-2 text-gray-600 dark:text-gray-300">
{item.content}
</div>
</div>
</Link> </Link>
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}

View File

@ -33,23 +33,12 @@ const menuAside: MenuAsideItem[] = [
permissions: 'READ_PERMISSIONS' permissions: 'READ_PERMISSIONS'
}, },
{ {
href: '/leads/leads-list',
label: 'Leads', label: 'Leads',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_LEADS', permissions: 'READ_LEADS'
menu: [
{
href: '/leads/list',
label: 'Leads List',
permissions: 'READ_LEADS',
},
{
href: '/leads/kanban',
label: 'Kanban',
permissions: 'READ_LEADS',
},
],
}, },
{ {
href: '/projects/projects-list', href: '/projects/projects-list',

View File

@ -1,155 +1,373 @@
import * as icon from '@mdi/js' import * as icon from '@mdi/js';
import Head from 'next/head' import Head from 'next/head'
import React, { ReactElement } from 'react' import React from 'react'
import axios from 'axios' import axios from 'axios';
import type { ReactElement } from 'react'
import LayoutAuthenticated from '../layouts/Authenticated' import LayoutAuthenticated from '../layouts/Authenticated'
import SectionMain from '../components/SectionMain' import SectionMain from '../components/SectionMain'
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
import BaseIcon from '../components/BaseIcon' import BaseIcon from "../components/BaseIcon";
import { getPageTitle } from '../config' import { getPageTitle } from '../config'
import Link from 'next/link' import Link from "next/link";
import { useAppSelector } from '../stores/hooks'
import { hasPermission } from '../helpers/userPermissions'
import { hasPermission } from "../helpers/userPermissions";
import { fetchWidgets } from '../stores/roles/rolesSlice';
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
const Dashboard = () => { const Dashboard = () => {
const iconsColor = useAppSelector((state) => state.style.iconsColor) const dispatch = useAppDispatch();
const corners = useAppSelector((state) => state.style.corners) const iconsColor = useAppSelector((state) => state.style.iconsColor);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle) const corners = useAppSelector((state) => state.style.corners);
const { currentUser } = useAppSelector((state) => state.auth) const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const loadingMessage = 'Loading...' const loadingMessage = 'Loading...';
const [leads, setLeads] = React.useState(loadingMessage)
const [projects, setProjects] = React.useState(loadingMessage) const [users, setUsers] = React.useState(loadingMessage);
const [tasks, setTasks] = React.useState(loadingMessage) const [roles, setRoles] = React.useState(loadingMessage);
const [permissions, setPermissions] = React.useState(loadingMessage);
const [leads, setLeads] = React.useState(loadingMessage);
const [projects, setProjects] = React.useState(loadingMessage);
const [tasks, setTasks] = React.useState(loadingMessage);
const [comments, setComments] = React.useState(loadingMessage);
const [attachments, setAttachments] = React.useState(loadingMessage);
async function loadData() {
const entities = ['leads', 'projects', 'tasks'] const [widgetsRole, setWidgetsRole] = React.useState({
const fns = [setLeads, setProjects, setTasks] role: { value: '', label: '' },
});
const { currentUser } = useAppSelector((state) => state.auth);
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
async function loadData() {
const entities = ['users','roles','permissions','leads','projects','tasks','comments','attachments',];
const fns = [setUsers,setRoles,setPermissions,setLeads,setProjects,setTasks,setComments,setAttachments,];
const requests = entities.map((entity, index) => { const requests = entities.map((entity, index) => {
if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
return axios.get(`/${entity.toLowerCase()}/count`) if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
} else { return axios.get(`/${entity.toLowerCase()}/count`);
fns[index](null) } else {
return Promise.resolve({ data: { count: null } }) fns[index](null);
} return Promise.resolve({data: {count: null}});
}) }
});
Promise.allSettled(requests).then((results) => { Promise.allSettled(requests).then((results) => {
results.forEach((result, i) => { results.forEach((result, i) => {
if (result.status === 'fulfilled') { if (result.status === 'fulfilled') {
fns[i](result.value.data.count) fns[i](result.value.data.count);
} else { } else {
fns[i](result.reason.message) fns[i](result.reason.message);
} }
}) });
}) });
} }
React.useEffect(() => { async function getWidgets(roleId) {
if (!currentUser) return await dispatch(fetchWidgets(roleId));
loadData().then() }
}, [currentUser]) React.useEffect(() => {
if (!currentUser) return;
loadData().then();
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]);
return ( return (
<> <>
<Head> <Head>
<title>{getPageTitle('Dashboard')}</title> <title>
{getPageTitle('Overview')}
</title>
</Head> </Head>
<SectionMain> <SectionMain>
<SectionTitleLineWithButton <SectionTitleLineWithButton
icon={icon.mdiChartTimelineVariant} icon={icon.mdiChartTimelineVariant}
title="TeamFlow PM Dashboard" title='Overview'
main main>
>
{''} {''}
</SectionTitleLineWithButton> </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 id="dashboard" className="grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6"> <div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
{hasPermission(currentUser, 'READ_LEADS') && ( {(isFetchingQuery || loading) && (
<Link href={'/leads/leads-list'}> <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`}>
<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">
Potential Leads
</div>
<div className="text-3xl leading-tight font-semibold">{leads}</div>
</div>
<div>
<BaseIcon <BaseIcon
className={`${iconsColor}`} className={`${iconsColor} animate-spin mr-5`}
w="w-16" w='w-16'
h="h-16" h='h-16'
size={48} size={48}
path={icon.mdiAccountMultiple} path={icon.mdiLoading}
/> />{' '}
</div> Loading widgets...
</div> </div>
</div> )}
</Link>
)}
{hasPermission(currentUser, 'READ_PROJECTS') && ( { rolesWidgets &&
<Link href={'/projects/projects-list'}> rolesWidgets.map((widget) => (
<div <SmartWidget
className={`${ key={widget.id}
corners !== 'rounded-full' ? corners : 'rounded-3xl' userId={currentUser?.id}
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`} widget={widget}
> roleId={widgetsRole?.role?.value || ''}
<div className="flex justify-between align-center"> admin={hasPermission(currentUser, 'CREATE_ROLES')}
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Active Projects
</div>
<div className="text-3xl leading-tight font-semibold">{projects}</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
path={icon.mdiFolder}
/> />
</div> ))}
</div> </div>
</div>
</Link>
)}
{hasPermission(currentUser, 'READ_TASKS') && ( {!!rolesWidgets.length && <hr className='my-6 text-pastelEmeraldTheme-mainBG ' />}
<Link href={'/tasks/tasks-list'}>
<div <div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
className={`${
corners !== 'rounded-full' ? corners : 'rounded-3xl'
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`} {hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
> <div
<div className="flex justify-between align-center"> className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
<div> >
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400"> <div className="flex justify-between align-center">
Open Tasks <div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Users
</div>
<div className="text-3xl leading-tight font-semibold">
{users}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiAccountGroup || icon.mdiTable}
/>
</div>
</div> </div>
<div className="text-3xl leading-tight font-semibold">{tasks}</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
path={icon.mdiFormatListBulleted}
/>
</div>
</div> </div>
</div> </Link>}
</Link>
)} {hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
<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">
Roles
</div>
<div className="text-3xl leading-tight font-semibold">
{roles}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
<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">
Permissions
</div>
<div className="text-3xl leading-tight font-semibold">
{permissions}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiShieldAccountOutline || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_LEADS') && <Link href={'/leads/leads-list'}>
<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">
Leads
</div>
<div className="text-3xl leading-tight font-semibold">
{leads}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PROJECTS') && <Link href={'/projects/projects-list'}>
<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">
Projects
</div>
<div className="text-3xl leading-tight font-semibold">
{projects}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFolder' in icon ? icon['mdiFolder' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TASKS') && <Link href={'/tasks/tasks-list'}>
<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">
Tasks
</div>
<div className="text-3xl leading-tight font-semibold">
{tasks}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_COMMENTS') && <Link href={'/comments/comments-list'}>
<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">
Comments
</div>
<div className="text-3xl leading-tight font-semibold">
{comments}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiCommentText' in icon ? icon['mdiCommentText' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_ATTACHMENTS') && <Link href={'/attachments/attachments-list'}>
<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">
Attachments
</div>
<div className="text-3xl leading-tight font-semibold">
{attachments}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiPaperclip' in icon ? icon['mdiPaperclip' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
</div> </div>
</SectionMain> </SectionMain>
</> </>
@ -161,4 +379,3 @@ Dashboard.getLayout = function getLayout(page: ReactElement) {
} }
export default Dashboard export default Dashboard

View File

@ -1,64 +0,0 @@
import { mdiChartTimelineVariant } from '@mdi/js'
import Head from 'next/head'
import React, { ReactElement } from 'react'
import CardBox from '../../components/CardBox'
import LayoutAuthenticated from '../../layouts/Authenticated'
import SectionMain from '../../components/SectionMain'
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
import { getPageTitle } from '../../config'
import BaseButton from '../../components/BaseButton'
import { useAppSelector } from '../../stores/hooks'
import { hasPermission } from '../../helpers/userPermissions'
import KanbanBoard from '../../components/KanbanBoard/KanbanBoard'
import Link from 'next/link'
import { deleteItem, update } from '../../stores/leads/leadsSlice'
const LeadsKanbanPage = () => {
const { currentUser } = useAppSelector((state) => state.auth)
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_LEADS')
const columns = [
{ id: 'New', label: 'New' },
{ id: 'Contacted', label: 'Contacted' },
{ id: 'Qualified', label: 'Qualified' },
{ id: 'Proposal', label: 'Proposal' },
{ id: 'Converted', label: 'Converted' },
{ id: 'Lost', label: 'Lost' },
]
return (
<>
<Head>
<title>{getPageTitle('Leads Kanban')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Leads Kanban" main>
{''}
</SectionTitleLineWithButton>
<CardBox className="mb-6" cardBoxClassName="flex flex-wrap">
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/leads/leads-new'} color="info" label="New Item" />}
<div className="md:inline-flex items-center ms-auto">
<Link href={'/leads/list'}>Switch to List</Link>
</div>
</CardBox>
<KanbanBoard
columns={columns}
entityName="leads"
columnFieldName="stage"
showFieldName="name"
deleteThunk={deleteItem}
updateThunk={update}
filtersQuery={'&orderBy=createdAt&orderDirection=asc'}
/>
</SectionMain>
</>
)
}
LeadsKanbanPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated permission={'READ_LEADS'}>{page}</LayoutAuthenticated>
}
export default LeadsKanbanPage

View File

@ -117,7 +117,7 @@ const LeadsTablesPage = () => {
</div> </div>
<div className='md:inline-flex items-center ms-auto'> <div className='md:inline-flex items-center ms-auto'>
<Link href={'/leads/kanban'}>Switch to Kanban</Link> <Link href={'/leads/leads-table'}>Switch to Table</Link>
</div> </div>
</CardBox> </CardBox>

View File

@ -68,7 +68,7 @@ module.exports = {
pastelEmeraldTheme: { pastelEmeraldTheme: {
text: '#030A0D', text: '#515564',
iconsColor: '#030A0D', iconsColor: '#030A0D',
mainBG: '#DFECF2', mainBG: '#DFECF2',
buttonColor: '#030A0D', buttonColor: '#030A0D',