lavoce #1
15
src/layout/LayoutBreadcrumb.jsx
Normal file
15
src/layout/LayoutBreadcrumb.jsx
Normal 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);
|
||||
18
src/layout/LayoutFooter.jsx
Normal file
18
src/layout/LayoutFooter.jsx
Normal 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
119
src/layout/LayoutHeader.jsx
Normal 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
69
src/layout/LayoutLogo.jsx
Normal 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
110
src/layout/LayoutMenu.jsx
Normal 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;
|
||||
25
src/layout/LayoutSidebar.jsx
Normal file
25
src/layout/LayoutSidebar.jsx
Normal 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
46
src/layout/MainLayout.jsx
Normal 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;
|
||||
Reference in New Issue
Block a user