Refactor: move AppShell to global layout, add breadcrumbs, and restructure profile routes
This commit is contained in:
@@ -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 */}
|
||||||
|
|||||||
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 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
|
||||||
|
|||||||
@@ -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() {
|
||||||
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,
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Container,
|
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Stack,
|
Stack,
|
||||||
@@ -63,77 +63,72 @@ 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">
|
Edit Profil
|
||||||
Edit Profil
|
</Title>
|
||||||
</Title>
|
<Text c="dimmed" size="sm">
|
||||||
<Text c="dimmed" size="sm">
|
Perbarui informasi profil publik Anda
|
||||||
Perbarui informasi profil publik Anda
|
</Text>
|
||||||
</Text>
|
</Box>
|
||||||
</Box>
|
<Button
|
||||||
<Button
|
variant="subtle"
|
||||||
variant="subtle"
|
color="gray"
|
||||||
color="gray"
|
leftSection={<IconChevronLeft size={18} />}
|
||||||
leftSection={<IconChevronLeft size={18} />}
|
onClick={() => navigate({ to: "/profile" })}
|
||||||
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)" }}
|
|
||||||
>
|
>
|
||||||
<form onSubmit={form.onSubmit(handleUpdateProfile)}>
|
Kembali
|
||||||
<Stack gap="md">
|
</Button>
|
||||||
<TextInput
|
</Group>
|
||||||
label="Nama Lengkap"
|
|
||||||
placeholder="Masukkan nama lengkap Anda"
|
<Divider style={{ opacity: 0.1 }} />
|
||||||
{...form.getInputProps("name")}
|
|
||||||
styles={{
|
<Card
|
||||||
label: { marginBottom: 8 },
|
withBorder
|
||||||
input: {
|
radius="md"
|
||||||
backgroundColor: "var(--mantine-color-default-soft)",
|
p="xl"
|
||||||
},
|
style={{ border: "1px solid var(--mantine-color-default-border)" }}
|
||||||
}}
|
>
|
||||||
/>
|
<form onSubmit={form.onSubmit(handleUpdateProfile)}>
|
||||||
<TextInput
|
<Stack gap="md">
|
||||||
label="URL Foto Profil"
|
<TextInput
|
||||||
placeholder="https://example.com/photo.jpg"
|
label="Nama Lengkap"
|
||||||
{...form.getInputProps("image")}
|
placeholder="Masukkan nama lengkap Anda"
|
||||||
styles={{
|
{...form.getInputProps("name")}
|
||||||
label: { marginBottom: 8 },
|
styles={{
|
||||||
input: {
|
label: { marginBottom: 8 },
|
||||||
backgroundColor: "var(--mantine-color-default-soft)",
|
input: {
|
||||||
},
|
backgroundColor: "var(--mantine-color-default-soft)",
|
||||||
}}
|
},
|
||||||
/>
|
}}
|
||||||
<Button
|
/>
|
||||||
type="submit"
|
<TextInput
|
||||||
fullWidth
|
label="URL Foto Profil"
|
||||||
mt="lg"
|
placeholder="https://example.com/photo.jpg"
|
||||||
size="md"
|
{...form.getInputProps("image")}
|
||||||
color="orange"
|
styles={{
|
||||||
loading={isUpdating}
|
label: { marginBottom: 8 },
|
||||||
leftSection={<IconEdit size={18} />}
|
input: {
|
||||||
>
|
backgroundColor: "var(--mantine-color-default-soft)",
|
||||||
Simpan Perubahan
|
},
|
||||||
</Button>
|
}}
|
||||||
</Stack>
|
/>
|
||||||
</form>
|
<Button
|
||||||
</Card>
|
type="submit"
|
||||||
</Stack>
|
fullWidth
|
||||||
</Container>
|
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,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Code,
|
Code,
|
||||||
Container,
|
|
||||||
Divider,
|
Divider,
|
||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
@@ -141,213 +140,211 @@ 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>
|
<Title order={1} c="orange.6">
|
||||||
<Title order={1} c="orange.6">
|
Profil Saya
|
||||||
Profil Saya
|
</Title>
|
||||||
</Title>
|
<Text c="dimmed" size="sm">
|
||||||
<Text c="dimmed" size="sm">
|
Kelola informasi akun dan pengaturan keamanan Anda
|
||||||
Kelola informasi akun dan pengaturan keamanan Anda
|
</Text>
|
||||||
</Text>
|
</Box>
|
||||||
</Box>
|
<Group>
|
||||||
<Group>
|
{snap.user?.role === "admin" && (
|
||||||
{snap.user?.role === "admin" && (
|
|
||||||
<Button
|
|
||||||
variant="light"
|
|
||||||
color="orange"
|
|
||||||
leftSection={<IconDashboard size={18} />}
|
|
||||||
onClick={() => navigate({ to: "/admin" })}
|
|
||||||
>
|
|
||||||
Admin Panel
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="orange"
|
||||||
leftSection={<IconEdit size={18} />}
|
leftSection={<IconDashboard size={18} />}
|
||||||
onClick={() => navigate({ to: "/profile/edit" })}
|
onClick={() => navigate({ to: "/admin" })}
|
||||||
>
|
>
|
||||||
Edit Profil
|
Admin Panel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
)}
|
||||||
variant="outline"
|
<Button
|
||||||
color="red"
|
variant="light"
|
||||||
leftSection={<IconLogout size={18} />}
|
color="blue"
|
||||||
onClick={openLogoutModal}
|
leftSection={<IconEdit size={18} />}
|
||||||
>
|
onClick={() => navigate({ to: "/profile/edit" })}
|
||||||
Keluar
|
>
|
||||||
</Button>
|
Edit Profil
|
||||||
</Group>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
color="red"
|
||||||
|
leftSection={<IconLogout size={18} />}
|
||||||
|
onClick={openLogoutModal}
|
||||||
|
>
|
||||||
|
Keluar
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Divider style={{ opacity: 0.1 }} />
|
<Divider style={{ opacity: 0.1 }} />
|
||||||
|
|
||||||
{/* Profile Overview Card */}
|
{/* Profile Overview Card */}
|
||||||
<Card withBorder radius="lg" p={0} style={{ overflow: "hidden" }}>
|
<Card withBorder radius="lg" p={0} style={{ overflow: "hidden" }}>
|
||||||
<Box
|
<Box
|
||||||
h={120}
|
h={120}
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
"linear-gradient(45deg, var(--mantine-color-gray-filled) 0%, var(--mantine-color-dark-filled) 100%)",
|
"linear-gradient(45deg, var(--mantine-color-gray-filled) 0%, var(--mantine-color-dark-filled) 100%)",
|
||||||
borderBottom: "1px solid var(--mantine-color-default-border)",
|
borderBottom: "1px solid var(--mantine-color-default-border)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box px="xl" pb="xl" style={{ marginTop: rem(-60) }}>
|
<Box px="xl" pb="xl" style={{ marginTop: rem(-60) }}>
|
||||||
<Group align="flex-end" gap="xl" mb="md">
|
<Group align="flex-end" gap="xl" mb="md">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={snap.user?.image}
|
src={snap.user?.image}
|
||||||
size={120}
|
size={120}
|
||||||
radius={120}
|
radius={120}
|
||||||
style={{
|
style={{
|
||||||
border: "4px solid var(--mantine-color-body)",
|
border: "4px solid var(--mantine-color-body)",
|
||||||
boxShadow: "var(--mantine-shadow-md)",
|
boxShadow: "var(--mantine-shadow-md)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{snap.user?.name?.charAt(0).toUpperCase()}
|
{snap.user?.name?.charAt(0).toUpperCase()}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Stack gap={0} pb="md">
|
<Stack gap={0} pb="md">
|
||||||
<Title order={2}>{snap.user?.name}</Title>
|
<Title order={2}>{snap.user?.name}</Title>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" size="sm">
|
||||||
{snap.user?.email}
|
{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>
|
||||||
<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>
|
</Text>
|
||||||
<Badge
|
<Group gap="xs" wrap="nowrap">
|
||||||
variant="dot"
|
<Code
|
||||||
color={snap.user?.role === "admin" ? "orange" : "blue"}
|
block
|
||||||
size="sm"
|
style={{
|
||||||
>
|
fontSize: rem(11),
|
||||||
{snap.user?.role || "user"}
|
flex: 1,
|
||||||
</Badge>
|
}}
|
||||||
</Group>
|
>
|
||||||
|
{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>
|
</Stack>
|
||||||
</Group>
|
</Card>
|
||||||
</Box>
|
</Stack>
|
||||||
</Card>
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
<Grid gutter="lg">
|
</Stack>
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
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 { 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user