Refactor: move AppShell to global layout, add breadcrumbs, and restructure profile routes
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Avatar,
|
||||
Badge,
|
||||
Box,
|
||||
Breadcrumbs,
|
||||
Divider,
|
||||
Group,
|
||||
Text,
|
||||
@@ -20,14 +22,70 @@ interface HeaderProps {
|
||||
}
|
||||
|
||||
export function Header({ onSidebarToggle }: HeaderProps) {
|
||||
const _location = useLocation();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
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 (
|
||||
<Group justify="space-between" w="100%">
|
||||
{/* Title */}
|
||||
{/* Title & Breadcrumbs */}
|
||||
<Group gap="md">
|
||||
<ActionIcon
|
||||
onClick={onSidebarToggle}
|
||||
@@ -42,6 +100,18 @@ export function Header({ onSidebarToggle }: HeaderProps) {
|
||||
style={{ width: "70%", height: "70%" }}
|
||||
/>
|
||||
</ActionIcon>
|
||||
<Breadcrumbs
|
||||
separator={
|
||||
<Text c="white" size="xs">
|
||||
/
|
||||
</Text>
|
||||
}
|
||||
styles={{
|
||||
separator: { color: "white" },
|
||||
}}
|
||||
>
|
||||
{breadcrumbItems}
|
||||
</Breadcrumbs>
|
||||
</Group>
|
||||
|
||||
{/* Right Section */}
|
||||
|
||||
66
src/components/layout/main-layout.tsx
Normal file
66
src/components/layout/main-layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { Route as JennaAnalyticRouteImport } from './routes/jenna-analytic'
|
||||
import { Route as DemografiPekerjaanRouteImport } from './routes/demografi-pekerjaan'
|
||||
import { Route as BumdesRouteImport } from './routes/bumdes'
|
||||
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 AdminRouteRouteImport } from './routes/admin/route'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
@@ -91,6 +92,11 @@ const BantuanRoute = BantuanRouteImport.update({
|
||||
path: '/bantuan',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ProfileRouteRoute = ProfileRouteRouteImport.update({
|
||||
id: '/profile',
|
||||
path: '/profile',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const PengaturanRouteRoute = PengaturanRouteRouteImport.update({
|
||||
id: '/pengaturan',
|
||||
path: '/pengaturan',
|
||||
@@ -112,9 +118,9 @@ const UsersIndexRoute = UsersIndexRouteImport.update({
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ProfileIndexRoute = ProfileIndexRouteImport.update({
|
||||
id: '/profile/',
|
||||
path: '/profile/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => ProfileRouteRoute,
|
||||
} as any)
|
||||
const AdminIndexRoute = AdminIndexRouteImport.update({
|
||||
id: '/',
|
||||
@@ -127,9 +133,9 @@ const UsersIdRoute = UsersIdRouteImport.update({
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ProfileEditRoute = ProfileEditRouteImport.update({
|
||||
id: '/profile/edit',
|
||||
path: '/profile/edit',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
id: '/edit',
|
||||
path: '/edit',
|
||||
getParentRoute: () => ProfileRouteRoute,
|
||||
} as any)
|
||||
const PengaturanUmumRoute = PengaturanUmumRouteImport.update({
|
||||
id: '/umum',
|
||||
@@ -171,6 +177,7 @@ export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/admin': typeof AdminRouteRouteWithChildren
|
||||
'/pengaturan': typeof PengaturanRouteRouteWithChildren
|
||||
'/profile': typeof ProfileRouteRouteWithChildren
|
||||
'/bantuan': typeof BantuanRoute
|
||||
'/bumdes': typeof BumdesRoute
|
||||
'/demografi-pekerjaan': typeof DemografiPekerjaanRoute
|
||||
@@ -227,6 +234,7 @@ export interface FileRoutesById {
|
||||
'/': typeof IndexRoute
|
||||
'/admin': typeof AdminRouteRouteWithChildren
|
||||
'/pengaturan': typeof PengaturanRouteRouteWithChildren
|
||||
'/profile': typeof ProfileRouteRouteWithChildren
|
||||
'/bantuan': typeof BantuanRoute
|
||||
'/bumdes': typeof BumdesRoute
|
||||
'/demografi-pekerjaan': typeof DemografiPekerjaanRoute
|
||||
@@ -257,6 +265,7 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/admin'
|
||||
| '/pengaturan'
|
||||
| '/profile'
|
||||
| '/bantuan'
|
||||
| '/bumdes'
|
||||
| '/demografi-pekerjaan'
|
||||
@@ -312,6 +321,7 @@ export interface FileRouteTypes {
|
||||
| '/'
|
||||
| '/admin'
|
||||
| '/pengaturan'
|
||||
| '/profile'
|
||||
| '/bantuan'
|
||||
| '/bumdes'
|
||||
| '/demografi-pekerjaan'
|
||||
@@ -341,6 +351,7 @@ export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AdminRouteRoute: typeof AdminRouteRouteWithChildren
|
||||
PengaturanRouteRoute: typeof PengaturanRouteRouteWithChildren
|
||||
ProfileRouteRoute: typeof ProfileRouteRouteWithChildren
|
||||
BantuanRoute: typeof BantuanRoute
|
||||
BumdesRoute: typeof BumdesRoute
|
||||
DemografiPekerjaanRoute: typeof DemografiPekerjaanRoute
|
||||
@@ -352,9 +363,7 @@ export interface RootRouteChildren {
|
||||
SigninRoute: typeof SigninRoute
|
||||
SignupRoute: typeof SignupRoute
|
||||
SosialRoute: typeof SosialRoute
|
||||
ProfileEditRoute: typeof ProfileEditRoute
|
||||
UsersIdRoute: typeof UsersIdRoute
|
||||
ProfileIndexRoute: typeof ProfileIndexRoute
|
||||
UsersIndexRoute: typeof UsersIndexRoute
|
||||
}
|
||||
|
||||
@@ -437,6 +446,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof BantuanRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/profile': {
|
||||
id: '/profile'
|
||||
path: '/profile'
|
||||
fullPath: '/profile'
|
||||
preLoaderRoute: typeof ProfileRouteRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/pengaturan': {
|
||||
id: '/pengaturan'
|
||||
path: '/pengaturan'
|
||||
@@ -467,10 +483,10 @@ declare module '@tanstack/react-router' {
|
||||
}
|
||||
'/profile/': {
|
||||
id: '/profile/'
|
||||
path: '/profile'
|
||||
path: '/'
|
||||
fullPath: '/profile/'
|
||||
preLoaderRoute: typeof ProfileIndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
parentRoute: typeof ProfileRouteRoute
|
||||
}
|
||||
'/admin/': {
|
||||
id: '/admin/'
|
||||
@@ -488,10 +504,10 @@ declare module '@tanstack/react-router' {
|
||||
}
|
||||
'/profile/edit': {
|
||||
id: '/profile/edit'
|
||||
path: '/profile/edit'
|
||||
path: '/edit'
|
||||
fullPath: '/profile/edit'
|
||||
preLoaderRoute: typeof ProfileEditRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
parentRoute: typeof ProfileRouteRoute
|
||||
}
|
||||
'/pengaturan/umum': {
|
||||
id: '/pengaturan/umum'
|
||||
@@ -581,10 +597,25 @@ const PengaturanRouteRouteWithChildren = PengaturanRouteRoute._addFileChildren(
|
||||
PengaturanRouteRouteChildren,
|
||||
)
|
||||
|
||||
interface ProfileRouteRouteChildren {
|
||||
ProfileEditRoute: typeof ProfileEditRoute
|
||||
ProfileIndexRoute: typeof ProfileIndexRoute
|
||||
}
|
||||
|
||||
const ProfileRouteRouteChildren: ProfileRouteRouteChildren = {
|
||||
ProfileEditRoute: ProfileEditRoute,
|
||||
ProfileIndexRoute: ProfileIndexRoute,
|
||||
}
|
||||
|
||||
const ProfileRouteRouteWithChildren = ProfileRouteRoute._addFileChildren(
|
||||
ProfileRouteRouteChildren,
|
||||
)
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AdminRouteRoute: AdminRouteRouteWithChildren,
|
||||
PengaturanRouteRoute: PengaturanRouteRouteWithChildren,
|
||||
ProfileRouteRoute: ProfileRouteRouteWithChildren,
|
||||
BantuanRoute: BantuanRoute,
|
||||
BumdesRoute: BumdesRoute,
|
||||
DemografiPekerjaanRoute: DemografiPekerjaanRoute,
|
||||
@@ -596,9 +627,7 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
SigninRoute: SigninRoute,
|
||||
SignupRoute: SignupRoute,
|
||||
SosialRoute: SosialRoute,
|
||||
ProfileEditRoute: ProfileEditRoute,
|
||||
UsersIdRoute: UsersIdRoute,
|
||||
ProfileIndexRoute: ProfileIndexRoute,
|
||||
UsersIndexRoute: UsersIndexRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
|
||||
@@ -3,7 +3,12 @@ import { protectedRouteMiddleware } from "@/middleware/authMiddleware";
|
||||
import { authStore } from "@/store/auth";
|
||||
import "@mantine/core/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({
|
||||
component: RootComponent,
|
||||
@@ -21,5 +26,18 @@ export const Route = createRootRoute({
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return <Outlet />;
|
||||
const routerState = useRouterState();
|
||||
const isPublicRoute = ["/signin", "/signup", "/admin", "/profile"].some(
|
||||
(path) => routerState.location.pathname.startsWith(path),
|
||||
);
|
||||
|
||||
if (isPublicRoute) {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Outlet />
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Grid,
|
||||
Group,
|
||||
Progress,
|
||||
@@ -55,7 +54,7 @@ function DashboardComponent() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Container size="lg" py="xl">
|
||||
<Box py="xl">
|
||||
<Title
|
||||
order={1}
|
||||
ta="center"
|
||||
@@ -195,6 +194,6 @@ function DashboardComponent() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -315,9 +315,7 @@ function DashboardLayout() {
|
||||
</AppShell.Navbar>
|
||||
|
||||
<AppShell.Main>
|
||||
<Box p="lg" style={{ minHeight: "calc(100vh - 100px)" }}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
<Outlet />
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
);
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import HelpPage from "@/components/help-page";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
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")({
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
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";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,72 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { useState } from "react";
|
||||
import { DashboardContent } from "@/components/dashboard-content";
|
||||
import { Header } from "@/components/header";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import JennaAnalytic from "@/components/jenna-analytic";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import KeamananPage from "@/components/keamanan-page";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import KeuanganAnggaran from "@/components/keuangan-anggaran";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import KinerjaDivisi from "@/components/kinerja-divisi";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
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")({
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,84 +1,5 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
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";
|
||||
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Divider,
|
||||
Group,
|
||||
Stack,
|
||||
@@ -63,77 +63,72 @@ function EditProfile() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size="sm" py={50}>
|
||||
<Stack gap="xl">
|
||||
<Group justify="space-between" align="center">
|
||||
<Box>
|
||||
<Title order={1} c="orange.6">
|
||||
Edit Profil
|
||||
</Title>
|
||||
<Text c="dimmed" size="sm">
|
||||
Perbarui informasi profil publik Anda
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
leftSection={<IconChevronLeft size={18} />}
|
||||
onClick={() => navigate({ to: "/profile" })}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Divider style={{ opacity: 0.1 }} />
|
||||
|
||||
<Card
|
||||
withBorder
|
||||
radius="md"
|
||||
p="xl"
|
||||
style={{ border: "1px solid var(--mantine-color-default-border)" }}
|
||||
<Stack gap="xl" px={"lg"}>
|
||||
<Group justify="space-between" align="center">
|
||||
<Box>
|
||||
<Title order={1} c="orange.6">
|
||||
Edit Profil
|
||||
</Title>
|
||||
<Text c="dimmed" size="sm">
|
||||
Perbarui informasi profil publik Anda
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
leftSection={<IconChevronLeft size={18} />}
|
||||
onClick={() => navigate({ to: "/profile" })}
|
||||
>
|
||||
<form onSubmit={form.onSubmit(handleUpdateProfile)}>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama Lengkap"
|
||||
placeholder="Masukkan nama lengkap Anda"
|
||||
{...form.getInputProps("name")}
|
||||
styles={{
|
||||
label: { marginBottom: 8 },
|
||||
input: {
|
||||
backgroundColor: "var(--mantine-color-default-soft)",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="URL Foto Profil"
|
||||
placeholder="https://example.com/photo.jpg"
|
||||
{...form.getInputProps("image")}
|
||||
styles={{
|
||||
label: { marginBottom: 8 },
|
||||
input: {
|
||||
backgroundColor: "var(--mantine-color-default-soft)",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
mt="lg"
|
||||
size="md"
|
||||
color="orange"
|
||||
loading={isUpdating}
|
||||
leftSection={<IconEdit size={18} />}
|
||||
>
|
||||
Simpan Perubahan
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
Kembali
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Divider style={{ opacity: 0.1 }} />
|
||||
|
||||
<Card
|
||||
withBorder
|
||||
radius="md"
|
||||
p="xl"
|
||||
style={{ border: "1px solid var(--mantine-color-default-border)" }}
|
||||
>
|
||||
<form onSubmit={form.onSubmit(handleUpdateProfile)}>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama Lengkap"
|
||||
placeholder="Masukkan nama lengkap Anda"
|
||||
{...form.getInputProps("name")}
|
||||
styles={{
|
||||
label: { marginBottom: 8 },
|
||||
input: {
|
||||
backgroundColor: "var(--mantine-color-default-soft)",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="URL Foto Profil"
|
||||
placeholder="https://example.com/photo.jpg"
|
||||
{...form.getInputProps("image")}
|
||||
styles={{
|
||||
label: { marginBottom: 8 },
|
||||
input: {
|
||||
backgroundColor: "var(--mantine-color-default-soft)",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
mt="lg"
|
||||
size="md"
|
||||
color="orange"
|
||||
loading={isUpdating}
|
||||
leftSection={<IconEdit size={18} />}
|
||||
>
|
||||
Simpan Perubahan
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Card>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
// Need Box from @mantine/core
|
||||
import { Box } from "@mantine/core";
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Code,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
Group,
|
||||
@@ -141,213 +140,211 @@ function Profile() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Container size="md" py={50}>
|
||||
<Stack gap="xl">
|
||||
{/* Header Section */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Box>
|
||||
<Title order={1} c="orange.6">
|
||||
Profil Saya
|
||||
</Title>
|
||||
<Text c="dimmed" size="sm">
|
||||
Kelola informasi akun dan pengaturan keamanan Anda
|
||||
</Text>
|
||||
</Box>
|
||||
<Group>
|
||||
{snap.user?.role === "admin" && (
|
||||
<Button
|
||||
variant="light"
|
||||
color="orange"
|
||||
leftSection={<IconDashboard size={18} />}
|
||||
onClick={() => navigate({ to: "/admin" })}
|
||||
>
|
||||
Admin Panel
|
||||
</Button>
|
||||
)}
|
||||
<Stack gap="xl" px={"lg"}>
|
||||
{/* Header Section */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Box>
|
||||
<Title order={1} c="orange.6">
|
||||
Profil Saya
|
||||
</Title>
|
||||
<Text c="dimmed" size="sm">
|
||||
Kelola informasi akun dan pengaturan keamanan Anda
|
||||
</Text>
|
||||
</Box>
|
||||
<Group>
|
||||
{snap.user?.role === "admin" && (
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconEdit size={18} />}
|
||||
onClick={() => navigate({ to: "/profile/edit" })}
|
||||
color="orange"
|
||||
leftSection={<IconDashboard size={18} />}
|
||||
onClick={() => navigate({ to: "/admin" })}
|
||||
>
|
||||
Edit Profil
|
||||
Admin Panel
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="red"
|
||||
leftSection={<IconLogout size={18} />}
|
||||
onClick={openLogoutModal}
|
||||
>
|
||||
Keluar
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
leftSection={<IconEdit size={18} />}
|
||||
onClick={() => navigate({ to: "/profile/edit" })}
|
||||
>
|
||||
Edit Profil
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="red"
|
||||
leftSection={<IconLogout size={18} />}
|
||||
onClick={openLogoutModal}
|
||||
>
|
||||
Keluar
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Divider style={{ opacity: 0.1 }} />
|
||||
<Divider style={{ opacity: 0.1 }} />
|
||||
|
||||
{/* Profile Overview Card */}
|
||||
<Card withBorder radius="lg" p={0} style={{ overflow: "hidden" }}>
|
||||
<Box
|
||||
h={120}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(45deg, var(--mantine-color-gray-filled) 0%, var(--mantine-color-dark-filled) 100%)",
|
||||
borderBottom: "1px solid var(--mantine-color-default-border)",
|
||||
}}
|
||||
/>
|
||||
<Box px="xl" pb="xl" style={{ marginTop: rem(-60) }}>
|
||||
<Group align="flex-end" gap="xl" mb="md">
|
||||
<Avatar
|
||||
src={snap.user?.image}
|
||||
size={120}
|
||||
radius={120}
|
||||
style={{
|
||||
border: "4px solid var(--mantine-color-body)",
|
||||
boxShadow: "var(--mantine-shadow-md)",
|
||||
}}
|
||||
>
|
||||
{snap.user?.name?.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Stack gap={0} pb="md">
|
||||
<Title order={2}>{snap.user?.name}</Title>
|
||||
<Group gap="xs">
|
||||
<Text c="dimmed" size="sm">
|
||||
{snap.user?.email}
|
||||
{/* Profile Overview Card */}
|
||||
<Card withBorder radius="lg" p={0} style={{ overflow: "hidden" }}>
|
||||
<Box
|
||||
h={120}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(45deg, var(--mantine-color-gray-filled) 0%, var(--mantine-color-dark-filled) 100%)",
|
||||
borderBottom: "1px solid var(--mantine-color-default-border)",
|
||||
}}
|
||||
/>
|
||||
<Box px="xl" pb="xl" style={{ marginTop: rem(-60) }}>
|
||||
<Group align="flex-end" gap="xl" mb="md">
|
||||
<Avatar
|
||||
src={snap.user?.image}
|
||||
size={120}
|
||||
radius={120}
|
||||
style={{
|
||||
border: "4px solid var(--mantine-color-body)",
|
||||
boxShadow: "var(--mantine-shadow-md)",
|
||||
}}
|
||||
>
|
||||
{snap.user?.name?.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Stack gap={0} pb="md">
|
||||
<Title order={2}>{snap.user?.name}</Title>
|
||||
<Group gap="xs">
|
||||
<Text c="dimmed" size="sm">
|
||||
{snap.user?.email}
|
||||
</Text>
|
||||
<Text c="dimmed" size="xs">
|
||||
•
|
||||
</Text>
|
||||
<Badge
|
||||
variant="dot"
|
||||
color={snap.user?.role === "admin" ? "orange" : "blue"}
|
||||
size="sm"
|
||||
>
|
||||
{snap.user?.role || "user"}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Grid gutter="lg">
|
||||
<Grid.Col span={{ base: 12, md: 7 }}>
|
||||
<Stack gap="md">
|
||||
<Title order={4} c="orange.6">
|
||||
Informasi Identitas
|
||||
</Title>
|
||||
<Grid gutter="sm">
|
||||
<Grid.Col span={6}>
|
||||
<InfoField
|
||||
icon={IconUser}
|
||||
label="Nama Lengkap"
|
||||
value={snap.user?.name}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<InfoField
|
||||
icon={IconShield}
|
||||
label="Peran"
|
||||
value={snap.user?.role || "User"}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<InfoField
|
||||
icon={IconAt}
|
||||
label="Alamat Email"
|
||||
value={snap.user?.email}
|
||||
copyable
|
||||
id="email"
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<InfoField
|
||||
icon={IconId}
|
||||
label="Unique User ID"
|
||||
value={snap.user?.id}
|
||||
copyable
|
||||
id="userid"
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, md: 5 }}>
|
||||
<Stack gap="md">
|
||||
<Title order={4} c="orange.6">
|
||||
Keamanan & Sesi
|
||||
</Title>
|
||||
<Card
|
||||
withBorder
|
||||
radius="md"
|
||||
p="lg"
|
||||
style={{
|
||||
border: "1px solid var(--mantine-color-default-border)",
|
||||
}}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={8}>
|
||||
Sesi Saat Ini
|
||||
</Text>
|
||||
<Text c="dimmed" size="xs">
|
||||
•
|
||||
<Group justify="space-between" align="center">
|
||||
<Badge color="green" variant="light">
|
||||
Aktif Sekarang
|
||||
</Badge>
|
||||
<Text size="xs" c="dimmed">
|
||||
ID: {snap.session?.id?.substring(0, 8)}...
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={8}>
|
||||
Session Token
|
||||
</Text>
|
||||
<Badge
|
||||
variant="dot"
|
||||
color={snap.user?.role === "admin" ? "orange" : "blue"}
|
||||
size="sm"
|
||||
>
|
||||
{snap.user?.role || "user"}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Code
|
||||
block
|
||||
style={{
|
||||
fontSize: rem(11),
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
{snap.session?.token
|
||||
? `${snap.session.token.substring(0, 32)}...`
|
||||
: "N/A"}
|
||||
</Code>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="gray"
|
||||
onClick={() =>
|
||||
snap.session?.token &&
|
||||
copyToClipboard(snap.session.token, "token")
|
||||
}
|
||||
>
|
||||
{copied === "token" ? (
|
||||
<IconCheck size={16} />
|
||||
) : (
|
||||
<IconCopy size={16} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="gray"
|
||||
fullWidth
|
||||
leftSection={<IconExternalLink size={16} />}
|
||||
>
|
||||
Riwayat Sesi
|
||||
</Button>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Grid gutter="lg">
|
||||
<Grid.Col span={{ base: 12, md: 7 }}>
|
||||
<Stack gap="md">
|
||||
<Title order={4} c="orange.6">
|
||||
Informasi Identitas
|
||||
</Title>
|
||||
<Grid gutter="sm">
|
||||
<Grid.Col span={6}>
|
||||
<InfoField
|
||||
icon={IconUser}
|
||||
label="Nama Lengkap"
|
||||
value={snap.user?.name}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<InfoField
|
||||
icon={IconShield}
|
||||
label="Peran"
|
||||
value={snap.user?.role || "User"}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<InfoField
|
||||
icon={IconAt}
|
||||
label="Alamat Email"
|
||||
value={snap.user?.email}
|
||||
copyable
|
||||
id="email"
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<InfoField
|
||||
icon={IconId}
|
||||
label="Unique User ID"
|
||||
value={snap.user?.id}
|
||||
copyable
|
||||
id="userid"
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, md: 5 }}>
|
||||
<Stack gap="md">
|
||||
<Title order={4} c="orange.6">
|
||||
Keamanan & Sesi
|
||||
</Title>
|
||||
<Card
|
||||
withBorder
|
||||
radius="md"
|
||||
p="lg"
|
||||
style={{
|
||||
border: "1px solid var(--mantine-color-default-border)",
|
||||
}}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={8}>
|
||||
Sesi Saat Ini
|
||||
</Text>
|
||||
<Group justify="space-between" align="center">
|
||||
<Badge color="green" variant="light">
|
||||
Aktif Sekarang
|
||||
</Badge>
|
||||
<Text size="xs" c="dimmed">
|
||||
ID: {snap.session?.id?.substring(0, 8)}...
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={8}>
|
||||
Session Token
|
||||
</Text>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Code
|
||||
block
|
||||
style={{
|
||||
fontSize: rem(11),
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
{snap.session?.token
|
||||
? `${snap.session.token.substring(0, 32)}...`
|
||||
: "N/A"}
|
||||
</Code>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="gray"
|
||||
onClick={() =>
|
||||
snap.session?.token &&
|
||||
copyToClipboard(snap.session.token, "token")
|
||||
}
|
||||
>
|
||||
{copied === "token" ? (
|
||||
<IconCheck size={16} />
|
||||
) : (
|
||||
<IconCopy size={16} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="gray"
|
||||
fullWidth
|
||||
leftSection={<IconExternalLink size={16} />}
|
||||
>
|
||||
Riwayat Sesi
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
69
src/routes/profile/route.tsx
Normal file
69
src/routes/profile/route.tsx
Normal 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";
|
||||
@@ -1,66 +1,6 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import SosialPage from "@/components/sosial-page";
|
||||
import { useSidebarFullscreen } from "@/hooks/use-sidebar-fullscreen";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user