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:
chamikaJ
2025-07-30 17:20:20 +05:30
parent 374595261f
commit 2bd6c19c13
7 changed files with 128 additions and 138 deletions

View File

@@ -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
}), }),
}; };

View File

@@ -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>

View File

@@ -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 />

View File

@@ -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>
); );
}; };

View File

@@ -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')}`}

View File

@@ -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

View File

@@ -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}