refactor(layouts): simplify MainLayout and enhance styling
- Removed unused imports and performance monitoring hooks from MainLayout. - Updated layout structure to improve responsiveness and styling, including sticky header and optimized content padding. - Adjusted home page layout to reduce margin and improve spacing for better visual consistency. - Enhanced TodoList component with collapsible sections for improved user interaction and task management. - Streamlined project and schedule pages by removing unnecessary margin adjustments, ensuring a cleaner layout.
This commit is contained in:
@@ -105,7 +105,7 @@ const DropSpacer: React.FC<{ isVisible: boolean; visibleColumns: any[]; isDarkMo
|
||||
...(column.isSticky && {
|
||||
position: 'sticky' as const,
|
||||
left: leftPosition,
|
||||
zIndex: 5,
|
||||
zIndex: 10,
|
||||
backgroundColor: 'inherit', // Inherit from parent spacer
|
||||
}),
|
||||
};
|
||||
@@ -161,7 +161,7 @@ const EmptyGroupMessage: React.FC<{ visibleColumns: any[]; isDarkMode?: boolean
|
||||
...(column.isSticky && {
|
||||
position: 'sticky' as const,
|
||||
left: leftPosition,
|
||||
zIndex: 5,
|
||||
zIndex: 10,
|
||||
backgroundColor: 'inherit', // Inherit from parent container
|
||||
}),
|
||||
};
|
||||
@@ -668,7 +668,7 @@ const TaskListV2Section: React.FC = () => {
|
||||
...(column.isSticky && {
|
||||
position: 'sticky' as const,
|
||||
left: leftPosition,
|
||||
zIndex: 10,
|
||||
zIndex: 15,
|
||||
backgroundColor: isDarkMode ? '#141414' : '#f9fafb', // custom dark header : bg-gray-50
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,62 +1,18 @@
|
||||
import { Col, ConfigProvider, Layout } from '@/shared/antd-imports';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { memo, useMemo, useEffect, useRef } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
import { ConfigProvider, Layout } from '@/shared/antd-imports';
|
||||
import { Outlet, useLocation } from 'react-router-dom';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
import Navbar from '../features/navbar/navbar';
|
||||
import { useAppSelector } from '../hooks/useAppSelector';
|
||||
import { colors } from '../styles/colors';
|
||||
|
||||
import { useRenderPerformance } from '@/utils/performance';
|
||||
import { DynamicCSSLoader, LayoutStabilizer } from '@/utils/css-optimizations';
|
||||
|
||||
const MainLayout = memo(() => {
|
||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||
const isDesktop = useMediaQuery({ query: '(min-width: 1024px)' });
|
||||
const layoutRef = useRef<HTMLDivElement>(null);
|
||||
const location = useLocation();
|
||||
|
||||
// Performance monitoring in development
|
||||
useRenderPerformance('MainLayout');
|
||||
const isProjectView = location.pathname.includes('/projects/') &&
|
||||
!location.pathname.endsWith('/projects');
|
||||
|
||||
// Apply layout optimizations
|
||||
useEffect(() => {
|
||||
if (layoutRef.current) {
|
||||
// Prevent layout shifts in main content area
|
||||
LayoutStabilizer.applyContainment(layoutRef.current, 'layout');
|
||||
|
||||
// Load non-critical CSS dynamically
|
||||
DynamicCSSLoader.loadCSS('/styles/non-critical.css', {
|
||||
priority: 'low',
|
||||
media: 'all'
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
// Memoize styles to prevent object recreation on every render
|
||||
const headerStyles = useMemo(
|
||||
() => ({
|
||||
zIndex: 999,
|
||||
position: 'fixed' as const,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: 0,
|
||||
borderBottom: themeMode === 'dark' ? '1px solid #303030' : 'none',
|
||||
}),
|
||||
[themeMode]
|
||||
);
|
||||
|
||||
const contentStyles = useMemo(
|
||||
() => ({
|
||||
paddingInline: isDesktop ? 64 : 24,
|
||||
overflowX: 'hidden' as const,
|
||||
}),
|
||||
[isDesktop]
|
||||
);
|
||||
|
||||
// Memoize theme configuration
|
||||
const themeConfig = useMemo(
|
||||
() => ({
|
||||
components: {
|
||||
@@ -69,23 +25,19 @@ const MainLayout = memo(() => {
|
||||
[themeMode]
|
||||
);
|
||||
|
||||
// Memoize header className
|
||||
const headerClassName = useMemo(
|
||||
() => `shadow-md ${themeMode === 'dark' ? '' : 'shadow-[#18181811]'}`,
|
||||
[themeMode]
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<Layout ref={layoutRef} style={{ minHeight: '100vh' }} className="prevent-layout-shift">
|
||||
<Layout.Header className={`${headerClassName} gpu-accelerated`} style={headerStyles}>
|
||||
<Layout className="min-h-screen">
|
||||
<Layout.Header
|
||||
className={`sticky top-0 z-[999] flex items-center p-0 shadow-md ${
|
||||
themeMode === 'dark' ? 'border-b border-[#303030]' : 'shadow-[#18181811]'
|
||||
}`}
|
||||
>
|
||||
<Navbar />
|
||||
</Layout.Header>
|
||||
|
||||
<Layout.Content className="layout-contained">
|
||||
<Col xxl={{ span: 18, offset: 3, flex: '100%' }} style={contentStyles} className="task-content-container">
|
||||
<Outlet />
|
||||
</Col>
|
||||
<Layout.Content className={`px-4 sm:px-8 lg:px-12 xl:px-16 ${!isProjectView ? 'overflow-x-hidden max-w-[1400px]' : ''} mx-auto w-full`}>
|
||||
<Outlet />
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</ConfigProvider>
|
||||
|
||||
@@ -103,7 +103,7 @@ const HomePage = memo(() => {
|
||||
}, [isDesktop, isOwnerOrAdmin]);
|
||||
|
||||
return (
|
||||
<div className="my-24 min-h-[90vh]">
|
||||
<div className="my-8 min-h-[90vh]">
|
||||
<Col className="flex flex-col gap-6">
|
||||
<GreetingWithTime />
|
||||
{CreateProjectButtonComponent}
|
||||
@@ -113,13 +113,13 @@ const HomePage = memo(() => {
|
||||
<Col xs={24} lg={16}>
|
||||
<Flex vertical gap={24}>
|
||||
<TasksList />
|
||||
|
||||
<TodoList />
|
||||
</Flex>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} lg={8}>
|
||||
<Flex vertical gap={24}>
|
||||
<TodoList />
|
||||
|
||||
<UserActivityFeed />
|
||||
|
||||
<RecentAndFavouriteProjectList />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { CheckCircleOutlined, SyncOutlined } from '@/shared/antd-imports';
|
||||
import { CheckCircleOutlined, SyncOutlined, DownOutlined, RightOutlined } from '@/shared/antd-imports';
|
||||
import { useRef, useState } from 'react';
|
||||
import Form from 'antd/es/form';
|
||||
import Input, { InputRef } from 'antd/es/input';
|
||||
import Flex from 'antd/es/flex';
|
||||
import Card from 'antd/es/card';
|
||||
import Collapse from 'antd/es/collapse';
|
||||
import ConfigProvider from 'antd/es/config-provider';
|
||||
import Table, { TableProps } from 'antd/es/table';
|
||||
import Tooltip from 'antd/es/tooltip';
|
||||
@@ -23,6 +24,7 @@ import { useCreatePersonalTaskMutation } from '@/api/home-page/home-page.api.ser
|
||||
|
||||
const TodoList = () => {
|
||||
const [isAlertShowing, setIsAlertShowing] = useState(false);
|
||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation('home');
|
||||
|
||||
@@ -97,73 +99,109 @@ const TodoList = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<Typography.Title level={5} style={{ marginBlockEnd: 0 }}>
|
||||
{t('home:todoList.title')} ({data?.body.length})
|
||||
</Typography.Title>
|
||||
}
|
||||
extra={
|
||||
<Tooltip title={t('home:todoList.refreshTasks')}>
|
||||
<Button shape="circle" icon={<SyncOutlined spin={isFetching} />} onClick={refetch} />
|
||||
</Tooltip>
|
||||
}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<div>
|
||||
<Form form={form} onFinish={handleTodoSubmit}>
|
||||
<Form.Item name="name">
|
||||
<Flex vertical>
|
||||
<Input
|
||||
ref={todoInputRef}
|
||||
placeholder={t('home:todoList.addTask')}
|
||||
onChange={e => {
|
||||
const inputValue = e.currentTarget.value;
|
||||
<Card style={{ width: '100%' }} bodyStyle={{ padding: 0 }}>
|
||||
<style>{`
|
||||
.todo-collapse .ant-collapse-header {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
.todo-collapse .ant-collapse-expand-icon {
|
||||
margin-right: 8px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
`}</style>
|
||||
<Collapse
|
||||
defaultActiveKey={[]}
|
||||
ghost
|
||||
size="small"
|
||||
className="todo-collapse"
|
||||
expandIcon={({ isActive }) =>
|
||||
isActive ? <DownOutlined /> : <RightOutlined />
|
||||
}
|
||||
onChange={(keys) => {
|
||||
setIsCollapsed(keys.length === 0);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<Flex style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
{t('home:todoList.title')} ({data?.body.length})
|
||||
</Typography.Title>
|
||||
<Tooltip title={t('home:todoList.refreshTasks')}>
|
||||
<Button
|
||||
shape="circle"
|
||||
icon={<SyncOutlined spin={isFetching} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
children: (
|
||||
<div style={{ padding: '0 16px 16px 16px' }}>
|
||||
<Form form={form} onFinish={handleTodoSubmit}>
|
||||
<Form.Item name="name">
|
||||
<Flex vertical>
|
||||
<Input
|
||||
ref={todoInputRef}
|
||||
placeholder={t('home:todoList.addTask')}
|
||||
onChange={e => {
|
||||
const inputValue = e.currentTarget.value;
|
||||
|
||||
if (inputValue.length >= 1) setIsAlertShowing(true);
|
||||
else if (inputValue === '') setIsAlertShowing(false);
|
||||
}}
|
||||
/>
|
||||
{isAlertShowing && (
|
||||
<Alert
|
||||
message={
|
||||
<Typography.Text style={{ fontSize: 11 }}>
|
||||
{t('home:todoList.pressEnter')} <strong>Enter</strong>{' '}
|
||||
{t('home:todoList.toCreate')}
|
||||
</Typography.Text>
|
||||
}
|
||||
type="info"
|
||||
style={{
|
||||
width: 'fit-content',
|
||||
borderRadius: 2,
|
||||
padding: '0 6px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
if (inputValue.length >= 1) setIsAlertShowing(true);
|
||||
else if (inputValue === '') setIsAlertShowing(false);
|
||||
}}
|
||||
/>
|
||||
{isAlertShowing && (
|
||||
<Alert
|
||||
message={
|
||||
<Typography.Text style={{ fontSize: 11 }}>
|
||||
{t('home:todoList.pressEnter')} <strong>Enter</strong>{' '}
|
||||
{t('home:todoList.toCreate')}
|
||||
</Typography.Text>
|
||||
}
|
||||
type="info"
|
||||
style={{
|
||||
width: 'fit-content',
|
||||
borderRadius: 2,
|
||||
padding: '0 6px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<div style={{ maxHeight: 300, overflow: 'auto' }}>
|
||||
{data?.body.length === 0 ? (
|
||||
<EmptyListPlaceholder
|
||||
imageSrc="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
|
||||
text={t('home:todoList.noTasks')}
|
||||
/>
|
||||
) : (
|
||||
<Table
|
||||
className="custom-two-colors-row-table"
|
||||
rowKey={record => record.id || ''}
|
||||
dataSource={data?.body}
|
||||
columns={columns}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
size="small"
|
||||
loading={isFetching}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ maxHeight: 300, overflow: 'auto' }}>
|
||||
{data?.body.length === 0 ? (
|
||||
<EmptyListPlaceholder
|
||||
imageSrc="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
|
||||
text={t('home:todoList.noTasks')}
|
||||
/>
|
||||
) : (
|
||||
<Table
|
||||
className="custom-two-colors-row-table"
|
||||
rowKey={record => record.id || ''}
|
||||
dataSource={data?.body}
|
||||
columns={columns}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
size="small"
|
||||
loading={isFetching}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -802,7 +802,7 @@ const ProjectList: React.FC = () => {
|
||||
}, [loadingProjects, isFetchingProjects, viewMode, groupedProjects.loading, isLoading]);
|
||||
|
||||
return (
|
||||
<div style={{ marginBlock: 65, minHeight: '90vh' }}>
|
||||
<div style={{ minHeight: '90vh' }}>
|
||||
<PageHeader
|
||||
className="site-page-header"
|
||||
title={`${projectCount} ${t('projects')}`}
|
||||
|
||||
@@ -360,14 +360,14 @@ const ProjectView = React.memo(() => {
|
||||
// Show loading state while project is being fetched or translations are loading
|
||||
if (projectLoading || !isInitialized || !translationsReady) {
|
||||
return (
|
||||
<div style={{ marginBlockStart: 70, marginBlockEnd: 12, minHeight: '80vh' }}>
|
||||
<div style={{ marginBlockEnd: 12, minHeight: '80vh' }}>
|
||||
<SuspenseFallback />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBlockStart: 70, marginBlockEnd: 12, minHeight: '80vh' }}>
|
||||
<div style={{ marginBlockEnd: 12, minHeight: '80vh' }}>
|
||||
<ProjectViewHeader />
|
||||
|
||||
<Tabs
|
||||
|
||||
@@ -54,7 +54,7 @@ const Schedule: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginBlockStart: 65, minHeight: '90vh' }}>
|
||||
<div style={{ minHeight: '90vh' }}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Flex
|
||||
gap={16}
|
||||
|
||||
Reference in New Issue
Block a user