@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
@@ -1,86 +1,86 @@
1
- import ts from "typescript";
2
- import { fsExistsSync, fsReadSync } from "@simplysm/core-node";
3
-
4
- /**
5
- * Serialized Diagnostic that can be passed to Worker
6
- */
7
- export interface SerializedDiagnostic {
8
- category: number;
9
- code: number;
10
- messageText: string;
11
- file?: {
12
- fileName: string;
13
- };
14
- start?: number;
15
- length?: number;
16
- }
17
-
18
- /**
19
- * Convert Diagnostic to serializable form
20
- * (remove circular references/functions for structured clone communication between Worker threads)
21
- */
22
- export function serializeDiagnostic(diagnostic: ts.Diagnostic): SerializedDiagnostic {
23
- // If DiagnosticMessageChain, flatten entire chain to preserve all context info
24
- const messageText = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
25
-
26
- return {
27
- category: diagnostic.category,
28
- code: diagnostic.code,
29
- messageText,
30
- file: diagnostic.file
31
- ? {
32
- fileName: diagnostic.file.fileName,
33
- }
34
- : undefined,
35
- start: diagnostic.start,
36
- length: diagnostic.length,
37
- };
38
- }
39
-
40
- /**
41
- * Determine TypeScript ScriptKind from filename
42
- */
43
- function getScriptKind(fileName: string): ts.ScriptKind {
44
- if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
45
- if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
46
- if (fileName.endsWith(".js") || fileName.endsWith(".mjs") || fileName.endsWith(".cjs"))
47
- return ts.ScriptKind.JS;
48
- return ts.ScriptKind.TS;
49
- }
50
-
51
- /**
52
- * Restore SerializedDiagnostic to ts.Diagnostic
53
- * Reads actual file contents so source code context is displayed in formatDiagnosticsWithColorAndContext
54
- * @param serialized - Serialized diagnostic information
55
- * @param fileCache - File content cache (prevent duplicate reads of same file)
56
- * @returns Restored ts.Diagnostic object
57
- */
58
- export function deserializeDiagnostic(
59
- serialized: SerializedDiagnostic,
60
- fileCache: Map<string, string>,
61
- ): ts.Diagnostic {
62
- let file: ts.SourceFile | undefined;
63
- if (serialized.file != null) {
64
- const fileName = serialized.file.fileName;
65
-
66
- // Get cached file content (read and cache if not present)
67
- // If file was deleted or inaccessible, treat as empty content
68
- // (source code context won't be displayed but diagnostic message is shown normally)
69
- if (!fileCache.has(fileName)) {
70
- fileCache.set(fileName, fsExistsSync(fileName) ? fsReadSync(fileName) : "");
71
- }
72
- const content = fileCache.get(fileName)!;
73
-
74
- const scriptKind = getScriptKind(fileName);
75
- file = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, false, scriptKind);
76
- }
77
-
78
- return {
79
- category: serialized.category,
80
- code: serialized.code,
81
- messageText: serialized.messageText,
82
- file,
83
- start: serialized.start,
84
- length: serialized.length,
85
- };
86
- }
1
+ import ts from "typescript";
2
+ import { fsExistsSync, fsReadSync } from "@simplysm/core-node";
3
+
4
+ /**
5
+ * Serialized Diagnostic that can be passed to Worker
6
+ */
7
+ export interface SerializedDiagnostic {
8
+ category: number;
9
+ code: number;
10
+ messageText: string;
11
+ file?: {
12
+ fileName: string;
13
+ };
14
+ start?: number;
15
+ length?: number;
16
+ }
17
+
18
+ /**
19
+ * Convert Diagnostic to serializable form
20
+ * (remove circular references/functions for structured clone communication between Worker threads)
21
+ */
22
+ export function serializeDiagnostic(diagnostic: ts.Diagnostic): SerializedDiagnostic {
23
+ // If DiagnosticMessageChain, flatten entire chain to preserve all context info
24
+ const messageText = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
25
+
26
+ return {
27
+ category: diagnostic.category,
28
+ code: diagnostic.code,
29
+ messageText,
30
+ file: diagnostic.file
31
+ ? {
32
+ fileName: diagnostic.file.fileName,
33
+ }
34
+ : undefined,
35
+ start: diagnostic.start,
36
+ length: diagnostic.length,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Determine TypeScript ScriptKind from filename
42
+ */
43
+ function getScriptKind(fileName: string): ts.ScriptKind {
44
+ if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
45
+ if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
46
+ if (fileName.endsWith(".js") || fileName.endsWith(".mjs") || fileName.endsWith(".cjs"))
47
+ return ts.ScriptKind.JS;
48
+ return ts.ScriptKind.TS;
49
+ }
50
+
51
+ /**
52
+ * Restore SerializedDiagnostic to ts.Diagnostic
53
+ * Reads actual file contents so source code context is displayed in formatDiagnosticsWithColorAndContext
54
+ * @param serialized - Serialized diagnostic information
55
+ * @param fileCache - File content cache (prevent duplicate reads of same file)
56
+ * @returns Restored ts.Diagnostic object
57
+ */
58
+ export function deserializeDiagnostic(
59
+ serialized: SerializedDiagnostic,
60
+ fileCache: Map<string, string>,
61
+ ): ts.Diagnostic {
62
+ let file: ts.SourceFile | undefined;
63
+ if (serialized.file != null) {
64
+ const fileName = serialized.file.fileName;
65
+
66
+ // Get cached file content (read and cache if not present)
67
+ // If file was deleted or inaccessible, treat as empty content
68
+ // (source code context won't be displayed but diagnostic message is shown normally)
69
+ if (!fileCache.has(fileName)) {
70
+ fileCache.set(fileName, fsExistsSync(fileName) ? fsReadSync(fileName) : "");
71
+ }
72
+ const content = fileCache.get(fileName)!;
73
+
74
+ const scriptKind = getScriptKind(fileName);
75
+ file = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, false, scriptKind);
76
+ }
77
+
78
+ return {
79
+ category: serialized.category,
80
+ code: serialized.code,
81
+ messageText: serialized.messageText,
82
+ file,
83
+ start: serialized.start,
84
+ length: serialized.length,
85
+ };
86
+ }
@@ -1,4 +1,4 @@
1
- printWidth: 120
1
+ printWidth: 100
2
2
  tabWidth: 2
3
3
  useTabs: false
4
4
  semi: true
@@ -0,0 +1,15 @@
1
+ import simplysmRecommended from "@simplysm/lint/eslint-recommended";
2
+ import { globalIgnores } from "eslint/config";
3
+
4
+ export default [
5
+ globalIgnores([".legacy/**"]),
6
+ ...simplysmRecommended,
7
+ {
8
+ files: ["**/*.{ts,tsx}"],
9
+ settings: {
10
+ tailwindcss: {
11
+ config: "packages/client-admin/tailwind.config.ts",
12
+ },
13
+ },
14
+ },
15
+ ];
@@ -0,0 +1,3 @@
1
+ [tools]
2
+ node = "20.20.0"
3
+ pnpm = "10.30.0"
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "{{projectName}}",
3
3
  "version": "1.0.0",
4
+ "description": "{{description}}",
4
5
  "type": "module",
5
6
  "private": true,
6
7
  "scripts": {
@@ -12,18 +13,18 @@
12
13
  "typecheck": "sd-cli typecheck",
13
14
  "lint": "sd-cli lint",
14
15
  "lint:fix": "sd-cli lint --fix",
16
+ "check": "sd-cli check",
15
17
  "vitest": "vitest"
16
18
  },
17
19
  "devDependencies": {
18
- "@simplysm/sd-cli": "~13.0.71",
19
- "@simplysm/sd-claude": "~13.0.71",
20
- "@simplysm/lint": "~13.0.71",
21
- "@types/node": "^20.19.33",
22
- "eslint": "^9.39.2",
20
+ "@simplysm/lint": "~13.0.74",
21
+ "@simplysm/sd-cli": "~13.0.74",
22
+ "@types/node": "^20.19.35",
23
+ "eslint": "^9.39.3",
23
24
  "prettier": "^3.8.1",
24
- "stylelint": "^16.13.0",
25
+ "stylelint": "^16.26.1",
25
26
  "typescript": "^5.9.3",
26
- "vite-tsconfig-paths": "^6.1.0",
27
+ "vite-tsconfig-paths": "^6.1.1",
27
28
  "vitest": "^4.0.18"
28
29
  }
29
30
  }
@@ -0,0 +1,144 @@
1
+ <!doctype html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="favicon.ico" />
7
+ <title>{{projectName}}</title>
8
+ <script>
9
+ (function () {
10
+ var theme = localStorage.getItem("client-admin.theme");
11
+ if (theme === "dark") {
12
+ document.documentElement.classList.add("dark");
13
+ } else if (theme === "light") {
14
+ document.documentElement.classList.remove("dark");
15
+ }
16
+ })();
17
+ </script>
18
+ <style>
19
+ * {
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ html,
24
+ body,
25
+ #root {
26
+ padding: 0;
27
+ margin: 0;
28
+ width: 100%;
29
+ height: 100%;
30
+ }
31
+
32
+ .app-loading {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ justify-content: center;
37
+ height: 100%;
38
+ width: 100%;
39
+ gap: 24px;
40
+ background: #f9fafb;
41
+ padding-bottom: 128px;
42
+ position: relative;
43
+ overflow: hidden;
44
+ }
45
+
46
+ .dark .app-loading {
47
+ background: #18181b;
48
+ }
49
+
50
+ @media (prefers-color-scheme: dark) {
51
+ .app-loading {
52
+ background: #18181b;
53
+ }
54
+
55
+ .app-loading-spinner::before {
56
+ border-top-color: #3b82f6;
57
+ border-right-color: #2563eb;
58
+ }
59
+
60
+ .app-loading-spinner::after {
61
+ border-bottom-color: #60a5fa;
62
+ border-left-color: #3b82f6;
63
+ }
64
+ }
65
+
66
+ .app-loading img {
67
+ width: auto;
68
+ position: relative;
69
+ z-index: 1;
70
+ margin-bottom: 32px;
71
+ }
72
+
73
+ .app-loading-spinner {
74
+ width: 48px;
75
+ height: 48px;
76
+ position: relative;
77
+ z-index: 1;
78
+ }
79
+
80
+ .app-loading-spinner::before {
81
+ content: "";
82
+ position: absolute;
83
+ inset: 0;
84
+ border: 3px solid transparent;
85
+ border-radius: 50%;
86
+ border-top-color: #60a5fa;
87
+ border-right-color: #3b82f6;
88
+ animation: spinClockwise 1.2s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
89
+ }
90
+
91
+ .dark .app-loading-spinner::before {
92
+ border-top-color: #3b82f6;
93
+ border-right-color: #2563eb;
94
+ }
95
+
96
+ .app-loading-spinner::after {
97
+ content: "";
98
+ position: absolute;
99
+ inset: 8px;
100
+ border: 3px solid transparent;
101
+ border-radius: 50%;
102
+ border-bottom-color: #93c5fd;
103
+ border-left-color: #60a5fa;
104
+ animation: spinCounterClockwise 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
105
+ }
106
+
107
+ .dark .app-loading-spinner::after {
108
+ border-bottom-color: #60a5fa;
109
+ border-left-color: #3b82f6;
110
+ }
111
+
112
+ @keyframes spinClockwise {
113
+ 0% {
114
+ transform: rotate(0deg);
115
+ opacity: 1;
116
+ }
117
+ 100% {
118
+ transform: rotate(360deg);
119
+ opacity: 1;
120
+ }
121
+ }
122
+
123
+ @keyframes spinCounterClockwise {
124
+ 0% {
125
+ transform: rotate(0deg);
126
+ opacity: 0.8;
127
+ }
128
+ 100% {
129
+ transform: rotate(-360deg);
130
+ opacity: 0.8;
131
+ }
132
+ }
133
+ </style>
134
+ </head>
135
+ <body>
136
+ <div id="root">
137
+ <div class="app-loading">
138
+ <img src="assets/logo-landscape.png" alt="logo" />
139
+ <div class="app-loading-spinner"></div>
140
+ </div>
141
+ </div>
142
+ <script type="module" src="src/main.tsx"></script>
143
+ </body>
144
+ </html>
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@{{projectName}}/client-admin",
3
+ "version": "1.0.0",
4
+ "description": "{{description}}",
5
+ "type": "module",
6
+ "private": true,
7
+ "dependencies": {
8
+ "@{{projectName}}/db-main": "workspace:*",
9
+ "@simplysm/core-browser": "~13.0.74",
10
+ "@simplysm/core-common": "~13.0.74",
11
+ "@simplysm/excel": "^13.0.71",
12
+ "@simplysm/orm-common": "~13.0.74",
13
+ "@simplysm/service-client": "~13.0.74",
14
+ "@simplysm/solid": "~13.0.74",
15
+ "@solid-primitives/event-listener": "^2.4.5",
16
+ "@solidjs/router": "^0.15.4",
17
+ "@tabler/icons-solidjs": "^3.37.1",
18
+ "clsx": "^2.1.1",
19
+ "consola": "^3.4.2",
20
+ "solid-js": "^1.9.11",
21
+ "zod": "^4.3.6"
22
+ },
23
+ "devDependencies": {
24
+ "tailwindcss": "^3.4.19"
25
+ }
26
+ }
@@ -0,0 +1,42 @@
1
+ import type { RouteSectionProps } from "@solidjs/router";
2
+ import { createEventListener } from "@solid-primitives/event-listener";
3
+ import { useDialog } from "@simplysm/solid";
4
+ import { env } from "@simplysm/core-common";
5
+ import { DevDialog } from "./dev/DevDialog";
6
+
7
+ export function App(props: RouteSectionProps) {
8
+ const dialog = useDialog();
9
+
10
+ if (env.DEV) {
11
+ let preservedKeys: string[] = [];
12
+
13
+ createEventListener(document, "keydown", async (event) => {
14
+ if (
15
+ event.ctrlKey &&
16
+ event.altKey &&
17
+ event.shiftKey &&
18
+ (event.key === "F11" || event.key === "F12")
19
+ ) {
20
+ event.preventDefault();
21
+ event.stopPropagation();
22
+
23
+ preservedKeys.push(event.key);
24
+ preservedKeys = preservedKeys.slice(-4);
25
+
26
+ if (
27
+ preservedKeys[0] === "F12" &&
28
+ preservedKeys[1] === "F11" &&
29
+ preservedKeys[2] === "F12" &&
30
+ preservedKeys[3] === "F12"
31
+ ) {
32
+ preservedKeys = [];
33
+ await dialog.show(() => <DevDialog />, {
34
+ header: "DEV",
35
+ });
36
+ }
37
+ }
38
+ });
39
+ }
40
+
41
+ return <>{props.children}</>;
42
+ }
@@ -0,0 +1,34 @@
1
+ import { createSignal } from "solid-js";
2
+ import { BusyContainer, Button, useNotification } from "@simplysm/solid";
3
+ import { useAppService } from "../providers/AppServiceProvider";
4
+ import { useAppStructure } from "../providers/AppStructureProvider";
5
+
6
+ export function DevDialog() {
7
+ const serv = useAppService();
8
+ const noti = useNotification();
9
+ const appStructure = useAppStructure();
10
+ const [busyCount, setBusyCount] = createSignal(0);
11
+
12
+ const handleInitDb = async () => {
13
+ setBusyCount((c) => c + 1);
14
+ try {
15
+ const permCodes = appStructure.allFlatPerms.map((p) => p.code);
16
+ await serv.dev.initDb(permCodes);
17
+ noti.success("DB 초기화 완료");
18
+ } catch (err) {
19
+ noti.error(err, "오류");
20
+ }
21
+ setBusyCount((c) => c - 1);
22
+ };
23
+
24
+ return (
25
+ <BusyContainer busy={busyCount() > 0}>
26
+ <div class="inline-flex flex-row gap-2 p-2">
27
+ <Button onClick={handleInitDb} theme="primary" variant="solid">
28
+ DB 초기화
29
+ </Button>
30
+ <Button onClick={() => location.reload()}>새로고침</Button>
31
+ </div>
32
+ </BusyContainer>
33
+ );
34
+ }
@@ -1,4 +1,4 @@
1
- @import "@simplysm/solid/src/base.css";
1
+ @import "@simplysm/solid/tailwind.css";
2
2
 
3
3
  @tailwind components;
4
4
  @tailwind utilities;
@@ -0,0 +1,146 @@
1
+ import { render } from "solid-js/web";
2
+ import { HashRouter, Navigate, Route } from "@solidjs/router";
3
+ import { For } from "solid-js";
4
+ import {
5
+ DialogProvider,
6
+ PrintProvider,
7
+ SystemProvider,
8
+ useLogger,
9
+ useSyncStorage,
10
+ } from "@simplysm/solid";
11
+ import { DateTime, env, jsonStringify } from "@simplysm/core-common";
12
+ import { expr } from "@{{projectName}}/db-main";
13
+ import { App } from "./App";
14
+
15
+ import { NotFoundView } from "./views/not-found/NotFoundView";
16
+ import { AppStructureProvider, useAppStructure } from "./providers/AppStructureProvider";
17
+ import { AppServiceProvider, useAppService } from "./providers/AppServiceProvider";
18
+ import { AuthProvider, useAuth } from "./providers/AuthProvider";
19
+ import { configureSharedData } from "./providers/configureSharedData";
20
+ import "./main.css";
21
+ import { LoginView } from "./views/auth/LoginView";
22
+ import { HomeView } from "./views/home/HomeView";
23
+
24
+ function AppRoot() {
25
+ const appService = useAppService();
26
+ const auth = useAuth();
27
+ const appStructure = useAppStructure();
28
+
29
+ // SyncStorage
30
+ useSyncStorage()!.configure((origin) => ({
31
+ async getItem(key) {
32
+ const info = auth.authInfo();
33
+ if (!info) return origin.getItem(key);
34
+
35
+ return appService.orm.connectWithoutTransaction(async (db) => {
36
+ const row = await db
37
+ .employeeConfig()
38
+ .where((c) => [expr.eq(c.employeeId, info.employeeId), expr.eq(c.code, key)])
39
+ .first();
40
+ return row?.valueJson ?? null;
41
+ });
42
+ },
43
+
44
+ async setItem(key, value) {
45
+ const info = auth.authInfo();
46
+ if (!info) {
47
+ await origin.setItem(key, value);
48
+ return;
49
+ }
50
+
51
+ await appService.orm.connect(async (db) => {
52
+ await db
53
+ .employeeConfig()
54
+ .where((c) => [expr.eq(c.employeeId, info.employeeId), expr.eq(c.code, key)])
55
+ .upsert(() => ({
56
+ employeeId: expr.val("number", info.employeeId),
57
+ code: expr.val("string", key),
58
+ valueJson: expr.val("string", value),
59
+ }));
60
+ });
61
+ },
62
+
63
+ async removeItem(key) {
64
+ const info = auth.authInfo();
65
+ if (!info) {
66
+ await origin.removeItem(key);
67
+ return;
68
+ }
69
+
70
+ await appService.orm.connect(async (db) => {
71
+ await db
72
+ .employeeConfig()
73
+ .where((c) => [expr.eq(c.employeeId, info.employeeId), expr.eq(c.code, key)])
74
+ .delete();
75
+ });
76
+ },
77
+ }));
78
+
79
+ // Logger
80
+ useLogger().configure((origin) => ({
81
+ async write(severity, ...data) {
82
+ if (env.DEV) {
83
+ await origin.write(severity, ...data);
84
+ return;
85
+ }
86
+
87
+ try {
88
+ const message = data.map((d) => (typeof d === "string" ? d : jsonStringify(d))).join(" ");
89
+
90
+ await appService.orm.connect(async (db) => {
91
+ await db._log().insert([
92
+ {
93
+ clientName: "client-admin",
94
+ dateTime: new DateTime(),
95
+ severity,
96
+ message,
97
+ employeeId: auth.authInfo()?.employeeId,
98
+ },
99
+ ]);
100
+ });
101
+ } catch (err) {
102
+ await origin.write(severity, ...data);
103
+ await origin.write("error", "로그 저장 실패:", err);
104
+ }
105
+ },
106
+ }));
107
+
108
+ // SharedData
109
+ configureSharedData();
110
+
111
+ return (
112
+ <HashRouter>
113
+ <Route path="/" component={App}>
114
+ <Route path="/" component={() => <Navigate href="/login" />} />
115
+ <Route path="/login" component={LoginView} />
116
+ <Route path="/home" component={HomeView}>
117
+ <Route path="/" component={() => <Navigate href="/home/main" />} />
118
+ <For each={appStructure.usableRoutes()}>
119
+ {(r) => <Route path={r.path} component={r.component} />}
120
+ </For>
121
+ <Route path="*" component={NotFoundView} />
122
+ </Route>
123
+ <Route path="*" component={NotFoundView} />
124
+ </Route>
125
+ </HashRouter>
126
+ );
127
+ }
128
+
129
+ render(
130
+ () => (
131
+ <SystemProvider clientName="client-admin">
132
+ <AppServiceProvider>
133
+ <AuthProvider>
134
+ <AppStructureProvider>
135
+ <PrintProvider>
136
+ <DialogProvider>
137
+ <AppRoot />
138
+ </DialogProvider>
139
+ </PrintProvider>
140
+ </AppStructureProvider>
141
+ </AuthProvider>
142
+ </AppServiceProvider>
143
+ </SystemProvider>
144
+ ),
145
+ document.getElementById("root")!,
146
+ );