sonamu 0.0.1

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 (60) hide show
  1. package/.pnp.cjs +15552 -0
  2. package/.pnp.loader.mjs +285 -0
  3. package/.vscode/extensions.json +6 -0
  4. package/.vscode/settings.json +9 -0
  5. package/.yarnrc.yml +5 -0
  6. package/dist/bin/cli.d.ts +2 -0
  7. package/dist/bin/cli.d.ts.map +1 -0
  8. package/dist/bin/cli.js +123 -0
  9. package/dist/bin/cli.js.map +1 -0
  10. package/dist/index.js +34 -0
  11. package/package.json +60 -0
  12. package/src/api/caster.ts +72 -0
  13. package/src/api/code-converters.ts +552 -0
  14. package/src/api/context.ts +20 -0
  15. package/src/api/decorators.ts +63 -0
  16. package/src/api/index.ts +5 -0
  17. package/src/api/init.ts +128 -0
  18. package/src/bin/cli.ts +115 -0
  19. package/src/database/base-model.ts +287 -0
  20. package/src/database/db.ts +95 -0
  21. package/src/database/knex-plugins/knex-on-duplicate-update.ts +41 -0
  22. package/src/database/upsert-builder.ts +231 -0
  23. package/src/exceptions/error-handler.ts +29 -0
  24. package/src/exceptions/so-exceptions.ts +91 -0
  25. package/src/index.ts +17 -0
  26. package/src/shared/web.shared.ts.txt +119 -0
  27. package/src/smd/migrator.ts +1462 -0
  28. package/src/smd/smd-manager.ts +141 -0
  29. package/src/smd/smd-utils.ts +266 -0
  30. package/src/smd/smd.ts +533 -0
  31. package/src/syncer/index.ts +1 -0
  32. package/src/syncer/syncer.ts +1283 -0
  33. package/src/templates/base-template.ts +19 -0
  34. package/src/templates/generated.template.ts +247 -0
  35. package/src/templates/generated_http.template.ts +114 -0
  36. package/src/templates/index.ts +1 -0
  37. package/src/templates/init_enums.template.ts +71 -0
  38. package/src/templates/init_generated.template.ts +44 -0
  39. package/src/templates/init_types.template.ts +38 -0
  40. package/src/templates/model.template.ts +168 -0
  41. package/src/templates/model_test.template.ts +39 -0
  42. package/src/templates/service.template.ts +263 -0
  43. package/src/templates/smd.template.ts +49 -0
  44. package/src/templates/view_enums_buttonset.template.ts +34 -0
  45. package/src/templates/view_enums_dropdown.template.ts +67 -0
  46. package/src/templates/view_enums_select.template.ts +60 -0
  47. package/src/templates/view_form.template.ts +397 -0
  48. package/src/templates/view_id_all_select.template.ts +34 -0
  49. package/src/templates/view_id_async_select.template.ts +113 -0
  50. package/src/templates/view_list.template.ts +652 -0
  51. package/src/templates/view_list_columns.template.ts +59 -0
  52. package/src/templates/view_search_input.template.ts +67 -0
  53. package/src/testing/fixture-manager.ts +271 -0
  54. package/src/types/types.ts +668 -0
  55. package/src/typings/knex.d.ts +24 -0
  56. package/src/utils/controller.ts +21 -0
  57. package/src/utils/lodash-able.ts +11 -0
  58. package/src/utils/model.ts +33 -0
  59. package/src/utils/utils.ts +28 -0
  60. package/tsconfig.json +47 -0
@@ -0,0 +1,652 @@
1
+ import { camelize, underscore } from "inflection";
2
+ import { flattenDeep, uniq } from "lodash";
3
+ import { z } from "zod";
4
+ import { RenderingNode, TemplateKey, TemplateOptions } from "../types/types";
5
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
6
+ import { isEnumProp, isRelationProp, RelationProp } from "../types/types";
7
+ import { RenderedTemplate } from "../syncer/syncer";
8
+ import { Template } from "./base-template";
9
+ export class Template__view_list extends Template {
10
+ constructor() {
11
+ super("view_list");
12
+ }
13
+
14
+ getTargetAndPath(names: SMDNamesRecord) {
15
+ return {
16
+ target: "web/src/pages/admin",
17
+ path: `${names.fsPlural}/index.tsx`,
18
+ };
19
+ }
20
+
21
+ wrapTc(
22
+ body: string,
23
+ key: string,
24
+ collapsing: boolean = true,
25
+ className: string = ""
26
+ ) {
27
+ return `<Table.Cell key="${key}"${collapsing ? " collapsing" : ""}${
28
+ className ? ` className={\`${className}\`}` : ""
29
+ }>${body}</Table.Cell>`;
30
+ }
31
+
32
+ renderColumn(
33
+ smdId: string,
34
+ col: RenderingNode,
35
+ names: SMDNamesRecord,
36
+ parentObj: string = "row",
37
+ withoutName: boolean = false
38
+ ): string {
39
+ const colName = withoutName ? `${parentObj}` : `${parentObj}.${col.name}`;
40
+
41
+ switch (col.renderType) {
42
+ case "string-plain":
43
+ case "number-id":
44
+ return `<>{${colName}}</>`;
45
+ case "number-fk_id":
46
+ const relPropFk = getRelationPropFromColName(
47
+ smdId,
48
+ col.name.replace("_id", "")
49
+ );
50
+ return `<>${relPropFk.with}#{${colName}}</>`;
51
+ case "string-image":
52
+ return `<img src={${colName}} />`;
53
+ case "string-datetime":
54
+ if (col.nullable) {
55
+ return `<span className="text-tiny">{${colName} === null ? '-' : DateTime.fromSQL(${colName}).toFormat('y-MM-dd')}</span>`;
56
+ } else {
57
+ return `<span className="text-tiny">{DateTime.fromSQL(${colName}).toFormat('y-MM-dd')}</span>`;
58
+ }
59
+ case "boolean":
60
+ return `<>{${colName} ? <Label color='green' circular>O</Label> : <Label color='grey' circular>X</Label> }</>`;
61
+ case "enums":
62
+ const { targetMDNames, name } = getEnumInfoFromColName(smdId, col.name);
63
+ return `<>{${col.nullable ? `${colName} && ` : ""}${
64
+ targetMDNames.constant
65
+ }.${name}[${colName}].ko}</>`;
66
+ case "array-images":
67
+ return `<>{ ${colName}.map(r => <img src={r} />) }</>`;
68
+ case "number-plain":
69
+ return `<>{${
70
+ col.nullable ? `${colName} && ` : ""
71
+ }new Intl.NumberFormat().format(${colName})}</>`;
72
+ case "object":
73
+ try {
74
+ const relProp = getRelationPropFromColName(smdId, col.name);
75
+ smdId = relProp.with;
76
+ names = SMDManager.getNamesFromId(relProp.with);
77
+ return `
78
+ <>{${colName} && <Table celled compact>
79
+ <Table.Body>
80
+ ${col
81
+ .children!.map((child) => {
82
+ return [
83
+ `<Table.Row>`,
84
+ this.wrapTc(child.label, `${child.name}-0`),
85
+ this.wrapTc(
86
+ this.renderColumn(smdId, child, names, colName),
87
+ `${child.name}-1`
88
+ ),
89
+ `</Table.Row>`,
90
+ ].join("\n");
91
+ })
92
+ .join("\n")}
93
+ </Table.Body>
94
+ </Table>}</>`;
95
+ } catch {
96
+ return "<></>";
97
+ }
98
+ case "object-pick":
99
+ const pickedChild = col.children!.find(
100
+ (child) => child.name === col.config?.picked
101
+ );
102
+ if (!pickedChild) {
103
+ throw new Error(`object-pick 선택 실패 (오브젝트: ${col.name})`);
104
+ }
105
+ return this.renderColumn(
106
+ smdId,
107
+ pickedChild,
108
+ names,
109
+ `${colName}${col.nullable ? "?" : ""}`
110
+ );
111
+ case "array":
112
+ const elementTableCell = this.renderColumn(
113
+ smdId,
114
+ col.element!,
115
+ names,
116
+ "elem",
117
+ true
118
+ );
119
+ return `<>{ ${colName} && ${colName}.map((elem, index) => <span key={index} className="ui button mini compact active">${elementTableCell}</span>) }</>`;
120
+ default:
121
+ throw new Error(`렌더 불가 컬럼 ${col.renderType}`);
122
+ }
123
+ }
124
+
125
+ renderColumnImport(
126
+ smdId: string,
127
+ col: RenderingNode,
128
+ names: SMDNamesRecord
129
+ ): (string | null)[] {
130
+ if (col.renderType === "enums") {
131
+ const { modulePath, targetMDNames } = getEnumInfoFromColName(
132
+ names.capital,
133
+ col.name
134
+ );
135
+ return [
136
+ `import { ${targetMDNames.constant} } from 'src/services/${modulePath}';`,
137
+ ];
138
+ } else if (col.renderType === "object") {
139
+ try {
140
+ const relProp = getRelationPropFromColName(smdId, col.name);
141
+ const result = col.children!.map((child) => {
142
+ smdId = relProp.with;
143
+ names = SMDManager.getNamesFromId(relProp.with);
144
+ return this.renderColumnImport(smdId, child, names);
145
+ });
146
+ return flattenDeep(result);
147
+ } catch {
148
+ return [null];
149
+ }
150
+ } else if (col.renderType === "array") {
151
+ return this.renderColumnImport(smdId, col.element!, names);
152
+ }
153
+
154
+ return [null];
155
+ }
156
+
157
+ renderFilterImport(smdId: string, col: RenderingNode, names: SMDNamesRecord) {
158
+ if (col.name === "search") {
159
+ return `import { ${names.capital}SearchInput } from "src/components/${names.fs}/${names.capital}SearchInput";`;
160
+ } else if (col.renderType === "enums") {
161
+ if (col.name === "orderBy") {
162
+ const componentId = `${names.capital}${camelize(col.name)}Select`;
163
+ return `import { ${componentId} } from "src/components/${names.fs}/${componentId}";`;
164
+ } else {
165
+ try {
166
+ const { id, targetMDNames } = getEnumInfoFromColName(smdId, col.name);
167
+ const componentId = `${id}Select`;
168
+ return `import { ${componentId} } from "src/components/${targetMDNames.fs}/${componentId}";`;
169
+ } catch {
170
+ return "";
171
+ }
172
+ }
173
+ } else if (col.renderType === "number-fk_id") {
174
+ try {
175
+ const relProp = getRelationPropFromColName(
176
+ smdId,
177
+ col.name.replace("_id", "")
178
+ );
179
+ const targetNames = SMDManager.getNamesFromId(relProp.with);
180
+ const componentId = `${relProp.with}IdAsyncSelect`;
181
+ return `import { ${componentId} } from "src/components/${targetNames.fs}/${componentId}";`;
182
+ } catch {
183
+ return "";
184
+ }
185
+ } else {
186
+ throw new Error(
187
+ `렌더 불가능한 필터 임포트 ${col.name} ${col.renderType}`
188
+ );
189
+ }
190
+ }
191
+
192
+ renderFilter(smdId: string, col: RenderingNode, names: SMDNamesRecord) {
193
+ if (col.name === "search") {
194
+ return "";
195
+ }
196
+
197
+ const isClearable = col.optional === true && col.name !== "orderBy";
198
+ let componentId: string;
199
+ if (col.renderType === "enums") {
200
+ if (col.name === "orderBy") {
201
+ componentId = `${names.capital}${camelize(col.name)}Select`;
202
+ } else {
203
+ try {
204
+ const { id } = getEnumInfoFromColName(smdId, col.name);
205
+ componentId = `${id}Select`;
206
+ } catch {
207
+ return "";
208
+ }
209
+ }
210
+ return `<${componentId} {...register('${col.name}')} ${
211
+ isClearable ? "clearable" : ""
212
+ } />`;
213
+ } else if (col.renderType === "number-fk_id") {
214
+ try {
215
+ const relProp = getRelationPropFromColName(
216
+ smdId,
217
+ col.name.replace("_id", "")
218
+ );
219
+ componentId = `${relProp.with}IdAsyncSelect`;
220
+ return `<${componentId} {...register('${col.name}')} ${
221
+ isClearable ? "clearable" : ""
222
+ } subset="A" />`;
223
+ } catch {
224
+ return "";
225
+ }
226
+ } else {
227
+ throw new Error(
228
+ `렌더 불가능한 필터 임포트 ${col.name} ${col.renderType}`
229
+ );
230
+ }
231
+ }
232
+
233
+ getDefault(columns: RenderingNode[]): {
234
+ orderBy: string;
235
+ search: string;
236
+ } {
237
+ const def = {
238
+ orderBy: "id-desc",
239
+ search: "title",
240
+ };
241
+ const orderByZodType = columns.find(
242
+ (col) => col.name === "orderBy"
243
+ )?.zodType;
244
+ if (orderByZodType && orderByZodType instanceof z.ZodEnum) {
245
+ def.orderBy = Object.keys(orderByZodType.Enum)[0];
246
+ }
247
+ const searchZodType = columns.find((col) => col.name === "search")?.zodType;
248
+ if (searchZodType && searchZodType instanceof z.ZodEnum) {
249
+ def.search = Object.keys(searchZodType.Enum)[0];
250
+ }
251
+ return def;
252
+ }
253
+
254
+ render(
255
+ { smdId }: TemplateOptions["view_list"],
256
+ columnsNode: RenderingNode,
257
+ listParamsNode: RenderingNode
258
+ ) {
259
+ const names = SMDManager.getNamesFromId(smdId);
260
+
261
+ // 실제 리스트 컬럼
262
+ const columns = (columnsNode.children as RenderingNode[])
263
+ .filter((col) => col.name !== "id")
264
+ .map((col) => {
265
+ return {
266
+ name: col.name,
267
+ label: col.label,
268
+ tc: `(row) => ${this.renderColumn(smdId, col, names)}`,
269
+ };
270
+ });
271
+
272
+ // 필터 컬럼
273
+ const filterColumns = (listParamsNode.children as RenderingNode[])
274
+ .filter(
275
+ (col) =>
276
+ col.name !== "id" &&
277
+ (["enums", "number-id"].includes(col.renderType) ||
278
+ col.name.endsWith("_id"))
279
+ )
280
+ // orderBy가 가장 뒤로 오게 순서 조정
281
+ .sort((a) => {
282
+ return a.name == "orderBy" ? 1 : -1;
283
+ });
284
+
285
+ // 필터 컬럼을 프리 템플릿으로 설정
286
+ const preTemplates: RenderedTemplate["preTemplates"] = [];
287
+ for (let col of filterColumns) {
288
+ let key: TemplateKey;
289
+ let targetMdId = smdId;
290
+ let enumId: string | undefined;
291
+ let idConstant: string | undefined;
292
+
293
+ if (col.renderType === "enums") {
294
+ if (col.name === "search") {
295
+ key = "view_enums_dropdown";
296
+ enumId = `${names.capital}SearchField`;
297
+ targetMdId = names.capital;
298
+ idConstant = "SEARCH_FIELD";
299
+ } else {
300
+ key = "view_enums_select";
301
+ try {
302
+ const { targetMDNames, id, name } = getEnumInfoFromColName(
303
+ smdId,
304
+ col.name
305
+ );
306
+ targetMdId = targetMDNames.capital;
307
+ enumId = id;
308
+ idConstant = name;
309
+ } catch {
310
+ continue;
311
+ }
312
+ }
313
+ } else {
314
+ key = "view_id_async_select";
315
+ try {
316
+ const relProp = getRelationPropFromColName(
317
+ smdId,
318
+ col.name.replace("_id", "")
319
+ );
320
+ targetMdId = relProp.with;
321
+ } catch {
322
+ continue;
323
+ }
324
+ }
325
+
326
+ preTemplates.push({
327
+ key,
328
+ options: {
329
+ smdId: targetMdId,
330
+ enumId,
331
+ idConstant,
332
+ },
333
+ });
334
+ }
335
+
336
+ // 리스트 컬럼 프리템플릿
337
+ const columnImports = uniq(
338
+ columnsNode
339
+ .children!.map((col) => {
340
+ return this.renderColumnImport(smdId, col, names);
341
+ })
342
+ .flat()
343
+ .filter((col) => col !== null)
344
+ ).join("\n");
345
+ preTemplates.push({
346
+ key: "view_list_columns",
347
+ options: {
348
+ smdId,
349
+ columns,
350
+ columnImports,
351
+ } as TemplateOptions["view_list_columns"],
352
+ });
353
+
354
+ // SearchInput
355
+ preTemplates!.push({
356
+ key: "view_search_input",
357
+ options: {
358
+ smdId,
359
+ },
360
+ });
361
+
362
+ // 디폴트 파라미터
363
+ const def = this.getDefault(filterColumns);
364
+
365
+ return {
366
+ ...this.getTargetAndPath(names),
367
+ body: `
368
+ import React from 'react';
369
+ import { Link } from 'react-router-dom';
370
+ import {
371
+ Breadcrumb,
372
+ Checkbox,
373
+ Pagination,
374
+ Segment,
375
+ Table,
376
+ TableRow,
377
+ Message,
378
+ Transition,
379
+ Button,
380
+ Label,
381
+ } from 'semantic-ui-react';
382
+ import classNames from 'classnames';
383
+ import { DateTime } from "luxon";
384
+ import { DelButton } from 'src/typeframe/components/DelButton';
385
+ import { EditButton } from 'src/typeframe/components/EditButton';
386
+ import { AppBreadcrumbs } from 'src/typeframe/components/AppBreadcrumbs';
387
+ import { AddButton } from 'src/typeframe/components/AddButton';
388
+ import { useSelection, useListParams } from 'src/typeframe/helpers';
389
+ import { TFColumn } from "src/typeframe/iso-types";
390
+ import dc from './_columns';
391
+
392
+ import { ${names.capital}SubsetA } from "src/services/${names.fs}/${
393
+ names.fs
394
+ }.generated";
395
+ import { ${names.capital}Service } from 'src/services/${names.fs}/${
396
+ names.fs
397
+ }.service';
398
+ import { ${names.capital}ListParams } from 'src/services/${names.fs}/${
399
+ names.fs
400
+ }.types';
401
+ ${columnImports}
402
+ ${filterColumns
403
+ .map((col) => {
404
+ return this.renderFilterImport(smdId, col, names);
405
+ })
406
+ .join("\n")}
407
+
408
+ type ${names.capital}ListProps = {};
409
+ export default function ${names.capital}List({}: ${names.capital}ListProps) {
410
+ // 리스트 필터
411
+ const { listParams, register } = useListParams(${names.capital}ListParams, {
412
+ num: 12,
413
+ page: 1,
414
+ orderBy: '${def.orderBy}',
415
+ search: '${def.search}',
416
+ });
417
+
418
+ // 리스트 쿼리
419
+ const { data, mutate, error } = ${names.capital}Service.use${
420
+ names.capitalPlural
421
+ }('A', listParams);
422
+ const { rows, total } = data ?? {};
423
+ const isLoading = !error && !data;
424
+
425
+ // 삭제
426
+ const confirmDel = (ids: number[]) => {
427
+ const answer = confirm('삭제하시겠습니까?');
428
+ if (!answer) {
429
+ return;
430
+ }
431
+
432
+ ${names.capital}Service.del(ids).then(() => {
433
+ mutate();
434
+ });
435
+ };
436
+
437
+ // 일괄 삭제
438
+ const confirmDelSelected = () => {
439
+ const answer = confirm(\`\${selectedKeys.length}건을 일괄 삭제하시겠습니까?\`);
440
+ if (!answer) {
441
+ return;
442
+ }
443
+
444
+ ${names.capital}Service.del(selectedKeys).then(() => {
445
+ mutate();
446
+ });
447
+ };
448
+
449
+ // 현재 경로와 타이틀
450
+ const PAGE = {
451
+ route: '/admin/${names.fsPlural}',
452
+ title: '${names.capital}',
453
+ };
454
+
455
+ // 선택
456
+ const {
457
+ getSelected,
458
+ isAllSelected,
459
+ selectedKeys,
460
+ toggle,
461
+ selectAll,
462
+ deselectAll,
463
+ } = useSelection((rows ?? []).map((row) => row.id));
464
+
465
+ // 컬럼
466
+ const columns:TFColumn<${names.capital}SubsetA>[] = [
467
+ ${columns.map((col) => `{ ...dc.${col.name} }`).join(",\n")}
468
+ ]
469
+
470
+ return (
471
+ <div className="list ${names.fsPlural}-index">
472
+ <div className="top-nav">
473
+ <div className="header-row">
474
+ <div className="header">{PAGE.title}</div>
475
+ <AppBreadcrumbs>
476
+ <Breadcrumb.Section active>{PAGE.title}</Breadcrumb.Section>
477
+ </AppBreadcrumbs>
478
+ <${names.capital}SearchInput
479
+ input={register('keyword')}
480
+ dropdown={register('search')}
481
+ />
482
+ </div>
483
+ <div className="filters-row">
484
+ ${filterColumns
485
+ .map((col) => {
486
+ return this.renderFilter(smdId, col, names);
487
+ })
488
+ .join("&nbsp;\n")}
489
+ </div>
490
+ </div>
491
+
492
+ <Segment basic padded className="contents-segment" loading={isLoading}>
493
+ <div className="buttons-row">
494
+ <div className={classNames('count', { hidden: isLoading })}>
495
+ {total} 건
496
+ </div>
497
+ <div className="buttons">
498
+ <AddButton currentRoute={PAGE.route} icon="write" label="추가" />
499
+ </div>
500
+ </div>
501
+
502
+ <Table
503
+ celled
504
+ compact
505
+ selectable
506
+ className={classNames({ hidden: total === undefined || total === 0 })}
507
+ >
508
+ <Table.Header>
509
+ <TableRow>
510
+ <Table.HeaderCell collapsing>
511
+ <Checkbox
512
+ label="ID"
513
+ checked={isAllSelected}
514
+ onChange={isAllSelected ? deselectAll : selectAll}
515
+ />
516
+ </Table.HeaderCell>
517
+ {
518
+ /* Header */
519
+ columns.map((col, index) => col.th ?? <Table.HeaderCell key={index}>{ col.label }</Table.HeaderCell>)
520
+ }
521
+ <Table.HeaderCell>관리</Table.HeaderCell>
522
+ </TableRow>
523
+ </Table.Header>
524
+ <Table.Body>
525
+ {rows &&
526
+ rows.map((row, rowIndex) => (
527
+ <Table.Row key={row.id}>
528
+ <Table.Cell>
529
+ <Checkbox
530
+ label={row.id}
531
+ checked={getSelected(row.id)}
532
+ onChange={() => toggle(row.id)}
533
+ />
534
+ </Table.Cell>
535
+ {
536
+ /* Body */
537
+ columns.map((col, colIndex) => (
538
+ <Table.Cell key={colIndex} collapsing={col.collapsing} className={col.className}>
539
+ {col.tc(row, rowIndex)}
540
+ </Table.Cell>
541
+ ))
542
+ }
543
+ <Table.Cell collapsing>
544
+ <EditButton
545
+ as={Link}
546
+ to={\`\${PAGE.route}/form?id=\${row.id}\`}
547
+ state={{ from: PAGE.route }}
548
+ />
549
+ <DelButton onClick={() => confirmDel([row.id])} />
550
+ </Table.Cell>
551
+ </Table.Row>
552
+ ))}
553
+ </Table.Body>
554
+ </Table>
555
+ <div
556
+ className={classNames('pagination-row', {
557
+ hidden: (total ?? 0) === 0,
558
+ })}
559
+ >
560
+ <Pagination
561
+ totalPages={Math.ceil((total ?? 0) / (listParams.num ?? 24))}
562
+ {...register('page')}
563
+ />
564
+ </div>
565
+ </Segment>
566
+
567
+ <div className="fixed-menu">
568
+ <Transition
569
+ visible={selectedKeys.length > 0}
570
+ animation="slide left"
571
+ duration={500}
572
+ >
573
+ <Message size="small" color="violet" className="text-center">
574
+ <span className="px-4">{selectedKeys.length}개 선택됨</span>
575
+ <Button size="tiny" color="violet" onClick={() => deselectAll()}>
576
+ 선택 해제
577
+ </Button>
578
+ <Button size="tiny" color="red" onClick={confirmDelSelected}>
579
+ 일괄 삭제
580
+ </Button>
581
+ </Message>
582
+ </Transition>
583
+ </div>
584
+ </div>
585
+ );
586
+ }
587
+ `.trim(),
588
+ importKeys: [],
589
+ preTemplates,
590
+ };
591
+ }
592
+ }
593
+
594
+ export function getEnumInfoFromColName(
595
+ smdId: string,
596
+ colName: string
597
+ ): {
598
+ id: string;
599
+ targetMDNames: SMDNamesRecord;
600
+ targetMDId: string;
601
+ modulePath: string;
602
+ name: string;
603
+ } {
604
+ const baseMd = SMDManager.get(smdId);
605
+ const prop = baseMd.props.find((p) => p.name === colName);
606
+ if (prop && isEnumProp(prop)) {
607
+ const modulePath = SMDManager.getModulePath(prop.id);
608
+ const targetMDId = camelize(modulePath.split("/")[0].replace("-", "_"));
609
+ const targetMDNames = SMDManager.getNamesFromId(targetMDId);
610
+ const name = underscore(
611
+ prop.id.replace(targetMDNames.capital, "")
612
+ ).toUpperCase();
613
+ return {
614
+ id: prop.id,
615
+ name,
616
+ targetMDId,
617
+ targetMDNames,
618
+ modulePath,
619
+ };
620
+ } else {
621
+ const idCandidate = camelize(
622
+ underscore(smdId) + "_" + underscore(colName),
623
+ false
624
+ );
625
+ try {
626
+ const modulePath = SMDManager.getModulePath(idCandidate);
627
+ const targetMDNames = SMDManager.getNamesFromId(smdId);
628
+ const name = underscore(colName).toUpperCase();
629
+ return {
630
+ id: idCandidate,
631
+ name,
632
+ targetMDId: smdId,
633
+ targetMDNames,
634
+ modulePath,
635
+ };
636
+ } catch {}
637
+ throw new Error(`찾을 수 없는 EnumProp ${colName}`);
638
+ }
639
+ }
640
+
641
+ export function getRelationPropFromColName(
642
+ smdId: string,
643
+ colName: string
644
+ ): RelationProp {
645
+ const baseMd = SMDManager.get(smdId);
646
+ const relProp = baseMd.props.find((prop) => prop.name === colName);
647
+ if (isRelationProp(relProp)) {
648
+ return relProp;
649
+ } else {
650
+ throw new Error(`찾을 수 없는 Relation ${colName}`);
651
+ }
652
+ }
@@ -0,0 +1,59 @@
1
+ import { TemplateOptions } from "../types/types";
2
+ import { SMDManager, SMDNamesRecord } from "../smd/smd-manager";
3
+ import { Template } from "./base-template";
4
+
5
+ export class Template__view_list_columns extends Template {
6
+ constructor() {
7
+ super("view_list_columns");
8
+ }
9
+
10
+ getTargetAndPath(names: SMDNamesRecord) {
11
+ return {
12
+ target: "web/src/pages/admin",
13
+ path: `${names.fsPlural}/_columns.tsx`,
14
+ };
15
+ }
16
+
17
+ // 컬럼
18
+ render({
19
+ smdId,
20
+ columns,
21
+ columnImports,
22
+ }: TemplateOptions["view_list_columns"]) {
23
+ const names = SMDManager.getNamesFromId(smdId);
24
+
25
+ return {
26
+ ...this.getTargetAndPath(names),
27
+ body: `
28
+ import React from 'react';
29
+ import {
30
+ Segment,
31
+ Table,
32
+ TableRow,
33
+ Button,
34
+ Label,
35
+ } from 'semantic-ui-react';
36
+ import { DateTime } from "luxon";
37
+ import { TFColumn } from "src/typeframe/iso-types";
38
+ import { ${names.capital}SubsetA } from "src/services/${names.fs}/${
39
+ names.fs
40
+ }.generated";
41
+ ${columnImports}
42
+
43
+ const columns: { [key in Exclude<keyof ${
44
+ names.capital
45
+ }SubsetA, 'id'>]: TFColumn<${names.capital}SubsetA> } = {${columns
46
+ .map((col) => {
47
+ return [
48
+ `${col.name}: { label: "${col.label}",`,
49
+ `tc: ${col.tc}, `,
50
+ `collapsing: ${["Title", "Name"].includes(col.label) === false}, }`,
51
+ ].join("\n");
52
+ })
53
+ .join(",\n")}};
54
+ export default columns;
55
+ `.trim(),
56
+ importKeys: [],
57
+ };
58
+ }
59
+ }