Refactor: move AppShell to global layout, add breadcrumbs, and restructure profile routes

This commit is contained in:
2026-03-26 17:10:40 +08:00
parent 0d0dc187a5
commit 34804127c5
20 changed files with 548 additions and 992 deletions

View File

@@ -1,8 +1,10 @@
import { import {
ActionIcon, ActionIcon,
Anchor,
Avatar, Avatar,
Badge, Badge,
Box, Box,
Breadcrumbs,
Divider, Divider,
Group, Group,
Text, Text,
@@ -20,14 +22,70 @@ interface HeaderProps {
} }
export function Header({ onSidebarToggle }: HeaderProps) { export function Header({ onSidebarToggle }: HeaderProps) {
const _location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const { colorScheme, toggleColorScheme } = useMantineColorScheme(); const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark"; const dark = colorScheme === "dark";
const pathnames = location.pathname.split("/").filter((x) => x);
const breadcrumbItems = [
<Anchor
key="home"
onClick={() => navigate({ to: "/" })}
c="white"
size="sm"
underline="hover"
>
Desa Darmasaba
</Anchor>,
...pathnames.map((value, index) => {
const to = `/${pathnames.slice(0, index + 1).join("/")}`;
const isLast = index === pathnames.length - 1;
// Map route path to human-readable label
const labelMap: Record<string, string> = {
"kinerja-divisi": "Kinerja Divisi",
"pengaduan-layanan-publik": "Pengaduan & Layanan Publik",
"jenna-analytic": "Jenna Analytic",
"demografi-pekerjaan": "Demografi & Kependudukan",
"keuangan-anggaran": "Keuangan & Anggaran",
bumdes: "Bumdes & UMKM",
sosial: "Sosial",
keamanan: "Keamanan",
bantuan: "Bantuan",
pengaturan: "Pengaturan",
umum: "Umum",
notifikasi: "Notifikasi",
"akses-dan-tim": "Akses & Tim",
profile: "Profil",
edit: "Edit",
};
const label =
labelMap[value] || value.charAt(0).toUpperCase() + value.slice(1);
return isLast ? (
<Text key={to} c="white" size="sm" fw={600}>
{label}
</Text>
) : (
<Anchor
key={to}
onClick={() => navigate({ to })}
c="white"
size="sm"
underline="hover"
>
{label}
</Anchor>
);
}),
];
return ( return (
<Group justify="space-between" w="100%"> <Group justify="space-between" w="100%">
{/* Title */} {/* Title & Breadcrumbs */}
<Group gap="md"> <Group gap="md">
<ActionIcon <ActionIcon
onClick={onSidebarToggle} onClick={onSidebarToggle}
@@ -42,6 +100,18 @@ export function Header({ onSidebarToggle }: HeaderProps) {
style={{ width: "70%", height: "70%" }} style={{ width: "70%", height: "70%" }}
/> />
</ActionIcon> </ActionIcon>
<Breadcrumbs
separator={
<Text c="white" size="xs">
/
</Text>
}
styles={{
separator: { color: "white" },
}}
>
{breadcrumbItems}
</Breadcrumbs>
</Group> </Group>
{/* Right Section */} {/* Right Section */}

View File

@@ -0,0 +1,66 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import type React from "react";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
interface MainLayoutProps {
children: React.ReactNode;
}
export function MainLayout({ children }: MainLayoutProps) {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
{children}
</AppShell.Main>
</AppShell>
);
}

View File

@@ -20,6 +20,7 @@ import { Route as JennaAnalyticRouteImport } from './routes/jenna-analytic'
import { Route as DemografiPekerjaanRouteImport } from './routes/demografi-pekerjaan' import { Route as DemografiPekerjaanRouteImport } from './routes/demografi-pekerjaan'
import { Route as BumdesRouteImport } from './routes/bumdes' import { Route as BumdesRouteImport } from './routes/bumdes'
import { Route as BantuanRouteImport } from './routes/bantuan' import { Route as BantuanRouteImport } from './routes/bantuan'
import { Route as ProfileRouteRouteImport } from './routes/profile/route'
import { Route as PengaturanRouteRouteImport } from './routes/pengaturan/route' import { Route as PengaturanRouteRouteImport } from './routes/pengaturan/route'
import { Route as AdminRouteRouteImport } from './routes/admin/route' import { Route as AdminRouteRouteImport } from './routes/admin/route'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
@@ -91,6 +92,11 @@ const BantuanRoute = BantuanRouteImport.update({
path: '/bantuan', path: '/bantuan',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const ProfileRouteRoute = ProfileRouteRouteImport.update({
id: '/profile',
path: '/profile',
getParentRoute: () => rootRouteImport,
} as any)
const PengaturanRouteRoute = PengaturanRouteRouteImport.update({ const PengaturanRouteRoute = PengaturanRouteRouteImport.update({
id: '/pengaturan', id: '/pengaturan',
path: '/pengaturan', path: '/pengaturan',
@@ -112,9 +118,9 @@ const UsersIndexRoute = UsersIndexRouteImport.update({
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const ProfileIndexRoute = ProfileIndexRouteImport.update({ const ProfileIndexRoute = ProfileIndexRouteImport.update({
id: '/profile/', id: '/',
path: '/profile/', path: '/',
getParentRoute: () => rootRouteImport, getParentRoute: () => ProfileRouteRoute,
} as any) } as any)
const AdminIndexRoute = AdminIndexRouteImport.update({ const AdminIndexRoute = AdminIndexRouteImport.update({
id: '/', id: '/',
@@ -127,9 +133,9 @@ const UsersIdRoute = UsersIdRouteImport.update({
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const ProfileEditRoute = ProfileEditRouteImport.update({ const ProfileEditRoute = ProfileEditRouteImport.update({
id: '/profile/edit', id: '/edit',
path: '/profile/edit', path: '/edit',
getParentRoute: () => rootRouteImport, getParentRoute: () => ProfileRouteRoute,
} as any) } as any)
const PengaturanUmumRoute = PengaturanUmumRouteImport.update({ const PengaturanUmumRoute = PengaturanUmumRouteImport.update({
id: '/umum', id: '/umum',
@@ -171,6 +177,7 @@ export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/admin': typeof AdminRouteRouteWithChildren '/admin': typeof AdminRouteRouteWithChildren
'/pengaturan': typeof PengaturanRouteRouteWithChildren '/pengaturan': typeof PengaturanRouteRouteWithChildren
'/profile': typeof ProfileRouteRouteWithChildren
'/bantuan': typeof BantuanRoute '/bantuan': typeof BantuanRoute
'/bumdes': typeof BumdesRoute '/bumdes': typeof BumdesRoute
'/demografi-pekerjaan': typeof DemografiPekerjaanRoute '/demografi-pekerjaan': typeof DemografiPekerjaanRoute
@@ -227,6 +234,7 @@ export interface FileRoutesById {
'/': typeof IndexRoute '/': typeof IndexRoute
'/admin': typeof AdminRouteRouteWithChildren '/admin': typeof AdminRouteRouteWithChildren
'/pengaturan': typeof PengaturanRouteRouteWithChildren '/pengaturan': typeof PengaturanRouteRouteWithChildren
'/profile': typeof ProfileRouteRouteWithChildren
'/bantuan': typeof BantuanRoute '/bantuan': typeof BantuanRoute
'/bumdes': typeof BumdesRoute '/bumdes': typeof BumdesRoute
'/demografi-pekerjaan': typeof DemografiPekerjaanRoute '/demografi-pekerjaan': typeof DemografiPekerjaanRoute
@@ -257,6 +265,7 @@ export interface FileRouteTypes {
| '/' | '/'
| '/admin' | '/admin'
| '/pengaturan' | '/pengaturan'
| '/profile'
| '/bantuan' | '/bantuan'
| '/bumdes' | '/bumdes'
| '/demografi-pekerjaan' | '/demografi-pekerjaan'
@@ -312,6 +321,7 @@ export interface FileRouteTypes {
| '/' | '/'
| '/admin' | '/admin'
| '/pengaturan' | '/pengaturan'
| '/profile'
| '/bantuan' | '/bantuan'
| '/bumdes' | '/bumdes'
| '/demografi-pekerjaan' | '/demografi-pekerjaan'
@@ -341,6 +351,7 @@ export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
AdminRouteRoute: typeof AdminRouteRouteWithChildren AdminRouteRoute: typeof AdminRouteRouteWithChildren
PengaturanRouteRoute: typeof PengaturanRouteRouteWithChildren PengaturanRouteRoute: typeof PengaturanRouteRouteWithChildren
ProfileRouteRoute: typeof ProfileRouteRouteWithChildren
BantuanRoute: typeof BantuanRoute BantuanRoute: typeof BantuanRoute
BumdesRoute: typeof BumdesRoute BumdesRoute: typeof BumdesRoute
DemografiPekerjaanRoute: typeof DemografiPekerjaanRoute DemografiPekerjaanRoute: typeof DemografiPekerjaanRoute
@@ -352,9 +363,7 @@ export interface RootRouteChildren {
SigninRoute: typeof SigninRoute SigninRoute: typeof SigninRoute
SignupRoute: typeof SignupRoute SignupRoute: typeof SignupRoute
SosialRoute: typeof SosialRoute SosialRoute: typeof SosialRoute
ProfileEditRoute: typeof ProfileEditRoute
UsersIdRoute: typeof UsersIdRoute UsersIdRoute: typeof UsersIdRoute
ProfileIndexRoute: typeof ProfileIndexRoute
UsersIndexRoute: typeof UsersIndexRoute UsersIndexRoute: typeof UsersIndexRoute
} }
@@ -437,6 +446,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof BantuanRouteImport preLoaderRoute: typeof BantuanRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/profile': {
id: '/profile'
path: '/profile'
fullPath: '/profile'
preLoaderRoute: typeof ProfileRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/pengaturan': { '/pengaturan': {
id: '/pengaturan' id: '/pengaturan'
path: '/pengaturan' path: '/pengaturan'
@@ -467,10 +483,10 @@ declare module '@tanstack/react-router' {
} }
'/profile/': { '/profile/': {
id: '/profile/' id: '/profile/'
path: '/profile' path: '/'
fullPath: '/profile/' fullPath: '/profile/'
preLoaderRoute: typeof ProfileIndexRouteImport preLoaderRoute: typeof ProfileIndexRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof ProfileRouteRoute
} }
'/admin/': { '/admin/': {
id: '/admin/' id: '/admin/'
@@ -488,10 +504,10 @@ declare module '@tanstack/react-router' {
} }
'/profile/edit': { '/profile/edit': {
id: '/profile/edit' id: '/profile/edit'
path: '/profile/edit' path: '/edit'
fullPath: '/profile/edit' fullPath: '/profile/edit'
preLoaderRoute: typeof ProfileEditRouteImport preLoaderRoute: typeof ProfileEditRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof ProfileRouteRoute
} }
'/pengaturan/umum': { '/pengaturan/umum': {
id: '/pengaturan/umum' id: '/pengaturan/umum'
@@ -581,10 +597,25 @@ const PengaturanRouteRouteWithChildren = PengaturanRouteRoute._addFileChildren(
PengaturanRouteRouteChildren, PengaturanRouteRouteChildren,
) )
interface ProfileRouteRouteChildren {
ProfileEditRoute: typeof ProfileEditRoute
ProfileIndexRoute: typeof ProfileIndexRoute
}
const ProfileRouteRouteChildren: ProfileRouteRouteChildren = {
ProfileEditRoute: ProfileEditRoute,
ProfileIndexRoute: ProfileIndexRoute,
}
const ProfileRouteRouteWithChildren = ProfileRouteRoute._addFileChildren(
ProfileRouteRouteChildren,
)
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
AdminRouteRoute: AdminRouteRouteWithChildren, AdminRouteRoute: AdminRouteRouteWithChildren,
PengaturanRouteRoute: PengaturanRouteRouteWithChildren, PengaturanRouteRoute: PengaturanRouteRouteWithChildren,
ProfileRouteRoute: ProfileRouteRouteWithChildren,
BantuanRoute: BantuanRoute, BantuanRoute: BantuanRoute,
BumdesRoute: BumdesRoute, BumdesRoute: BumdesRoute,
DemografiPekerjaanRoute: DemografiPekerjaanRoute, DemografiPekerjaanRoute: DemografiPekerjaanRoute,
@@ -596,9 +627,7 @@ const rootRouteChildren: RootRouteChildren = {
SigninRoute: SigninRoute, SigninRoute: SigninRoute,
SignupRoute: SignupRoute, SignupRoute: SignupRoute,
SosialRoute: SosialRoute, SosialRoute: SosialRoute,
ProfileEditRoute: ProfileEditRoute,
UsersIdRoute: UsersIdRoute, UsersIdRoute: UsersIdRoute,
ProfileIndexRoute: ProfileIndexRoute,
UsersIndexRoute: UsersIndexRoute, UsersIndexRoute: UsersIndexRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport

View File

@@ -3,7 +3,12 @@ import { protectedRouteMiddleware } from "@/middleware/authMiddleware";
import { authStore } from "@/store/auth"; import { authStore } from "@/store/auth";
import "@mantine/core/styles.css"; import "@mantine/core/styles.css";
import "@mantine/dates/styles.css"; import "@mantine/dates/styles.css";
import { createRootRoute, Outlet } from "@tanstack/react-router"; import {
createRootRoute,
Outlet,
useRouterState,
} from "@tanstack/react-router";
import { MainLayout } from "@/components/layout/main-layout";
export const Route = createRootRoute({ export const Route = createRootRoute({
component: RootComponent, component: RootComponent,
@@ -21,5 +26,18 @@ export const Route = createRootRoute({
}); });
function RootComponent() { function RootComponent() {
const routerState = useRouterState();
const isPublicRoute = ["/signin", "/signup", "/admin", "/profile"].some(
(path) => routerState.location.pathname.startsWith(path),
);
if (isPublicRoute) {
return <Outlet />; return <Outlet />;
}
return (
<MainLayout>
<Outlet />
</MainLayout>
);
} }

View File

@@ -4,7 +4,6 @@ import {
Box, Box,
Button, Button,
Card, Card,
Container,
Grid, Grid,
Group, Group,
Progress, Progress,
@@ -55,7 +54,7 @@ function DashboardComponent() {
]; ];
return ( return (
<Container size="lg" py="xl"> <Box py="xl">
<Title <Title
order={1} order={1}
ta="center" ta="center"
@@ -195,6 +194,6 @@ function DashboardComponent() {
</Card> </Card>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Container> </Box>
); );
} }

View File

@@ -315,9 +315,7 @@ function DashboardLayout() {
</AppShell.Navbar> </AppShell.Navbar>
<AppShell.Main> <AppShell.Main>
<Box p="lg" style={{ minHeight: "calc(100vh - 100px)" }}>
<Outlet /> <Outlet />
</Box>
</AppShell.Main> </AppShell.Main>
</AppShell> </AppShell>
); );

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import HelpPage from "@/components/help-page"; import HelpPage from "@/components/help-page";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/bantuan")({ export const Route = createFileRoute("/bantuan")({
component: BantuanRoute, component: HelpPage,
}); });
function BantuanRoute() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<HelpPage />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import BumdesPage from "@/components/bumdes-page"; import BumdesPage from "@/components/bumdes-page";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/bumdes")({ export const Route = createFileRoute("/bumdes")({
component: BumdesRoute, component: BumdesPage,
}); });
function BumdesRoute() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<BumdesPage />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
import DemografiPekerjaan from "../components/demografi-pekerjaan"; import DemografiPekerjaan from "../components/demografi-pekerjaan";
export const Route = createFileRoute("/demografi-pekerjaan")({ export const Route = createFileRoute("/demografi-pekerjaan")({
component: DemografiPekerjaanPage, component: DemografiPekerjaan,
}); });
function DemografiPekerjaanPage() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<DemografiPekerjaan />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,72 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import { DashboardContent } from "@/components/dashboard-content"; import { DashboardContent } from "@/components/dashboard-content";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: DashboardPage, component: DashboardContent,
}); });
function DashboardPage() {
const [opened, { toggle }] = useDisclosure();
const [sidebarCollapsed, setSidebarCollapsed] = useDisclosure(false);
const [clickCount, setClickCount] = useState(0);
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
const handleMainClick = () => {
if (!sidebarCollapsed) {
const newCount = clickCount + 1;
setClickCount(newCount);
if (newCount === 2) {
setSidebarCollapsed.toggle();
setClickCount(0);
} else {
setTimeout(() => setClickCount(0), 300);
}
}
};
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
<Header onSidebarToggle={setSidebarCollapsed.toggle} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<DashboardContent />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import JennaAnalytic from "@/components/jenna-analytic"; import JennaAnalytic from "@/components/jenna-analytic";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/jenna-analytic")({ export const Route = createFileRoute("/jenna-analytic")({
component: JennaAnalyticPage, component: JennaAnalytic,
}); });
function JennaAnalyticPage() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<JennaAnalytic />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import KeamananPage from "@/components/keamanan-page"; import KeamananPage from "@/components/keamanan-page";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/keamanan")({ export const Route = createFileRoute("/keamanan")({
component: KeamananRoute, component: KeamananPage,
}); });
function KeamananRoute() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<KeamananPage />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import KeuanganAnggaran from "@/components/keuangan-anggaran"; import KeuanganAnggaran from "@/components/keuangan-anggaran";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/keuangan-anggaran")({ export const Route = createFileRoute("/keuangan-anggaran")({
component: KeuanganAnggaranPage, component: KeuanganAnggaran,
}); });
function KeuanganAnggaranPage() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<KeuanganAnggaran />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import KinerjaDivisi from "@/components/kinerja-divisi"; import KinerjaDivisi from "@/components/kinerja-divisi";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/kinerja-divisi")({ export const Route = createFileRoute("/kinerja-divisi")({
component: KinerjaDivisiPage, component: KinerjaDivisi,
}); });
function KinerjaDivisiPage() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<KinerjaDivisi />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import PengaduanLayananPublik from "@/components/pengaduan-layanan-publik"; import PengaduanLayananPublik from "@/components/pengaduan-layanan-publik";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/pengaduan-layanan-publik")({ export const Route = createFileRoute("/pengaduan-layanan-publik")({
component: PengaduanLayananPublikPage, component: PengaduanLayananPublik,
}); });
function PengaduanLayananPublikPage() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<PengaduanLayananPublik />
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,84 +1,5 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core"; import { createFileRoute, Outlet } from "@tanstack/react-router";
import { useMediaQuery } from "@mantine/hooks";
import {
createFileRoute,
Outlet,
useRouterState,
} from "@tanstack/react-router";
import { useEffect } from "react";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/pengaturan")({ export const Route = createFileRoute("/pengaturan")({
component: PengaturanLayout, component: Outlet,
}); });
function PengaturanLayout() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const isMobile = useMediaQuery("(max-width: 48em)");
const _routerState = useRouterState();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
// Auto close navbar on route change (mobile only)
useEffect(() => {
if (isMobile && opened) {
toggleMobile();
}
}, [isMobile, opened, toggleMobile]);
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="lg" align="center" wrap="nowrap">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<div className="p-2">
<Outlet />
</div>
</AppShell.Main>
</AppShell>
);
}

View File

@@ -1,7 +1,7 @@
import { import {
Box,
Button, Button,
Card, Card,
Container,
Divider, Divider,
Group, Group,
Stack, Stack,
@@ -63,8 +63,7 @@ function EditProfile() {
}; };
return ( return (
<Container size="sm" py={50}> <Stack gap="xl" px={"lg"}>
<Stack gap="xl">
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Box> <Box>
<Title order={1} c="orange.6"> <Title order={1} c="orange.6">
@@ -131,9 +130,5 @@ function EditProfile() {
</form> </form>
</Card> </Card>
</Stack> </Stack>
</Container>
); );
} }
// Need Box from @mantine/core
import { Box } from "@mantine/core";

View File

@@ -6,7 +6,6 @@ import {
Button, Button,
Card, Card,
Code, Code,
Container,
Divider, Divider,
Grid, Grid,
Group, Group,
@@ -141,8 +140,7 @@ function Profile() {
); );
return ( return (
<Container size="md" py={50}> <Stack gap="xl" px={"lg"}>
<Stack gap="xl">
{/* Header Section */} {/* Header Section */}
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Box> <Box>
@@ -348,6 +346,5 @@ function Profile() {
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Stack> </Stack>
</Container>
); );
} }

View File

@@ -0,0 +1,69 @@
import {
AppShell,
Button,
Container,
Group,
Text,
useMantineColorScheme,
} from "@mantine/core";
import { IconChevronLeft } from "@tabler/icons-react";
import { createFileRoute, Outlet, useNavigate } from "@tanstack/react-router";
export const Route = createFileRoute("/profile")({
component: ProfileLayout,
});
function ProfileLayout() {
const navigate = useNavigate();
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<AppShell
header={{ height: 60 }}
padding="md"
styles={{
main: {
backgroundColor: dark
? "var(--mantine-color-dark-8)"
: "var(--mantine-color-gray-0)",
},
}}
>
<AppShell.Header
style={{
borderBottom: "1px solid var(--mantine-color-default-border)",
backgroundColor: dark ? "var(--mantine-color-dark-7)" : "white",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
<Group h="100%" justify="space-between">
<Group>
<Button
variant="subtle"
color="gray"
leftSection={<IconChevronLeft size={16} />}
onClick={() => navigate({ to: "/" })}
>
Kembali ke Dashboard
</Button>
</Group>
<Text fw={700} size="lg" c="orange.6">
PENGATURAN AKUN
</Text>
<Box w={150} />
</Group>
</AppShell.Header>
<AppShell.Main>
<Outlet />
</AppShell.Main>
</AppShell>
);
}
import { Box } from "@mantine/core";

View File

@@ -1,66 +1,6 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
import SosialPage from "@/components/sosial-page"; import SosialPage from "@/components/sosial-page";
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
export const Route = createFileRoute("/sosial")({ export const Route = createFileRoute("/sosial")({
component: SosialRoute, component: SosialPage,
}); });
function SosialRoute() {
const {
opened,
toggleMobile,
sidebarCollapsed,
toggleSidebar,
handleMainClick,
} = useSidebarFullscreen();
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: sidebarCollapsed },
}}
padding="md"
>
<AppShell.Header bg={headerBgColor}>
<Group h="100%" px="md">
<Burger
opened={opened}
onClick={toggleMobile}
hiddenFrom="sm"
size="sm"
/>
<Header onSidebarToggle={toggleSidebar} />
</Group>
</AppShell.Header>
<AppShell.Navbar
p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar />
</div>
</AppShell.Navbar>
<AppShell.Main
bg={mainBgColor}
onClick={handleMainClick}
style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
>
<SosialPage />
</AppShell.Main>
</AppShell>
);
}