From b25aaf124c30fe3f6febd4427bef61719e691b33 Mon Sep 17 00:00:00 2001 From: bragaz_rexita Date: Wed, 17 Sep 2025 12:17:02 +0700 Subject: [PATCH] Add layout --- src/layout/LayoutBreadcrumb.jsx | 15 ++++ src/layout/LayoutFooter.jsx | 18 +++++ src/layout/LayoutHeader.jsx | 119 ++++++++++++++++++++++++++++++++ src/layout/LayoutLogo.jsx | 69 ++++++++++++++++++ src/layout/LayoutMenu.jsx | 110 +++++++++++++++++++++++++++++ src/layout/LayoutSidebar.jsx | 25 +++++++ src/layout/MainLayout.jsx | 46 ++++++++++++ 7 files changed, 402 insertions(+) create mode 100644 src/layout/LayoutBreadcrumb.jsx create mode 100644 src/layout/LayoutFooter.jsx create mode 100644 src/layout/LayoutHeader.jsx create mode 100644 src/layout/LayoutLogo.jsx create mode 100644 src/layout/LayoutMenu.jsx create mode 100644 src/layout/LayoutSidebar.jsx create mode 100644 src/layout/MainLayout.jsx diff --git a/src/layout/LayoutBreadcrumb.jsx b/src/layout/LayoutBreadcrumb.jsx new file mode 100644 index 0000000..5889c90 --- /dev/null +++ b/src/layout/LayoutBreadcrumb.jsx @@ -0,0 +1,15 @@ +import React, { createContext, useContext, useState } from 'react'; + +const BreadcrumbContext = createContext(); + +export const BreadcrumbProvider = ({ children }) => { + const [breadcrumbItems, setBreadcrumbItems] = useState([]); + + return ( + + {children} + + ); +}; + +export const useBreadcrumb = () => useContext(BreadcrumbContext); \ No newline at end of file diff --git a/src/layout/LayoutFooter.jsx b/src/layout/LayoutFooter.jsx new file mode 100644 index 0000000..be22b02 --- /dev/null +++ b/src/layout/LayoutFooter.jsx @@ -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 ( + + ) +} + +export default LayoutFooter diff --git a/src/layout/LayoutHeader.jsx b/src/layout/LayoutHeader.jsx new file mode 100644 index 0000000..21a7717 --- /dev/null +++ b/src/layout/LayoutHeader.jsx @@ -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 ( + <> +
+
+ + Login AS {roleName} + +
+ +
+ + { + handleLogOut(); + navigate('/signin'); + }} + aria-label="Log out from the application" + style={{ + color: colorText, + whiteSpace: 'nowrap', + }} + > + Logout + +
+
+ +
+ +
+ + ); +}; + +export default LayoutHeader; diff --git a/src/layout/LayoutLogo.jsx b/src/layout/LayoutLogo.jsx new file mode 100644 index 0000000..086db57 --- /dev/null +++ b/src/layout/LayoutLogo.jsx @@ -0,0 +1,69 @@ +import { Image } from 'antd'; +import logoPiu from '../assets/freepik/LOGOPIU.png'; +import React from 'react'; + +const LayoutLogo = () => { + return ( +
+
+ {/* Ring sebelum logo utama */} +
+ +
+ logo +
+
+
+ ); +}; + +export default LayoutLogo; \ No newline at end of file diff --git a/src/layout/LayoutMenu.jsx b/src/layout/LayoutMenu.jsx new file mode 100644 index 0000000..33fb21f --- /dev/null +++ b/src/layout/LayoutMenu.jsx @@ -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: , + label: Home, + }, + { + key: 'master', + icon: , + label: 'Master', + children: [ + { + key: 'master-device', + icon: , + label: Device, + }, + ], + }, +]; + +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 ( + + ); +}; +export default LayoutMenu; \ No newline at end of file diff --git a/src/layout/LayoutSidebar.jsx b/src/layout/LayoutSidebar.jsx new file mode 100644 index 0000000..4589344 --- /dev/null +++ b/src/layout/LayoutSidebar.jsx @@ -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 ( + { + // console.log(broken); + }} + onCollapse={(collapsed, type) => { + // console.log(collapsed, type); + }} + > + + + + ) +} + +export default LayoutSidebar diff --git a/src/layout/MainLayout.jsx b/src/layout/MainLayout.jsx new file mode 100644 index 0000000..6d035f9 --- /dev/null +++ b/src/layout/MainLayout.jsx @@ -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 ( + + + + + + {/*
*/} + {children} + {/*
*/} +
+ {/* */} +
+
+ ); +}; +export default MainLayout; \ No newline at end of file