@farmzone/fz-template-react 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -102
- package/bin/create.js +108 -108
- package/package.json +24 -24
- package/template/.env.example +5 -5
- package/template/.prettierrc +9 -9
- package/template/eslint.config.js +26 -26
- package/template/index.css +32 -32
- package/template/index.html +19 -19
- package/template/package.json +54 -54
- package/template/pnpm-lock.yaml +4214 -4214
- package/template/public/mockServiceWorker.js +349 -349
- package/template/src/app/App.tsx +26 -26
- package/template/src/app/api/api.ts +178 -178
- package/template/src/app/api/queries.ts +335 -335
- package/template/src/app/api/queryKey.ts +7 -7
- package/template/src/app/api/token.ts +8 -7
- package/template/src/app/layout/Layout.tsx +33 -33
- package/template/src/app/layout/ListContents.tsx +9 -9
- package/template/src/app/layout/ListHeader.tsx +41 -41
- package/template/src/app/layout/MultiTabNav.tsx +106 -101
- package/template/src/app/layout/Sidebar.tsx +33 -33
- package/template/src/app/layout/UserInfo.tsx +95 -94
- package/template/src/app/layout/menu.ts +79 -55
- package/template/src/app/layout/tabSwitchStore.ts +11 -11
- package/template/src/app/router/Router.tsx +56 -56
- package/template/src/app/store/index.ts +26 -26
- package/template/src/index.tsx +21 -21
- package/template/src/mocks/browser.ts +17 -17
- package/template/src/mocks/handlers.ts +43 -43
- package/template/src/mocks/scenarios.ts +57 -57
- package/template/src/pages/dashboard/index.tsx +541 -541
- package/template/src/pages/error/Error.tsx +29 -29
- package/template/src/pages/error/NotFound.tsx +27 -27
- package/template/src/pages/login/index.tsx +317 -317
- package/template/src/pages/post/PostFormModal.tsx +128 -128
- package/template/src/pages/post/detail/index.tsx +545 -545
- package/template/src/pages/post/index.tsx +266 -266
- package/template/src/pages/sample/SampleFormModal.tsx +188 -188
- package/template/src/pages/sample/detail/index.tsx +551 -517
- package/template/src/pages/sample/index.tsx +298 -298
- package/template/src/pages/sample/modal/index.tsx +308 -308
- package/template/src/pages/system/log/index.tsx +173 -173
- package/template/src/pages/user/config/columns.tsx +102 -102
- package/template/src/pages/user/config/schema.ts +54 -54
- package/template/src/pages/user/index.tsx +704 -650
- package/template/src/shared/components/CommentInput.tsx +243 -243
- package/template/src/shared/components/FilePreviewCard.tsx +71 -71
- package/template/src/shared/config/text.ts +27 -27
- package/template/src/shared/config/type.ts +40 -40
- package/template/src/shared/utils/format.ts +11 -11
- package/template/src/types/auth.ts +10 -10
- package/template/src/types/comment.ts +33 -33
- package/template/src/types/common.ts +19 -19
- package/template/src/types/dashboard.ts +53 -53
- package/template/src/types/index.ts +16 -16
- package/template/src/types/log.ts +21 -21
- package/template/src/types/post.ts +32 -32
- package/template/src/types/sample.ts +33 -33
- package/template/src/types/user.ts +51 -51
- package/template/src/vite-env.d.ts +10 -10
- package/template/tsconfig.app.json +32 -32
- package/template/tsconfig.json +7 -7
- package/template/tsconfig.node.json +26 -26
- package/template/vite.config.ts +13 -13
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export const AUTH_QUERY_KEY = "auth";
|
|
2
|
-
export const SAMPLE_QUERY_KEY = "sample";
|
|
3
|
-
export const POST_QUERY_KEY = "post";
|
|
4
|
-
export const COMMENT_QUERY_KEY = "comment";
|
|
5
|
-
export const USER_QUERY_KEY = "user";
|
|
6
|
-
export const LOG_QUERY_KEY = "log";
|
|
7
|
-
export const DASHBOARD_QUERY_KEY = "dashboard";
|
|
1
|
+
export const AUTH_QUERY_KEY = "auth";
|
|
2
|
+
export const SAMPLE_QUERY_KEY = "sample";
|
|
3
|
+
export const POST_QUERY_KEY = "post";
|
|
4
|
+
export const COMMENT_QUERY_KEY = "comment";
|
|
5
|
+
export const USER_QUERY_KEY = "user";
|
|
6
|
+
export const LOG_QUERY_KEY = "log";
|
|
7
|
+
export const DASHBOARD_QUERY_KEY = "dashboard";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import Cookies from "js-cookie";
|
|
2
|
-
|
|
3
|
-
export const clearUserToken = () => {
|
|
4
|
-
Cookies.remove("AccessToken");
|
|
5
|
-
Cookies.remove("RefreshToken");
|
|
6
|
-
localStorage.removeItem("currentId");
|
|
7
|
-
|
|
1
|
+
import Cookies from "js-cookie";
|
|
2
|
+
|
|
3
|
+
export const clearUserToken = () => {
|
|
4
|
+
Cookies.remove("AccessToken");
|
|
5
|
+
Cookies.remove("RefreshToken");
|
|
6
|
+
localStorage.removeItem("currentId");
|
|
7
|
+
localStorage.removeItem("multi-tab-store");
|
|
8
|
+
};
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { Outlet, useLocation } from "react-router";
|
|
3
|
-
import { toast } from "@farmzone/fz-react-ui";
|
|
4
|
-
|
|
5
|
-
import { useTabSwitchStore } from "./tabSwitchStore";
|
|
6
|
-
import MultiTabNav from "./MultiTabNav";
|
|
7
|
-
import Sidebar from "./Sidebar";
|
|
8
|
-
|
|
9
|
-
export default function Layout() {
|
|
10
|
-
const { pathname } = useLocation();
|
|
11
|
-
const prevPathnameRef = useRef(pathname);
|
|
12
|
-
const { tabSwitchKey } = useTabSwitchStore();
|
|
13
|
-
|
|
14
|
-
// 페이지 전환 시 success toast 외 모두 제거
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
const prev = prevPathnameRef.current;
|
|
17
|
-
prevPathnameRef.current = pathname;
|
|
18
|
-
if (prev === pathname) return;
|
|
19
|
-
toast.dismissAllExcept("success");
|
|
20
|
-
}, [pathname]);
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div className="flex h-screen bg-gray-50">
|
|
24
|
-
<Sidebar />
|
|
25
|
-
<div className="flex flex-1 flex-col overflow-hidden">
|
|
26
|
-
<MultiTabNav />
|
|
27
|
-
<main className="flex-1 overflow-y-auto">
|
|
28
|
-
<Outlet key={tabSwitchKey} />
|
|
29
|
-
</main>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Outlet, useLocation } from "react-router";
|
|
3
|
+
import { toast } from "@farmzone/fz-react-ui";
|
|
4
|
+
|
|
5
|
+
import { useTabSwitchStore } from "./tabSwitchStore";
|
|
6
|
+
import MultiTabNav from "./MultiTabNav";
|
|
7
|
+
import Sidebar from "./Sidebar";
|
|
8
|
+
|
|
9
|
+
export default function Layout() {
|
|
10
|
+
const { pathname } = useLocation();
|
|
11
|
+
const prevPathnameRef = useRef(pathname);
|
|
12
|
+
const { tabSwitchKey } = useTabSwitchStore();
|
|
13
|
+
|
|
14
|
+
// 페이지 전환 시 success toast 외 모두 제거
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const prev = prevPathnameRef.current;
|
|
17
|
+
prevPathnameRef.current = pathname;
|
|
18
|
+
if (prev === pathname) return;
|
|
19
|
+
toast.dismissAllExcept("success");
|
|
20
|
+
}, [pathname]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="flex h-screen bg-gray-50">
|
|
24
|
+
<Sidebar />
|
|
25
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
26
|
+
<MultiTabNav />
|
|
27
|
+
<main className="flex-1 overflow-y-auto">
|
|
28
|
+
<Outlet key={tabSwitchKey} />
|
|
29
|
+
</main>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
interface ListContentsProps {
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function ListContents({ children }: ListContentsProps) {
|
|
8
|
-
return <div className="mt-4 flex flex-col gap-3">{children}</div>;
|
|
9
|
-
}
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
interface ListContentsProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function ListContents({ children }: ListContentsProps) {
|
|
8
|
+
return <div className="mt-4 flex flex-col gap-3">{children}</div>;
|
|
9
|
+
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import { useLocation } from "react-router";
|
|
3
|
-
|
|
4
|
-
import { MENU_SECTIONS } from "./menu";
|
|
5
|
-
|
|
6
|
-
function findPathText(pathname: string): string {
|
|
7
|
-
for (const section of MENU_SECTIONS) {
|
|
8
|
-
for (const item of section.items) {
|
|
9
|
-
if (item.children?.length) {
|
|
10
|
-
for (const child of item.children) {
|
|
11
|
-
if (child.path && (pathname === child.path || pathname.startsWith(`${child.path}/`))) {
|
|
12
|
-
return `${item.label} > ${child.label}`;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
} else if (item.path && (pathname === item.path || pathname.startsWith(`${item.path}/`))) {
|
|
16
|
-
return section.title ? `${section.title} > ${item.label}` : item.label;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return "";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface ListHeaderProps {
|
|
24
|
-
title: string;
|
|
25
|
-
rightArea?: ReactNode;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default function ListHeader({ title, rightArea }: ListHeaderProps) {
|
|
29
|
-
const { pathname } = useLocation();
|
|
30
|
-
const pathText = findPathText(pathname);
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<div className="flex min-h-8 items-end justify-between">
|
|
34
|
-
<div className="flex flex-col gap-0.5">
|
|
35
|
-
{pathText && <span className="text-xs text-gray-400">{pathText}</span>}
|
|
36
|
-
<h2 className="text-2xl font-bold text-gray-900">{title}</h2>
|
|
37
|
-
</div>
|
|
38
|
-
{rightArea && <div className="flex items-center">{rightArea}</div>}
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { useLocation } from "react-router";
|
|
3
|
+
|
|
4
|
+
import { MENU_SECTIONS } from "./menu";
|
|
5
|
+
|
|
6
|
+
function findPathText(pathname: string): string {
|
|
7
|
+
for (const section of MENU_SECTIONS) {
|
|
8
|
+
for (const item of section.items) {
|
|
9
|
+
if (item.children?.length) {
|
|
10
|
+
for (const child of item.children) {
|
|
11
|
+
if (child.path && (pathname === child.path || pathname.startsWith(`${child.path}/`))) {
|
|
12
|
+
return `${item.label} > ${child.label}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
} else if (item.path && (pathname === item.path || pathname.startsWith(`${item.path}/`))) {
|
|
16
|
+
return section.title ? `${section.title} > ${item.label}` : item.label;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ListHeaderProps {
|
|
24
|
+
title: string;
|
|
25
|
+
rightArea?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function ListHeader({ title, rightArea }: ListHeaderProps) {
|
|
29
|
+
const { pathname } = useLocation();
|
|
30
|
+
const pathText = findPathText(pathname);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex min-h-8 items-end justify-between">
|
|
34
|
+
<div className="flex flex-col gap-0.5">
|
|
35
|
+
{pathText && <span className="text-xs text-gray-400">{pathText}</span>}
|
|
36
|
+
<h2 className="text-2xl font-bold text-gray-900">{title}</h2>
|
|
37
|
+
</div>
|
|
38
|
+
{rightArea && <div className="flex items-center">{rightArea}</div>}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -1,101 +1,106 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { useLocation, useNavigate } from "react-router";
|
|
3
|
-
import { MultiTab, useMultiTabStore } from "@farmzone/fz-react-ui";
|
|
4
|
-
|
|
5
|
-
import { findTabInfoForPath } from "./menu";
|
|
6
|
-
import { useTabSwitchStore } from "./tabSwitchStore";
|
|
7
|
-
|
|
8
|
-
const DASHBOARD_TAB = { basePath: "/", label: "대시보드" } as const;
|
|
9
|
-
|
|
10
|
-
export default function MultiTabNav() {
|
|
11
|
-
const { tabs, activeTabId, hasHydrated, openTab, addTab, switchToTab, closeTab, updateCurrentPath } =
|
|
12
|
-
useMultiTabStore();
|
|
13
|
-
const { commitTabSwitch } = useTabSwitchStore();
|
|
14
|
-
const navigate = useNavigate();
|
|
15
|
-
const location = useLocation();
|
|
16
|
-
const { pathname } = location;
|
|
17
|
-
const pendingSwitch = useRef(false);
|
|
18
|
-
|
|
19
|
-
// hydration 완료 후 최초 1회: 탭 없으면 대시보드 생성, 있으면 활성 탭 경로 복원
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (!hasHydrated) return;
|
|
22
|
-
if (tabs.length === 0) {
|
|
23
|
-
openTab(DASHBOARD_TAB.basePath, DASHBOARD_TAB.label);
|
|
24
|
-
navigate(DASHBOARD_TAB.basePath);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const activeTab = tabs.find((t) => t.id === activeTabId);
|
|
28
|
-
if (activeTab && pathname !== activeTab.currentPath) {
|
|
29
|
-
navigate(activeTab.currentPath, { replace: true });
|
|
30
|
-
}
|
|
31
|
-
// hasHydrated 변경 시 1회만 실행
|
|
32
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
-
}, [hasHydrated]);
|
|
34
|
-
|
|
35
|
-
// 탭 전환 후 URL이 실제로 바뀌면 commitTabSwitch 호출 → Outlet 강제 리마운트
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (!pendingSwitch.current) return;
|
|
38
|
-
pendingSwitch.current = false;
|
|
39
|
-
commitTabSwitch();
|
|
40
|
-
// location.key 변경 시 실행 (실제 URL 변경 감지)
|
|
41
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
-
}, [location.key]);
|
|
43
|
-
|
|
44
|
-
// pathname 변경 시: 탭 소유권 확인 → auto-switch / auto-create / currentPath 동기화
|
|
45
|
-
// getState() 사용으로 stale closure 방지
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (!hasHydrated) return;
|
|
48
|
-
const { tabs: currentTabs, activeTabId: currentActiveTabId } = useMultiTabStore.getState();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (ownerTab.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useLocation, useNavigate } from "react-router";
|
|
3
|
+
import { MultiTab, useMultiTabStore } from "@farmzone/fz-react-ui";
|
|
4
|
+
|
|
5
|
+
import { findTabInfoForPath } from "./menu";
|
|
6
|
+
import { useTabSwitchStore } from "./tabSwitchStore";
|
|
7
|
+
|
|
8
|
+
const DASHBOARD_TAB = { basePath: "/", label: "대시보드" } as const;
|
|
9
|
+
|
|
10
|
+
export default function MultiTabNav() {
|
|
11
|
+
const { tabs, activeTabId, hasHydrated, openTab, addTab, switchToTab, closeTab, updateCurrentPath } =
|
|
12
|
+
useMultiTabStore();
|
|
13
|
+
const { commitTabSwitch } = useTabSwitchStore();
|
|
14
|
+
const navigate = useNavigate();
|
|
15
|
+
const location = useLocation();
|
|
16
|
+
const { pathname } = location;
|
|
17
|
+
const pendingSwitch = useRef(false);
|
|
18
|
+
|
|
19
|
+
// hydration 완료 후 최초 1회: 탭 없으면 대시보드 생성, 있으면 활성 탭 경로 복원
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!hasHydrated) return;
|
|
22
|
+
if (tabs.length === 0) {
|
|
23
|
+
openTab(DASHBOARD_TAB.basePath, DASHBOARD_TAB.label);
|
|
24
|
+
navigate(DASHBOARD_TAB.basePath);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const activeTab = tabs.find((t) => t.id === activeTabId);
|
|
28
|
+
if (activeTab && pathname !== activeTab.currentPath) {
|
|
29
|
+
navigate(activeTab.currentPath, { replace: true });
|
|
30
|
+
}
|
|
31
|
+
// hasHydrated 변경 시 1회만 실행
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
}, [hasHydrated]);
|
|
34
|
+
|
|
35
|
+
// 탭 전환 후 URL이 실제로 바뀌면 commitTabSwitch 호출 → Outlet 강제 리마운트
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!pendingSwitch.current) return;
|
|
38
|
+
pendingSwitch.current = false;
|
|
39
|
+
commitTabSwitch();
|
|
40
|
+
// location.key 변경 시 실행 (실제 URL 변경 감지)
|
|
41
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
+
}, [location.key]);
|
|
43
|
+
|
|
44
|
+
// pathname 변경 시: 탭 소유권 확인 → auto-switch / auto-create / currentPath 동기화
|
|
45
|
+
// getState() 사용으로 stale closure 방지
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!hasHydrated) return;
|
|
48
|
+
const { tabs: currentTabs, activeTabId: currentActiveTabId } = useMultiTabStore.getState();
|
|
49
|
+
|
|
50
|
+
// 등록된 더 구체적인 경로가 있으면 prefix startsWith 흡수를 막음
|
|
51
|
+
// (예: /sample 탭이 /sample/modal, /sample/123 을 흡수하지 않도록)
|
|
52
|
+
const registeredInfo = findTabInfoForPath(pathname);
|
|
53
|
+
const ownerTab = currentTabs.find((t) => {
|
|
54
|
+
if (pathname === t.basePath) return true;
|
|
55
|
+
if (registeredInfo && registeredInfo.basePath !== t.basePath) return false;
|
|
56
|
+
return pathname.startsWith(`${t.basePath}/`);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (ownerTab) {
|
|
60
|
+
if (ownerTab.id !== currentActiveTabId) {
|
|
61
|
+
// Sidebar 등에서 다른 탭 도메인으로 직접 navigate된 경우 — 활성 탭 전환
|
|
62
|
+
const fromTab = currentTabs.find((t) => t.id === currentActiveTabId);
|
|
63
|
+
useMultiTabStore.getState().switchToTab(ownerTab.id, fromTab?.currentPath ?? pathname);
|
|
64
|
+
}
|
|
65
|
+
if (ownerTab.currentPath !== pathname) {
|
|
66
|
+
updateCurrentPath(ownerTab.id, pathname);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// 어떤 탭에도 속하지 않는 경로 — 메뉴 정보로 탭 자동 생성
|
|
70
|
+
const info = findTabInfoForPath(pathname);
|
|
71
|
+
if (info) openTab(info.basePath, info.label, pathname);
|
|
72
|
+
}
|
|
73
|
+
}, [pathname, hasHydrated]);
|
|
74
|
+
|
|
75
|
+
if (!tabs.length) return null;
|
|
76
|
+
|
|
77
|
+
const activeTab = tabs.find((t) => t.id === activeTabId);
|
|
78
|
+
|
|
79
|
+
const handleTabClick = (tabId: string) => {
|
|
80
|
+
if (tabId === activeTabId) return;
|
|
81
|
+
pendingSwitch.current = true;
|
|
82
|
+
const targetPath = switchToTab(tabId, pathname);
|
|
83
|
+
navigate(targetPath);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleTabClose = (tabId: string) => {
|
|
87
|
+
const nextPath = closeTab(tabId);
|
|
88
|
+
if (nextPath != null) navigate(nextPath);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleTabAdd = () => {
|
|
92
|
+
if (!activeTab) return;
|
|
93
|
+
const newPath = addTab(activeTab.basePath, activeTab.label, activeTab.currentPath);
|
|
94
|
+
navigate(newPath);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<MultiTab
|
|
99
|
+
tabs={tabs}
|
|
100
|
+
activeTabId={activeTabId}
|
|
101
|
+
onTabClick={handleTabClick}
|
|
102
|
+
onTabClose={handleTabClose}
|
|
103
|
+
onTabAdd={handleTabAdd}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { useNavigate } from "react-router";
|
|
3
|
-
import { Sidebar as FzSidebar } from "@farmzone/fz-react-ui";
|
|
4
|
-
|
|
5
|
-
import { MENU_SECTIONS } from "./menu";
|
|
6
|
-
import UserInfo from "./UserInfo";
|
|
7
|
-
|
|
8
|
-
function SidebarHeader() {
|
|
9
|
-
const navigate = useNavigate();
|
|
10
|
-
return (
|
|
11
|
-
<div className="flex flex-col gap-4 my-4">
|
|
12
|
-
<div className="text-center cursor-pointer" onClick={() => navigate("/")}>
|
|
13
|
-
<div className="inline-flex items-center justify-center">
|
|
14
|
-
<img src="/favicon.ico" alt="logo" className="w-44 pr-4 object-contain" />
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
<UserInfo />
|
|
18
|
-
</div>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export default function Sidebar() {
|
|
23
|
-
const [isOpen, setIsOpen] = useState(true);
|
|
24
|
-
return (
|
|
25
|
-
<FzSidebar
|
|
26
|
-
isOpen={isOpen}
|
|
27
|
-
onClose={() => setIsOpen(false)}
|
|
28
|
-
menuSections={MENU_SECTIONS}
|
|
29
|
-
header={<SidebarHeader />}
|
|
30
|
-
showCollapseButton
|
|
31
|
-
/>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useNavigate } from "react-router";
|
|
3
|
+
import { Sidebar as FzSidebar } from "@farmzone/fz-react-ui";
|
|
4
|
+
|
|
5
|
+
import { MENU_SECTIONS } from "./menu";
|
|
6
|
+
import UserInfo from "./UserInfo";
|
|
7
|
+
|
|
8
|
+
function SidebarHeader() {
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex flex-col gap-4 my-4">
|
|
12
|
+
<div className="text-center cursor-pointer" onClick={() => navigate("/")}>
|
|
13
|
+
<div className="inline-flex items-center justify-center">
|
|
14
|
+
<img src="/favicon.ico" alt="logo" className="w-44 pr-4 object-contain" />
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<UserInfo />
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function Sidebar() {
|
|
23
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
24
|
+
return (
|
|
25
|
+
<FzSidebar
|
|
26
|
+
isOpen={isOpen}
|
|
27
|
+
onClose={() => setIsOpen(false)}
|
|
28
|
+
menuSections={MENU_SECTIONS}
|
|
29
|
+
header={<SidebarHeader />}
|
|
30
|
+
showCollapseButton
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|