@simplysm/sd-cli 13.0.71 → 13.0.74

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.
Files changed (108) hide show
  1. package/README.md +62 -14
  2. package/dist/commands/init.d.ts +4 -5
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +26 -8
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/publish.js +1 -1
  7. package/dist/commands/publish.js.map +1 -1
  8. package/dist/sd-cli-entry.d.ts.map +1 -1
  9. package/dist/sd-cli-entry.js +0 -20
  10. package/dist/sd-cli-entry.js.map +1 -1
  11. package/package.json +4 -4
  12. package/src/commands/init.ts +40 -21
  13. package/src/commands/publish.ts +1 -1
  14. package/src/sd-cli-entry.ts +0 -24
  15. package/src/utils/replace-deps.ts +361 -361
  16. package/src/utils/sd-config.ts +44 -44
  17. package/src/utils/tailwind-config-deps.ts +98 -98
  18. package/src/utils/template.ts +56 -56
  19. package/src/utils/tsconfig.ts +127 -127
  20. package/src/utils/typecheck-serialization.ts +86 -86
  21. package/templates/init/{.prettierrc.yaml.hbs → .prettierrc.yaml} +1 -1
  22. package/templates/init/eslint.config.ts +15 -0
  23. package/templates/init/mise.toml +3 -0
  24. package/templates/init/package.json.hbs +8 -7
  25. package/templates/init/packages/client-admin/index.html.hbs +144 -0
  26. package/templates/init/packages/client-admin/package.json.hbs +26 -0
  27. package/templates/init/packages/client-admin/public/assets/logo-landscape.png +0 -0
  28. package/templates/init/packages/client-admin/public/assets/logo.png +0 -0
  29. package/templates/init/packages/client-admin/src/App.tsx +42 -0
  30. package/templates/init/packages/client-admin/src/dev/DevDialog.tsx +34 -0
  31. package/templates/{add-client/__CLIENT__/src/main.css.hbs → init/packages/client-admin/src/main.css} +1 -1
  32. package/templates/init/packages/client-admin/src/main.tsx.hbs +146 -0
  33. package/templates/init/packages/client-admin/src/providers/AppServiceProvider.tsx.hbs +103 -0
  34. package/templates/init/packages/client-admin/src/providers/AppStructureProvider.tsx +84 -0
  35. package/templates/init/packages/client-admin/src/providers/AuthProvider.tsx.hbs +71 -0
  36. package/templates/init/packages/client-admin/src/providers/configureSharedData.ts.hbs +67 -0
  37. package/templates/init/packages/client-admin/src/views/auth/LoginView.tsx +132 -0
  38. package/templates/init/packages/client-admin/src/views/home/HomeView.tsx +108 -0
  39. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeDetail.tsx.hbs +262 -0
  40. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeSheet.tsx.hbs +271 -0
  41. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleDetail.tsx.hbs +154 -0
  42. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionDetail.tsx.hbs +123 -0
  43. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionView.tsx +52 -0
  44. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleSheet.tsx.hbs +125 -0
  45. package/templates/init/packages/client-admin/src/views/home/main/MainView.tsx.hbs +13 -0
  46. package/templates/init/packages/client-admin/src/views/home/my-info/MyInfoDetail.tsx.hbs +248 -0
  47. package/templates/init/packages/client-admin/src/views/home/system/system-log/SystemLogSheet.tsx.hbs +169 -0
  48. package/templates/init/packages/client-admin/src/views/not-found/NotFoundView.tsx +15 -0
  49. package/templates/init/packages/client-admin/tailwind.config.ts +10 -0
  50. package/templates/init/packages/db-main/package.json.hbs +13 -0
  51. package/templates/init/packages/db-main/src/MainDbContext.ts +20 -0
  52. package/templates/init/packages/db-main/src/dataLogExt.ts +127 -0
  53. package/templates/init/packages/db-main/src/index.ts +10 -0
  54. package/templates/init/packages/db-main/src/tables/Employee.ts +24 -0
  55. package/templates/init/packages/db-main/src/tables/EmployeeConfig.ts +13 -0
  56. package/templates/init/packages/db-main/src/tables/Role.ts +9 -0
  57. package/templates/init/packages/db-main/src/tables/RolePermission.ts +13 -0
  58. package/templates/init/packages/db-main/src/tables/_DataLog.ts +19 -0
  59. package/templates/init/packages/db-main/src/tables/_Log.ts +16 -0
  60. package/templates/init/packages/server/package.json.hbs +20 -0
  61. package/templates/init/packages/server/public-dev/dev//354/264/210/352/270/260/355/231/224.xlsx +0 -0
  62. package/templates/init/packages/server/src/index.ts +4 -0
  63. package/templates/init/packages/server/src/main.ts.hbs +34 -0
  64. package/templates/init/packages/server/src/services/AuthService.ts.hbs +171 -0
  65. package/templates/init/packages/server/src/services/DevService.ts.hbs +94 -0
  66. package/templates/init/packages/server/src/services/EmployeeService.ts.hbs +122 -0
  67. package/templates/init/packages/server/src/services/RoleService.ts.hbs +59 -0
  68. package/templates/init/{pnpm-workspace.yaml.hbs → pnpm-workspace.yaml} +3 -1
  69. package/templates/init/sd.config.ts.hbs +30 -1
  70. package/templates/init/tests/e2e/package.json.hbs +16 -0
  71. package/templates/init/tests/e2e/src/e2e.spec.ts +36 -0
  72. package/templates/init/tests/e2e/src/employee-crud.ts +204 -0
  73. package/templates/init/tests/e2e/src/login.ts +61 -0
  74. package/templates/init/tests/e2e/vitest.setup.ts.hbs +220 -0
  75. package/templates/init/tsconfig.json.hbs +0 -11
  76. package/templates/init/{vitest.config.ts.hbs → vitest.config.ts} +16 -12
  77. package/dist/commands/add-client.d.ts +0 -18
  78. package/dist/commands/add-client.d.ts.map +0 -1
  79. package/dist/commands/add-client.js +0 -79
  80. package/dist/commands/add-client.js.map +0 -6
  81. package/dist/commands/add-server.d.ts +0 -18
  82. package/dist/commands/add-server.d.ts.map +0 -1
  83. package/dist/commands/add-server.js +0 -83
  84. package/dist/commands/add-server.js.map +0 -6
  85. package/dist/utils/config-editor.d.ts +0 -17
  86. package/dist/utils/config-editor.d.ts.map +0 -1
  87. package/dist/utils/config-editor.js +0 -79
  88. package/dist/utils/config-editor.js.map +0 -6
  89. package/src/commands/add-client.ts +0 -126
  90. package/src/commands/add-server.ts +0 -138
  91. package/src/utils/config-editor.ts +0 -141
  92. package/templates/add-client/__CLIENT__/index.html.hbs +0 -13
  93. package/templates/add-client/__CLIENT__/package.json.hbs +0 -16
  94. package/templates/add-client/__CLIENT__/src/App.tsx.hbs +0 -65
  95. package/templates/add-client/__CLIENT__/src/appStructure.ts.hbs +0 -20
  96. package/templates/add-client/__CLIENT__/src/main.tsx.hbs +0 -24
  97. package/templates/add-client/__CLIENT__/src/pages/HomePage.tsx.hbs +0 -9
  98. package/templates/add-client/__CLIENT__/tailwind.config.ts.hbs +0 -15
  99. package/templates/add-server/__SERVER__/package.json.hbs +0 -10
  100. package/templates/add-server/__SERVER__/src/main.ts.hbs +0 -14
  101. package/templates/init/.gitignore.hbs +0 -26
  102. package/templates/init/.npmrc.hbs +0 -1
  103. package/templates/init/eslint.config.ts.hbs +0 -5
  104. package/templates/init/mise.toml.hbs +0 -3
  105. package/tests/config-editor.spec.ts +0 -160
  106. /package/templates/init/{.prettierignore.hbs → .prettierignore} +0 -0
  107. /package/templates/{add-client/__CLIENT__ → init/packages/client-admin}/public/favicon.ico +0 -0
  108. /package/templates/init/{stylelint.config.ts.hbs → stylelint.config.ts} +0 -0
@@ -0,0 +1,103 @@
1
+ import {
2
+ createContext,
3
+ createSignal,
4
+ onMount,
5
+ Show,
6
+ useContext,
7
+ type ParentComponent,
8
+ } from "solid-js";
9
+ import { useServiceClient } from "@simplysm/solid";
10
+ import {
11
+ createOrmClientConnector,
12
+ type RemoteService,
13
+ type ServiceClient,
14
+ } from "@simplysm/service-client";
15
+ import type {
16
+ AuthServiceMethods,
17
+ DevServiceMethods,
18
+ EmployeeServiceMethods,
19
+ } from "@{{projectName}}/server";
20
+ import { MainDbContext, type MainDbContext as MainDbContextType } from "@{{projectName}}/db-main";
21
+
22
+ export interface IAppServiceClient {
23
+ auth: RemoteService<AuthServiceMethods>;
24
+ dev: RemoteService<DevServiceMethods>;
25
+ employee: RemoteService<EmployeeServiceMethods>;
26
+ sc: ServiceClient;
27
+ orm: {
28
+ connect: <R>(
29
+ fn: (db: MainDbContextType) => Promise<R> | R,
30
+ connOpt?: { configName?: string },
31
+ ) => Promise<R>;
32
+ connectWithoutTransaction: <R>(
33
+ fn: (db: MainDbContextType) => Promise<R> | R,
34
+ connOpt?: { configName?: string },
35
+ ) => Promise<R>;
36
+ };
37
+ }
38
+
39
+ const AppServiceContext = createContext<IAppServiceClient>();
40
+
41
+ export const AppServiceProvider: ParentComponent = (props) => {
42
+ const sc = useServiceClient();
43
+ const [ready, setReady] = createSignal(false);
44
+
45
+ let value: IAppServiceClient;
46
+
47
+ onMount(async () => {
48
+ await sc.connect();
49
+ document.querySelector(".app-loading")?.remove();
50
+
51
+ const client = sc.get();
52
+ const connector = createOrmClientConnector(client);
53
+
54
+ const ormHelper = {
55
+ connect: async <R,>(
56
+ fn: (db: MainDbContextType) => Promise<R> | R,
57
+ connOpt?: { configName?: string },
58
+ ) => {
59
+ return connector.connect(
60
+ {
61
+ dbContextDef: MainDbContext,
62
+ connOpt: { configName: connOpt?.configName ?? "default" },
63
+ },
64
+ fn,
65
+ );
66
+ },
67
+ connectWithoutTransaction: async <R,>(
68
+ fn: (db: MainDbContextType) => Promise<R> | R,
69
+ connOpt?: { configName?: string },
70
+ ) => {
71
+ return connector.connectWithoutTransaction(
72
+ {
73
+ dbContextDef: MainDbContext,
74
+ connOpt: { configName: connOpt?.configName ?? "default" },
75
+ },
76
+ fn,
77
+ );
78
+ },
79
+ };
80
+
81
+ value = {
82
+ auth: client.getService<AuthServiceMethods>("AuthService"),
83
+ dev: client.getService<DevServiceMethods>("DevService"),
84
+ employee: client.getService<EmployeeServiceMethods>("EmployeeService"),
85
+ sc: client,
86
+ orm: ormHelper,
87
+ };
88
+
89
+ setReady(true);
90
+ });
91
+
92
+ return (
93
+ <Show when={ready()}>
94
+ <AppServiceContext.Provider value={value!}>{props.children}</AppServiceContext.Provider>
95
+ </Show>
96
+ );
97
+ };
98
+
99
+ export function useAppService(): IAppServiceClient {
100
+ const ctx = useContext(AppServiceContext);
101
+ if (!ctx) throw new Error("AppServiceProvider is required.");
102
+ return ctx;
103
+ }
@@ -0,0 +1,84 @@
1
+ import { lazy } from "solid-js";
2
+ import { createAppStructure } from "@simplysm/solid";
3
+ import { useAuth } from "./AuthProvider";
4
+
5
+ const MainView = lazy(() =>
6
+ import("../views/home/main/MainView").then((m) => ({ default: m.MainView })),
7
+ );
8
+ const MyInfoDetail = lazy(() =>
9
+ import("../views/home/my-info/MyInfoDetail").then((m) => ({ default: m.MyInfoDetail })),
10
+ );
11
+ const EmployeeSheet = lazy(() =>
12
+ import("../views/home/base/employee/EmployeeSheet").then((m) => ({ default: m.EmployeeSheet })),
13
+ );
14
+ const RolePermissionView = lazy(() =>
15
+ import("../views/home/base/role-permission/RolePermissionView").then((m) => ({
16
+ default: m.RolePermissionView,
17
+ })),
18
+ );
19
+ const SystemLogSheet = lazy(() =>
20
+ import("../views/home/system/system-log/SystemLogSheet").then((m) => ({
21
+ default: m.SystemLogSheet,
22
+ })),
23
+ );
24
+
25
+ export const { AppStructureProvider, useAppStructure } = createAppStructure(() => {
26
+ const auth = useAuth();
27
+ return {
28
+ permRecord: () => auth.authInfo()?.permissions,
29
+ items: [
30
+ {
31
+ code: "home",
32
+ title: "홈",
33
+ children: [
34
+ {
35
+ code: "main",
36
+ title: "메인",
37
+ component: MainView,
38
+ },
39
+ {
40
+ code: "my-info",
41
+ title: "설정",
42
+ component: MyInfoDetail,
43
+ isNotMenu: true,
44
+ },
45
+ {
46
+ code: "base",
47
+ title: "기준정보",
48
+ children: [
49
+ {
50
+ code: "employee",
51
+ title: "직원",
52
+ component: EmployeeSheet,
53
+ perms: ["use", "edit"],
54
+ subPerms: [
55
+ { title: "인증정보", code: "auth", perms: ["use", "edit"] },
56
+ { title: "개인정보", code: "personal", perms: ["use", "edit"] },
57
+ { title: "급여정보", code: "payroll", perms: ["use", "edit"] },
58
+ ],
59
+ },
60
+ {
61
+ code: "role-permission",
62
+ title: "권한그룹",
63
+ component: RolePermissionView,
64
+ perms: ["use", "edit"],
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ code: "system",
70
+ title: "시스템",
71
+ children: [
72
+ {
73
+ code: "system-log",
74
+ title: "시스템로그",
75
+ component: SystemLogSheet,
76
+ perms: ["use"],
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ },
82
+ ],
83
+ };
84
+ });
@@ -0,0 +1,71 @@
1
+ import { createContext, useContext, type Accessor, type ParentComponent } from "solid-js";
2
+ import { createSignal } from "solid-js";
3
+ import { useLocalStorage } from "@simplysm/solid";
4
+ import { useAppService } from "./AppServiceProvider";
5
+ import type { IAuthData } from "@{{projectName}}/server";
6
+
7
+ interface IAuthContextValue {
8
+ authInfo: Accessor<IAuthData | undefined>;
9
+ login: (email: string, password: string) => Promise<void>;
10
+ logout: () => void;
11
+ tryReloadAuth: () => Promise<boolean>;
12
+ }
13
+
14
+ const AuthContext = createContext<IAuthContextValue>();
15
+
16
+ export const AuthProvider: ParentComponent = (props) => {
17
+ const appService = useAppService();
18
+ const [authInfo, setAuthInfo] = createSignal<IAuthData>();
19
+ const [token, setToken] = useLocalStorage<string | undefined>("auth-token");
20
+
21
+ const login = async (email: string, password: string) => {
22
+ const result = await appService.auth.login(email, password);
23
+
24
+ const { token: newToken, ...authData } = result;
25
+ setToken(newToken);
26
+ await appService.sc.auth(newToken);
27
+
28
+ setAuthInfo(authData);
29
+ };
30
+
31
+ const logout = () => {
32
+ setToken(undefined);
33
+ setAuthInfo(undefined);
34
+ };
35
+
36
+ const tryReloadAuth = async (): Promise<boolean> => {
37
+ const currentToken = token();
38
+ if (currentToken == null || currentToken === "") return false;
39
+
40
+ try {
41
+ await appService.sc.auth(currentToken);
42
+
43
+ const result = await appService.auth.refresh();
44
+
45
+ const { token: newToken, ...authData } = result;
46
+ setToken(newToken);
47
+ await appService.sc.auth(newToken);
48
+
49
+ setAuthInfo(authData);
50
+
51
+ return true;
52
+ } catch (err) {
53
+ // eslint-disable-next-line no-console
54
+ console.warn("Failed to reload auth:", err);
55
+ setToken(undefined);
56
+ return false;
57
+ }
58
+ };
59
+
60
+ return (
61
+ <AuthContext.Provider value=\{{ authInfo, login, logout, tryReloadAuth }}>
62
+ {props.children}
63
+ </AuthContext.Provider>
64
+ );
65
+ };
66
+
67
+ export function useAuth(): IAuthContextValue {
68
+ const ctx = useContext(AuthContext);
69
+ if (!ctx) throw new Error("AuthProvider is required.");
70
+ return ctx;
71
+ }
@@ -0,0 +1,67 @@
1
+ import { useSharedData } from "@simplysm/solid";
2
+ import { expr } from "@{{projectName}}/db-main";
3
+ import { useAppService } from "./AppServiceProvider";
4
+
5
+ export type EmployeeSharedItem = {
6
+ id: number;
7
+ name: string;
8
+ email: string | undefined;
9
+ isDeleted: boolean;
10
+ };
11
+
12
+ export type RoleSharedItem = {
13
+ id: number;
14
+ name: string;
15
+ isDeleted: boolean;
16
+ };
17
+
18
+ export type AppSharedData = {
19
+ employee: EmployeeSharedItem;
20
+ role: RoleSharedItem;
21
+ };
22
+
23
+ export function configureSharedData() {
24
+ const appService = useAppService();
25
+
26
+ useSharedData<AppSharedData>().configure((_origin) => ({
27
+ employee: {
28
+ fetch: async (changeKeys) => {
29
+ return appService.orm.connect(async (db) => {
30
+ let qr = db.employee().select((item) => ({
31
+ id: item.id,
32
+ name: item.name,
33
+ email: item.email,
34
+ isDeleted: item.isDeleted,
35
+ }));
36
+ if (changeKeys) {
37
+ qr = qr.where((item) => [expr.in(item.id, changeKeys)]);
38
+ }
39
+ return qr.result();
40
+ });
41
+ },
42
+ orderBy: [[(item) => item.name, "asc"]],
43
+ getKey: (item) => item.id,
44
+ getSearchText: (item) => item.name,
45
+ getIsHidden: (item) => item.isDeleted,
46
+ },
47
+ role: {
48
+ fetch: async (changeKeys) => {
49
+ return appService.orm.connect(async (db) => {
50
+ let qr = db.role().select((item) => ({
51
+ id: item.id,
52
+ name: item.name,
53
+ isDeleted: item.isDeleted,
54
+ }));
55
+ if (changeKeys) {
56
+ qr = qr.where((item) => [expr.in(item.id, changeKeys)]);
57
+ }
58
+ return qr.result();
59
+ });
60
+ },
61
+ orderBy: [[(item) => item.name, "asc"]],
62
+ getKey: (item) => item.id,
63
+ getSearchText: (item) => item.name,
64
+ getIsHidden: (item) => item.isDeleted,
65
+ },
66
+ }));
67
+ }
@@ -0,0 +1,132 @@
1
+ import { createSignal, onMount } from "solid-js";
2
+ import { createStore } from "solid-js/store";
3
+ import { useNavigate } from "@solidjs/router";
4
+ import {
5
+ BusyContainer,
6
+ Button,
7
+ Card,
8
+ FormGroup,
9
+ Icon,
10
+ TextInput,
11
+ ThemeToggle,
12
+ useLocalStorage,
13
+ useNotification,
14
+ } from "@simplysm/solid";
15
+ import { useAuth } from "../../providers/AuthProvider";
16
+ import clsx from "clsx";
17
+ import { IconLock, IconMail } from "@tabler/icons-solidjs";
18
+
19
+ type Data = {
20
+ email?: string;
21
+ password?: string;
22
+ };
23
+
24
+ export function LoginView() {
25
+ const auth = useAuth();
26
+ const navigate = useNavigate();
27
+ const noti = useNotification();
28
+
29
+ const [busyCount, setBusyCount] = createSignal(0);
30
+ const [ready, setReady] = createSignal(false);
31
+
32
+ const [lastEmail, setLastEmail] = useLocalStorage<string | undefined>("last-login-email");
33
+ const [data, setData] = createStore<Data>({ email: lastEmail() });
34
+
35
+ onMount(async () => {
36
+ const ok = await auth.tryReloadAuth();
37
+ if (ok) {
38
+ navigate("/home/main", { replace: true });
39
+ return;
40
+ }
41
+ setReady(true);
42
+ });
43
+
44
+ const handleSubmit = async (e: Event) => {
45
+ e.preventDefault();
46
+ if (busyCount() > 0) return;
47
+
48
+ const currEmail = data.email!;
49
+ const currPassword = data.password!;
50
+
51
+ setBusyCount((c) => c + 1);
52
+ try {
53
+ await auth.login(currEmail, currPassword);
54
+ setLastEmail(currEmail);
55
+ navigate("/home/main", { replace: true });
56
+ } catch (err) {
57
+ noti.error(err, "로그인 오류");
58
+ }
59
+ setBusyCount((c) => c - 1);
60
+ };
61
+
62
+ return (
63
+ <BusyContainer
64
+ ready={ready()}
65
+ busy={busyCount() > 0}
66
+ class={clsx("flex items-center justify-center", "pb-64", "bg-base-100 dark:bg-base-900")}
67
+ >
68
+ <div class={"max-w-sm"}>
69
+ {/* Logo */}
70
+ <div class={clsx("flex justify-center", "mb-4", "animate-fade-in")}>
71
+ <img src="assets/logo.png" alt="logo" class="scale-75" />
72
+ </div>
73
+
74
+ <Card class={clsx("rounded-2xl p-8", "animate-fade-in [animation-delay:0.3s]")}>
75
+ {/* Form */}
76
+ <form onSubmit={handleSubmit}>
77
+ <FormGroup class={"w-full"}>
78
+ <FormGroup.Item>
79
+ <TextInput
80
+ class="w-full"
81
+ type="email"
82
+ placeholder="이메일을 입력하세요"
83
+ required
84
+ touchMode
85
+ size="lg"
86
+ autocomplete="employeename"
87
+ value={data.email}
88
+ onValueChange={(v) => setData("email", v)}
89
+ >
90
+ <TextInput.Prefix>
91
+ <Icon icon={IconMail} />
92
+ </TextInput.Prefix>
93
+ </TextInput>
94
+ </FormGroup.Item>
95
+ <FormGroup.Item>
96
+ <TextInput
97
+ required
98
+ touchMode
99
+ class="w-full"
100
+ type="password"
101
+ placeholder="비밀번호를 입력하세요"
102
+ size="lg"
103
+ autocomplete="current-password"
104
+ value={data.password}
105
+ onValueChange={(v) => setData("password", v)}
106
+ >
107
+ <TextInput.Prefix>
108
+ <Icon icon={IconLock} />
109
+ </TextInput.Prefix>
110
+ </TextInput>
111
+ </FormGroup.Item>
112
+
113
+ <FormGroup.Item>
114
+ <Button
115
+ theme="primary"
116
+ variant="solid"
117
+ class="w-full"
118
+ type="submit"
119
+ size="xl"
120
+ disabled={busyCount() > 0}
121
+ >
122
+ 로그인
123
+ </Button>
124
+ </FormGroup.Item>
125
+ </FormGroup>
126
+ </form>
127
+ </Card>
128
+ </div>
129
+ <ThemeToggle class="fixed bottom-4 right-4" />
130
+ </BusyContainer>
131
+ );
132
+ }
@@ -0,0 +1,108 @@
1
+ import { createSignal, onMount, Show, Suspense } from "solid-js";
2
+ import { type RouteSectionProps, useLocation, useNavigate } from "@solidjs/router";
3
+ import {
4
+ BusyContainer,
5
+ NotificationBell,
6
+ Sidebar,
7
+ ThemeToggle,
8
+ Topbar,
9
+ useNotification,
10
+ } from "@simplysm/solid";
11
+ import { env } from "@simplysm/core-common";
12
+ import { useAuth } from "../../providers/AuthProvider";
13
+ import { useAppService } from "../../providers/AppServiceProvider";
14
+ import { useAppStructure } from "../../providers/AppStructureProvider";
15
+
16
+ function HomeContent(props: RouteSectionProps) {
17
+ const auth = useAuth();
18
+ const appService = useAppService();
19
+ const navigate = useNavigate();
20
+ const location = useLocation();
21
+ const noti = useNotification();
22
+ const appStructure = useAppStructure();
23
+
24
+ const [ready, setReady] = createSignal(false);
25
+
26
+ const titleChain = () => {
27
+ const chain = appStructure.getTitleChainByHref(location.pathname);
28
+ // 최상위 그룹(홈) 제외
29
+ return chain.length > 1 ? chain.slice(1) : chain;
30
+ };
31
+
32
+ const employeeMenus = [
33
+ {
34
+ title: "설정",
35
+ onClick: () => navigate("/home/my-info"),
36
+ },
37
+ {
38
+ title: "로그아웃",
39
+ onClick: () => {
40
+ auth.logout();
41
+ navigate("/login", { replace: true });
42
+ },
43
+ },
44
+ ];
45
+
46
+ onMount(async () => {
47
+ try {
48
+ // 1. Check auth - reload token only if not already authenticated
49
+ if (!auth.authInfo()) {
50
+ const authOk = await auth.tryReloadAuth();
51
+ if (!authOk) {
52
+ navigate("/login", { replace: true });
53
+ return;
54
+ }
55
+ }
56
+
57
+ await appService.orm.connectWithoutTransaction(async (db) => {
58
+ await db.initialize();
59
+ });
60
+
61
+ setReady(true);
62
+ } catch (err) {
63
+ noti.error(err, "초기화 실패");
64
+ }
65
+ });
66
+
67
+ return (
68
+ <BusyContainer ready={ready()}>
69
+ <Sidebar.Container>
70
+ <Sidebar>
71
+ <div class="p-2 px-4">
72
+ <img src="assets/logo-landscape.png" alt="SIMPLYSM" class="h-9 w-auto" />
73
+ </div>
74
+ <Sidebar.User
75
+ name={auth.authInfo()?.employeeName ?? ""}
76
+ description={auth.authInfo()?.email ?? ""}
77
+ menus={employeeMenus}
78
+ />
79
+ <Sidebar.Menu menus={appStructure.usableMenus()} class="flex-1" />
80
+ <div class="pointer-events-none px-2 py-1 text-sm text-black/30 dark:text-white/30">
81
+ v{env.VER}
82
+ <Show when={env.DEV}>(dev)</Show>
83
+ </div>
84
+ </Sidebar>
85
+
86
+ <Topbar.Container>
87
+ <Topbar>
88
+ <span class="ml-2 mr-6 text-lg font-bold">{titleChain().join(" > ")}</span>
89
+ <Topbar.Actions />
90
+ <div class="flex-1" />
91
+ <NotificationBell />
92
+ <ThemeToggle size="sm" />
93
+ </Topbar>
94
+
95
+ <main class="flex-1 overflow-auto">
96
+ <Suspense fallback={<BusyContainer ready={false} />}>
97
+ {props.children}
98
+ </Suspense>
99
+ </main>
100
+ </Topbar.Container>
101
+ </Sidebar.Container>
102
+ </BusyContainer>
103
+ );
104
+ }
105
+
106
+ export function HomeView(props: RouteSectionProps) {
107
+ return <HomeContent {...props} />;
108
+ }