Add layout

This commit is contained in:
2025-09-17 12:17:02 +07:00
parent 291cbd5f94
commit b25aaf124c
7 changed files with 402 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
import React, { createContext, useContext, useState } from 'react';
const BreadcrumbContext = createContext();
export const BreadcrumbProvider = ({ children }) => {
const [breadcrumbItems, setBreadcrumbItems] = useState([]);
return (
<BreadcrumbContext.Provider value={{ breadcrumbItems, setBreadcrumbItems }}>
{children}
</BreadcrumbContext.Provider>
);
};
export const useBreadcrumb = () => useContext(BreadcrumbContext);

View File

@@ -0,0 +1,18 @@
import React from 'react'
import { Layout } from 'antd'
import Link from 'antd/es/typography/Link';
const { Footer } = Layout;
const LayoutFooter = () => {
return (
<Footer
style={{
textAlign: 'center',
}}
>
{/* SYPIU GGCP */}
</Footer>
)
}
export default LayoutFooter

119
src/layout/LayoutHeader.jsx Normal file
View File

@@ -0,0 +1,119 @@
import React from 'react';
import { Layout, theme, Space, Typography, Breadcrumb, Button } from 'antd';
import { UserOutlined } from '@ant-design/icons';
import handleLogOut from '../Utils/Auth/Logout';
import { useBreadcrumb } from './LayoutBreadcrumb';
import { decryptData } from '../components/Global/Formatter';
import { useNavigate } from 'react-router-dom';
const { Link, Text } = Typography;
const { Header } = Layout;
const LayoutHeader = () => {
const { breadcrumbItems } = useBreadcrumb();
const navigate = useNavigate();
const {
token: { colorBgContainer, colorBorder, colorText },
} = theme.useToken();
// Ambil data user dari localStorage dan dekripsi
const sessionData = localStorage.getItem('session');
const userData = sessionData ? decryptData(sessionData) : null;
// console.log(userData);
const roleName = userData?.user?.approval || userData?.user?.partner_name || 'Guest';
const userName = userData?.user?.name || userData?.user?.username || 'User';
return (
<>
<Header
style={{
background: colorBgContainer,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'wrap',
rowGap: 10,
paddingTop:15,
paddingBottom: 20,
paddingLeft: 24,
paddingRight: 24,
minHeight: 100,
boxSizing: 'border-box',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
gap: 12,
}}
>
<Text
style={{
color: colorText,
fontSize: 16,
fontWeight: 'bold',
whiteSpace: 'nowrap',
}}
>
Login AS {roleName}
</Text>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
gap: 10,
}}
>
<Button
style={{
display: 'flex',
alignItems: 'center',
background: '#f5f5f5',
border: `1px solid ${colorBorder}`,
borderRadius: 6,
padding: '4px 12px',
}}
>
<UserOutlined style={{ fontSize: 16, color: colorText }} />
<Text style={{ marginLeft: 8, color: colorText }} strong>
{userName}
</Text>
</Button>
<Link
onClick={() => {
handleLogOut();
navigate('/signin');
}}
aria-label="Log out from the application"
style={{
color: colorText,
whiteSpace: 'nowrap',
}}
>
Logout
</Link>
</div>
</Header>
<div style={{ width: '100%', maxWidth: '50%', textAlign: 'left' }}>
<Breadcrumb
style={{
marginLeft: '20px',
marginTop: '20px',
marginBottom: '10px',
}}
items={breadcrumbItems || []}
/>
</div>
</>
);
};
export default LayoutHeader;

69
src/layout/LayoutLogo.jsx Normal file
View File

@@ -0,0 +1,69 @@
import { Image } from 'antd';
import logoPiu from '../assets/freepik/LOGOPIU.png';
import React from 'react';
const LayoutLogo = () => {
return (
<div
style={{
margin: '1rem auto',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#001529',
padding: '1rem',
borderRadius: '1rem',
}}
>
<div
style={{
background: 'radial-gradient(circle at center, rgba(255,255,255,0.95), rgba(220,220,220,0.5))',
borderRadius: '50%',
width: 160,
height: 160,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
boxShadow: '0 6px 20px rgba(0, 0, 0, 0.2)',
position: 'relative',
overflow: 'hidden'
}}
>
{/* Ring sebelum logo utama */}
<div
style={{
border: '5px solid #ffffffff',
borderRadius: '50%',
width: 160,
height: 160,
position: 'absolute',
zIndex: 1
}}
/>
<div
style={{
borderRadius: '10px',
padding: '4px',
mixBlendMode: 'normal',
opacity: 1,
zIndex: 2,
}}
>
<Image
src={logoPiu}
alt="logo"
width={140}
height={100}
preview={false}
style={{
filter: 'drop-shadow(0 0 3px rgba(0, 0, 0, 0.2))',
}}
/>
</div>
</div>
</div>
);
};
export default LayoutLogo;

110
src/layout/LayoutMenu.jsx Normal file
View File

@@ -0,0 +1,110 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Menu, Typography, Image } from 'antd';
import { getSessionData } from '../components/Global/Formatter';
import { HomeOutlined,
DatabaseOutlined,
SettingOutlined,
UserOutlined,
AntDesignOutlined,
ShoppingCartOutlined,
ShoppingOutlined,
HistoryOutlined,
DollarOutlined,
RollbackOutlined,
ProductOutlined
} from '@ant-design/icons';
const { Text } = Typography;
const allItems = [
{
key: 'home',
icon: <HomeOutlined style={{fontSize:'19px'}} />,
label: <Link to="/dashboard/home" className='fontMenus'>Home</Link>,
},
{
key: 'master',
icon: <DatabaseOutlined style={{fontSize:'19px'}} />,
label: 'Master',
children: [
{
key: 'master-device',
icon: <DatabaseOutlined style={{fontSize:'19px'}} />,
label: <Link to="/master/device">Device</Link>,
},
],
},
];
const LayoutMenu = () => {
const [stateOpenKeys, setStateOpenKeys] = useState(['home']);
const getLevelKeys = items1 => {
const key = {};
const func = (items2, level = 1) => {
items2.forEach(item => {
if (item.key) {
key[item.key] = level;
}
if (item.children) {
func(item.children, level + 1);
}
});
};
func(items1);
return key;
};
const levelKeys = getLevelKeys(allItems);
const onOpenChange = openKeys => {
const currentOpenKey = openKeys.find(key => stateOpenKeys.indexOf(key) === -1);
if (currentOpenKey !== undefined) {
const repeatIndex = openKeys.filter(key => key !== currentOpenKey).findIndex(key => levelKeys[key] === levelKeys[currentOpenKey]);
setStateOpenKeys(
openKeys.filter((_, index) => index !== repeatIndex).filter(key => levelKeys[key] <= levelKeys[currentOpenKey]),
);
} else {
setStateOpenKeys(openKeys);
}
};
const session = getSessionData();
const isAdmin = session?.user?.user_id;
const karyawan = ()=>{
return allItems.filter(
item => item.key !== 'setting'
// tambahkan menu jika terdapat menu yang di sembunyikan dari user karyawan
// && item.key !== 'master'
// && item.key !== 'master'
).map(item=>{
if(item.key === 'master'){
return{
...item,
// buka command dibawah jika terdapat sub menu yang di sembunyikan
// children: item.children.filter(
// child => child.key !== 'master-product'
// tambahkan menu jika terdapat menu yang di sembunyikan dari user karyawan
// && child.key !== 'master-service'
// )
}
}
return item;
});
};
const items = isAdmin === 1 ? allItems : karyawan();
return (
<Menu
theme="dark"
mode="inline"
items={items}
defaultSelectedKeys={['home']}
openKeys={stateOpenKeys}
onOpenChange={onOpenChange}
/>
);
};
export default LayoutMenu;

View File

@@ -0,0 +1,25 @@
import React from 'react'
import { Layout } from 'antd';
import LayoutLogo from './LayoutLogo';
import LayoutMenu from './LayoutMenu';
const { Sider } = Layout;
const LayoutSidebar = () => {
return (
<Sider width={300}
breakpoint="lg"
collapsedWidth="0"
onBreakpoint={(broken) => {
// console.log(broken);
}}
onCollapse={(collapsed, type) => {
// console.log(collapsed, type);
}}
>
<LayoutLogo />
<LayoutMenu />
</Sider>
)
}
export default LayoutSidebar

46
src/layout/MainLayout.jsx Normal file
View File

@@ -0,0 +1,46 @@
import React from 'react';
import { Layout, theme } from 'antd';
import LayoutFooter from './LayoutFooter';
import LayoutHeader from './LayoutHeader';
import LayoutSidebar from './LayoutSidebar';
const { Content } = Layout;
const MainLayout = ({ children }) => {
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<Layout style={{ height: '100vh' }}>
<LayoutSidebar />
<Layout
style={{
overflow: 'auto',
}}>
<LayoutHeader />
<Content
style={{
margin: '24px 16px 0',
flex: '1 0 auto',
}}
>
{/* <div
style={{
padding: 24,
minHeight: '100%',
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
> */}
{children}
{/* </div> */}
</Content>
{/* <LayoutFooter /> */}
</Layout>
</Layout>
);
};
export default MainLayout;