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 && {
|
...(column.isSticky && {
|
||||||
position: 'sticky' as const,
|
position: 'sticky' as const,
|
||||||
left: leftPosition,
|
left: leftPosition,
|
||||||
zIndex: 5,
|
zIndex: 10,
|
||||||
backgroundColor: 'inherit', // Inherit from parent spacer
|
backgroundColor: 'inherit', // Inherit from parent spacer
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -161,7 +161,7 @@ const EmptyGroupMessage: React.FC<{ visibleColumns: any[]; isDarkMode?: boolean
|
|||||||
...(column.isSticky && {
|
...(column.isSticky && {
|
||||||
position: 'sticky' as const,
|
position: 'sticky' as const,
|
||||||
left: leftPosition,
|
left: leftPosition,
|
||||||
zIndex: 5,
|
zIndex: 10,
|
||||||
backgroundColor: 'inherit', // Inherit from parent container
|
backgroundColor: 'inherit', // Inherit from parent container
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -668,7 +668,7 @@ const TaskListV2Section: React.FC = () => {
|
|||||||
...(column.isSticky && {
|
...(column.isSticky && {
|
||||||
position: 'sticky' as const,
|
position: 'sticky' as const,
|
||||||
left: leftPosition,
|
left: leftPosition,
|
||||||
zIndex: 10,
|
zIndex: 15,
|
||||||
backgroundColor: isDarkMode ? '#141414' : '#f9fafb', // custom dark header : bg-gray-50
|
backgroundColor: isDarkMode ? '#141414' : '#f9fafb', // custom dark header : bg-gray-50
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,62 +1,18 @@
|
|||||||
import { Col, ConfigProvider, Layout } from '@/shared/antd-imports';
|
import { ConfigProvider, Layout } from '@/shared/antd-imports';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
import { memo, useMemo, useEffect, useRef } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useMediaQuery } from 'react-responsive';
|
|
||||||
|
|
||||||
import Navbar from '../features/navbar/navbar';
|
import Navbar from '../features/navbar/navbar';
|
||||||
import { useAppSelector } from '../hooks/useAppSelector';
|
import { useAppSelector } from '../hooks/useAppSelector';
|
||||||
import { colors } from '../styles/colors';
|
import { colors } from '../styles/colors';
|
||||||
|
|
||||||
import { useRenderPerformance } from '@/utils/performance';
|
|
||||||
import { DynamicCSSLoader, LayoutStabilizer } from '@/utils/css-optimizations';
|
|
||||||
|
|
||||||
const MainLayout = memo(() => {
|
const MainLayout = memo(() => {
|
||||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||||
const isDesktop = useMediaQuery({ query: '(min-width: 1024px)' });
|
const location = useLocation();
|
||||||
const layoutRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// Performance monitoring in development
|
|
||||||
useRenderPerformance('MainLayout');
|
|
||||||
|
|
||||||
// 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'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
|
const isProjectView = location.pathname.includes('/projects/') &&
|
||||||
|
!location.pathname.endsWith('/projects');
|
||||||
|
|
||||||
// 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(
|
const themeConfig = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
components: {
|
components: {
|
||||||
@@ -69,23 +25,19 @@ const MainLayout = memo(() => {
|
|||||||
[themeMode]
|
[themeMode]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Memoize header className
|
|
||||||
const headerClassName = useMemo(
|
|
||||||
() => `shadow-md ${themeMode === 'dark' ? '' : 'shadow-[#18181811]'}`,
|
|
||||||
[themeMode]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={themeConfig}>
|
<ConfigProvider theme={themeConfig}>
|
||||||
<Layout ref={layoutRef} style={{ minHeight: '100vh' }} className="prevent-layout-shift">
|
<Layout className="min-h-screen">
|
||||||
<Layout.Header className={`${headerClassName} gpu-accelerated`} style={headerStyles}>
|
<Layout.Header
|
||||||
|
className={`sticky top-0 z-[999] flex items-center p-0 shadow-md ${
|
||||||
|
themeMode === 'dark' ? 'border-b border-[#303030]' : 'shadow-[#18181811]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
</Layout.Header>
|
</Layout.Header>
|
||||||
|
|
||||||
<Layout.Content className="layout-contained">
|
<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`}>
|
||||||
<Col xxl={{ span: 18, offset: 3, flex: '100%' }} style={contentStyles} className="task-content-container">
|
<Outlet />
|
||||||
<Outlet />
|
|
||||||
</Col>
|
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ const HomePage = memo(() => {
|
|||||||
}, [isDesktop, isOwnerOrAdmin]);
|
}, [isDesktop, isOwnerOrAdmin]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="my-24 min-h-[90vh]">
|
<div className="my-8 min-h-[90vh]">
|
||||||
<Col className="flex flex-col gap-6">
|
<Col className="flex flex-col gap-6">
|
||||||
<GreetingWithTime />
|
<GreetingWithTime />
|
||||||
{CreateProjectButtonComponent}
|
{CreateProjectButtonComponent}
|
||||||
@@ -113,13 +113,13 @@ const HomePage = memo(() => {
|
|||||||
<Col xs={24} lg={16}>
|
<Col xs={24} lg={16}>
|
||||||
<Flex vertical gap={24}>
|
<Flex vertical gap={24}>
|
||||||
<TasksList />
|
<TasksList />
|
||||||
|
|
||||||
<TodoList />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col xs={24} lg={8}>
|
<Col xs={24} lg={8}>
|
||||||
<Flex vertical gap={24}>
|
<Flex vertical gap={24}>
|
||||||
|
<TodoList />
|
||||||
|
|
||||||
<UserActivityFeed />
|
<UserActivityFeed />
|
||||||
|
|
||||||
<RecentAndFavouriteProjectList />
|
<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 { useRef, useState } from 'react';
|
||||||
import Form from 'antd/es/form';
|
import Form from 'antd/es/form';
|
||||||
import Input, { InputRef } from 'antd/es/input';
|
import Input, { InputRef } from 'antd/es/input';
|
||||||
import Flex from 'antd/es/flex';
|
import Flex from 'antd/es/flex';
|
||||||
import Card from 'antd/es/card';
|
import Card from 'antd/es/card';
|
||||||
|
import Collapse from 'antd/es/collapse';
|
||||||
import ConfigProvider from 'antd/es/config-provider';
|
import ConfigProvider from 'antd/es/config-provider';
|
||||||
import Table, { TableProps } from 'antd/es/table';
|
import Table, { TableProps } from 'antd/es/table';
|
||||||
import Tooltip from 'antd/es/tooltip';
|
import Tooltip from 'antd/es/tooltip';
|
||||||
@@ -23,6 +24,7 @@ import { useCreatePersonalTaskMutation } from '@/api/home-page/home-page.api.ser
|
|||||||
|
|
||||||
const TodoList = () => {
|
const TodoList = () => {
|
||||||
const [isAlertShowing, setIsAlertShowing] = useState(false);
|
const [isAlertShowing, setIsAlertShowing] = useState(false);
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { t } = useTranslation('home');
|
const { t } = useTranslation('home');
|
||||||
|
|
||||||
@@ -97,73 +99,109 @@ const TodoList = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card style={{ width: '100%' }} bodyStyle={{ padding: 0 }}>
|
||||||
title={
|
<style>{`
|
||||||
<Typography.Title level={5} style={{ marginBlockEnd: 0 }}>
|
.todo-collapse .ant-collapse-header {
|
||||||
{t('home:todoList.title')} ({data?.body.length})
|
display: flex !important;
|
||||||
</Typography.Title>
|
align-items: center !important;
|
||||||
}
|
padding: 12px 16px !important;
|
||||||
extra={
|
}
|
||||||
<Tooltip title={t('home:todoList.refreshTasks')}>
|
.todo-collapse .ant-collapse-expand-icon {
|
||||||
<Button shape="circle" icon={<SyncOutlined spin={isFetching} />} onClick={refetch} />
|
margin-right: 8px !important;
|
||||||
</Tooltip>
|
display: flex !important;
|
||||||
}
|
align-items: center !important;
|
||||||
style={{ width: '100%' }}
|
}
|
||||||
>
|
`}</style>
|
||||||
<div>
|
<Collapse
|
||||||
<Form form={form} onFinish={handleTodoSubmit}>
|
defaultActiveKey={[]}
|
||||||
<Form.Item name="name">
|
ghost
|
||||||
<Flex vertical>
|
size="small"
|
||||||
<Input
|
className="todo-collapse"
|
||||||
ref={todoInputRef}
|
expandIcon={({ isActive }) =>
|
||||||
placeholder={t('home:todoList.addTask')}
|
isActive ? <DownOutlined /> : <RightOutlined />
|
||||||
onChange={e => {
|
}
|
||||||
const inputValue = e.currentTarget.value;
|
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);
|
if (inputValue.length >= 1) setIsAlertShowing(true);
|
||||||
else if (inputValue === '') setIsAlertShowing(false);
|
else if (inputValue === '') setIsAlertShowing(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isAlertShowing && (
|
{isAlertShowing && (
|
||||||
<Alert
|
<Alert
|
||||||
message={
|
message={
|
||||||
<Typography.Text style={{ fontSize: 11 }}>
|
<Typography.Text style={{ fontSize: 11 }}>
|
||||||
{t('home:todoList.pressEnter')} <strong>Enter</strong>{' '}
|
{t('home:todoList.pressEnter')} <strong>Enter</strong>{' '}
|
||||||
{t('home:todoList.toCreate')}
|
{t('home:todoList.toCreate')}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
}
|
}
|
||||||
type="info"
|
type="info"
|
||||||
style={{
|
style={{
|
||||||
width: 'fit-content',
|
width: 'fit-content',
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
padding: '0 6px',
|
padding: '0 6px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<div style={{ maxHeight: 300, overflow: 'auto' }}>
|
<div style={{ maxHeight: 300, overflow: 'auto' }}>
|
||||||
{data?.body.length === 0 ? (
|
{data?.body.length === 0 ? (
|
||||||
<EmptyListPlaceholder
|
<EmptyListPlaceholder
|
||||||
imageSrc="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
|
imageSrc="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
|
||||||
text={t('home:todoList.noTasks')}
|
text={t('home:todoList.noTasks')}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Table
|
<Table
|
||||||
className="custom-two-colors-row-table"
|
className="custom-two-colors-row-table"
|
||||||
rowKey={record => record.id || ''}
|
rowKey={record => record.id || ''}
|
||||||
dataSource={data?.body}
|
dataSource={data?.body}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
loading={isFetching}
|
loading={isFetching}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -802,7 +802,7 @@ const ProjectList: React.FC = () => {
|
|||||||
}, [loadingProjects, isFetchingProjects, viewMode, groupedProjects.loading, isLoading]);
|
}, [loadingProjects, isFetchingProjects, viewMode, groupedProjects.loading, isLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBlock: 65, minHeight: '90vh' }}>
|
<div style={{ minHeight: '90vh' }}>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
className="site-page-header"
|
className="site-page-header"
|
||||||
title={`${projectCount} ${t('projects')}`}
|
title={`${projectCount} ${t('projects')}`}
|
||||||
|
|||||||
@@ -360,14 +360,14 @@ const ProjectView = React.memo(() => {
|
|||||||
// Show loading state while project is being fetched or translations are loading
|
// Show loading state while project is being fetched or translations are loading
|
||||||
if (projectLoading || !isInitialized || !translationsReady) {
|
if (projectLoading || !isInitialized || !translationsReady) {
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBlockStart: 70, marginBlockEnd: 12, minHeight: '80vh' }}>
|
<div style={{ marginBlockEnd: 12, minHeight: '80vh' }}>
|
||||||
<SuspenseFallback />
|
<SuspenseFallback />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBlockStart: 70, marginBlockEnd: 12, minHeight: '80vh' }}>
|
<div style={{ marginBlockEnd: 12, minHeight: '80vh' }}>
|
||||||
<ProjectViewHeader />
|
<ProjectViewHeader />
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const Schedule: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBlockStart: 65, minHeight: '90vh' }}>
|
<div style={{ minHeight: '90vh' }}>
|
||||||
<Flex align="center" justify="space-between">
|
<Flex align="center" justify="space-between">
|
||||||
<Flex
|
<Flex
|
||||||
gap={16}
|
gap={16}
|
||||||
|
|||||||
Reference in New Issue
Block a user