dn-react-router-toolkit 0.4.2 → 0.5.0

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 (89) hide show
  1. package/dist/api/create_api_handler.d.mts +28 -0
  2. package/dist/api/create_api_handler.d.ts +28 -0
  3. package/dist/api/create_api_handler.js +148 -0
  4. package/dist/api/create_api_handler.mjs +132 -0
  5. package/dist/api/create_handler.d.mts +1 -1
  6. package/dist/api/create_handler.d.ts +1 -1
  7. package/dist/api/create_handler.js +29 -26
  8. package/dist/api/create_handler.mjs +29 -26
  9. package/dist/api/index.js +29 -26
  10. package/dist/api/index.mjs +29 -26
  11. package/dist/api/item_api_handler.d.mts +18 -0
  12. package/dist/api/item_api_handler.d.ts +18 -0
  13. package/dist/api/item_api_handler.js +55 -0
  14. package/dist/api/item_api_handler.mjs +30 -0
  15. package/dist/auth/index.d.mts +1 -1
  16. package/dist/auth/index.d.ts +1 -1
  17. package/dist/auth/index.js +62 -16
  18. package/dist/auth/index.mjs +60 -15
  19. package/dist/auth/with_auth.d.mts +8 -3
  20. package/dist/auth/with_auth.d.ts +8 -3
  21. package/dist/auth/with_auth.js +62 -16
  22. package/dist/auth/with_auth.mjs +60 -15
  23. package/dist/crud/crud_form.d.mts +13 -0
  24. package/dist/crud/crud_form.d.ts +13 -0
  25. package/dist/crud/crud_form.js +88 -0
  26. package/dist/crud/crud_form.mjs +55 -0
  27. package/dist/crud/crud_form_provider.d.mts +34 -0
  28. package/dist/crud/crud_form_provider.d.ts +34 -0
  29. package/dist/crud/crud_form_provider.js +160 -0
  30. package/dist/crud/crud_form_provider.mjs +124 -0
  31. package/dist/crud/crud_loader.d.mts +21 -0
  32. package/dist/crud/crud_loader.d.ts +21 -0
  33. package/dist/crud/crud_loader.js +288 -0
  34. package/dist/crud/crud_loader.mjs +273 -0
  35. package/dist/crud/crud_page.d.mts +25 -0
  36. package/dist/crud/crud_page.d.ts +25 -0
  37. package/dist/crud/crud_page.js +645 -0
  38. package/dist/crud/crud_page.mjs +616 -0
  39. package/dist/crud/generate_handlers.d.mts +15 -0
  40. package/dist/crud/generate_handlers.d.ts +15 -0
  41. package/dist/crud/generate_handlers.js +39 -0
  42. package/dist/crud/generate_handlers.mjs +14 -0
  43. package/dist/crud/generate_pages.d.mts +11 -0
  44. package/dist/crud/generate_pages.d.ts +11 -0
  45. package/dist/crud/generate_pages.js +52 -0
  46. package/dist/crud/generate_pages.mjs +17 -0
  47. package/dist/crud/generate_routes.d.mts +5 -0
  48. package/dist/crud/generate_routes.d.ts +5 -0
  49. package/dist/crud/generate_routes.js +62 -0
  50. package/dist/crud/generate_routes.mjs +27 -0
  51. package/dist/crud/index.d.mts +21 -0
  52. package/dist/crud/index.d.ts +21 -0
  53. package/dist/crud/index.js +970 -0
  54. package/dist/crud/index.mjs +945 -0
  55. package/dist/db/index.d.mts +4 -0
  56. package/dist/db/index.d.ts +4 -0
  57. package/dist/db/index.js +46 -0
  58. package/dist/db/index.mjs +8 -0
  59. package/dist/index.d.mts +3 -2
  60. package/dist/index.d.ts +3 -2
  61. package/dist/table/buttons.d.mts +10 -0
  62. package/dist/table/buttons.d.ts +10 -0
  63. package/dist/table/buttons.js +102 -0
  64. package/dist/table/buttons.mjs +71 -0
  65. package/dist/table/index.d.mts +9 -0
  66. package/dist/table/index.d.ts +9 -0
  67. package/dist/table/index.js +570 -0
  68. package/dist/table/index.mjs +543 -0
  69. package/dist/table/item_loader.d.mts +12 -0
  70. package/dist/table/item_loader.d.ts +12 -0
  71. package/dist/table/item_loader.js +51 -0
  72. package/dist/table/item_loader.mjs +26 -0
  73. package/dist/table/loader.d.mts +28 -0
  74. package/dist/table/loader.d.ts +28 -0
  75. package/dist/table/loader.js +70 -0
  76. package/dist/table/loader.mjs +48 -0
  77. package/dist/table/page.d.mts +27 -0
  78. package/dist/table/page.d.ts +27 -0
  79. package/dist/table/page.js +444 -0
  80. package/dist/table/page.mjs +415 -0
  81. package/dist/table/repository.d.mts +38 -0
  82. package/dist/table/repository.d.ts +38 -0
  83. package/dist/table/repository.js +76 -0
  84. package/dist/table/repository.mjs +56 -0
  85. package/dist/table/table.d.mts +27 -0
  86. package/dist/table/table.d.ts +27 -0
  87. package/dist/table/table.js +290 -0
  88. package/dist/table/table.mjs +255 -0
  89. package/package.json +78 -59
@@ -0,0 +1,945 @@
1
+ // src/crud/crud_form_provider.tsx
2
+ import { useNavigate } from "react-router";
3
+ import { useStore } from "dn-react-toolkit/store";
4
+ import { createContext, useContext } from "react";
5
+ import React from "react";
6
+ var FormContext = createContext({});
7
+ function useFormContext() {
8
+ return useContext(FormContext);
9
+ }
10
+ function CrudFormProvider({
11
+ primaryKey = "id",
12
+ name,
13
+ prefix: prefix2,
14
+ item,
15
+ columns = {},
16
+ children
17
+ }) {
18
+ const apiPrefix = `/api${prefix2}`;
19
+ const store = useStore({
20
+ ...item || {}
21
+ });
22
+ const navigate = useNavigate();
23
+ const submit = async () => {
24
+ const res = await fetch(apiPrefix, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json"
28
+ },
29
+ body: JSON.stringify(
30
+ Object.entries(store.state).reduce(
31
+ function reducer(acc, [key, value]) {
32
+ const converter = (value2) => {
33
+ if (value2 === void 0) {
34
+ return void 0;
35
+ }
36
+ if (value2 === null) {
37
+ return {
38
+ type: "null",
39
+ value: null
40
+ };
41
+ }
42
+ if (typeof value2 === "string") {
43
+ return {
44
+ type: "string",
45
+ value: value2
46
+ };
47
+ }
48
+ if (typeof value2 === "number") {
49
+ return {
50
+ type: "number",
51
+ value: value2
52
+ };
53
+ }
54
+ if (typeof value2 === "boolean") {
55
+ return {
56
+ type: "boolean",
57
+ value: value2
58
+ };
59
+ }
60
+ if (value2 instanceof Date) {
61
+ return {
62
+ type: "date",
63
+ value: value2.toISOString()
64
+ };
65
+ }
66
+ if (Array.isArray(value2)) {
67
+ return value2.map((v) => converter(v));
68
+ }
69
+ if (typeof value2 === "object") {
70
+ return Object.entries(
71
+ value2
72
+ ).reduce(reducer, {});
73
+ }
74
+ };
75
+ return {
76
+ ...acc,
77
+ [key]: converter(value)
78
+ };
79
+ },
80
+ {}
81
+ )
82
+ )
83
+ });
84
+ if (!res.ok) {
85
+ const { message } = await res.json();
86
+ alert(message);
87
+ return;
88
+ }
89
+ alert(`${name}\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`);
90
+ const { id } = await res.json();
91
+ navigate(`${prefix2}/${id}`);
92
+ };
93
+ const deleteItem = async () => {
94
+ if (!item || !primaryKey) {
95
+ return;
96
+ }
97
+ const ok = confirm("\uC815\uB9D0\uB85C \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?");
98
+ if (!ok) {
99
+ return;
100
+ }
101
+ const res = await fetch(`${apiPrefix}/${item[primaryKey]}`, {
102
+ method: "DELETE"
103
+ });
104
+ if (!res.ok) {
105
+ const { message } = await res.json();
106
+ alert(message);
107
+ return;
108
+ }
109
+ alert(`${name}\uB97C \uC0AD\uC81C\uD588\uC2B5\uB2C8\uB2E4.`);
110
+ navigate(`${prefix2}`);
111
+ };
112
+ return /* @__PURE__ */ React.createElement(
113
+ FormContext.Provider,
114
+ {
115
+ value: { name, item, store, submit, delete: deleteItem, columns }
116
+ },
117
+ children
118
+ );
119
+ }
120
+
121
+ // src/crud/crud_form.tsx
122
+ import { SyncInput } from "dn-react-toolkit/store";
123
+ import React2 from "react";
124
+ function CrudForm({ AdminLayout }) {
125
+ const form = useFormContext();
126
+ return /* @__PURE__ */ React2.createElement(
127
+ AdminLayout,
128
+ {
129
+ title: `${form.name} ${form.item ? "\uC218\uC815" : "\uCD94\uAC00"}`,
130
+ actions: /* @__PURE__ */ React2.createElement(
131
+ "button",
132
+ {
133
+ type: "button",
134
+ className: "button-primary",
135
+ onClick: form.submit
136
+ },
137
+ "\uC800\uC7A5\uD558\uAE30"
138
+ ),
139
+ className: "max-w-3xl mx-auto"
140
+ },
141
+ Object.keys(form.columns).length > 0 && /* @__PURE__ */ React2.createElement(React2.Fragment, null, Object.entries(form.columns).map(
142
+ ([key, value]) => /* @__PURE__ */ React2.createElement("label", { key }, value.label, value.component ? /* @__PURE__ */ React2.createElement(value.component, null) : /* @__PURE__ */ React2.createElement(
143
+ SyncInput,
144
+ {
145
+ store: form.store,
146
+ property: key,
147
+ className: "input-form"
148
+ }
149
+ ))
150
+ )),
151
+ form.item && /* @__PURE__ */ React2.createElement(
152
+ "button",
153
+ {
154
+ className: "button-dangerous mt-8",
155
+ onClick: () => {
156
+ form.delete();
157
+ }
158
+ },
159
+ "\uC0AD\uC81C\uD558\uAE30"
160
+ )
161
+ );
162
+ }
163
+
164
+ // src/table/loader.tsx
165
+ import {
166
+ and,
167
+ ilike
168
+ } from "drizzle-orm";
169
+ function tableLoader({
170
+ repository,
171
+ tableOptions
172
+ }) {
173
+ return async ({ request }) => {
174
+ const searchParams = new URL(request.url).searchParams;
175
+ const { where, searchKey, defaultOrderBy, defaultDirection } = tableOptions;
176
+ const query = searchParams.get("query") ?? void 0;
177
+ const limit = Number(searchParams.get("limit") ?? "10");
178
+ const offset = Number(searchParams.get("offset") ?? "0");
179
+ const orderBy = searchParams.get("orderBy") ?? defaultOrderBy;
180
+ const direction = searchParams.get("direction") ?? defaultDirection;
181
+ const whereClauses = and(
182
+ searchKey && query ? ilike(
183
+ repository.schema[searchKey],
184
+ `%${query}%`
185
+ ) : void 0,
186
+ ...where ?? []
187
+ );
188
+ const total = await repository.countTotal({ where: whereClauses });
189
+ const items = await repository.findAll({
190
+ orderBy,
191
+ direction,
192
+ limit,
193
+ offset,
194
+ where: whereClauses
195
+ });
196
+ return {
197
+ table: {
198
+ items,
199
+ total,
200
+ limit,
201
+ offset,
202
+ orderBy,
203
+ direction,
204
+ searchKey
205
+ }
206
+ };
207
+ };
208
+ }
209
+
210
+ // src/table/item_loader.tsx
211
+ var tableItemloader = ({
212
+ loader,
213
+ repository
214
+ }) => {
215
+ return async ({ params }) => {
216
+ const body = loader ? await (async () => {
217
+ const result = await loader({ params });
218
+ if (result instanceof Response) {
219
+ return result.json();
220
+ }
221
+ return result;
222
+ })() : {};
223
+ if (params["itemId"] === "new") {
224
+ return { item: void 0, ...body };
225
+ }
226
+ const item = params["itemId"] ? await repository.find(params["itemId"]) : void 0;
227
+ return {
228
+ item,
229
+ ...body
230
+ };
231
+ };
232
+ };
233
+
234
+ // src/api/create_api_handler.ts
235
+ import {
236
+ BAD_REQUEST,
237
+ CONFLICT,
238
+ CREATED,
239
+ METHOD_NOT_ALLOWED
240
+ } from "dn-react-toolkit/http";
241
+ import {
242
+ and as and2
243
+ } from "drizzle-orm";
244
+ import {
245
+ redirect
246
+ } from "react-router";
247
+ import { v4 } from "uuid";
248
+ function apiHandler({
249
+ withAuthAction,
250
+ repository,
251
+ validators,
252
+ existingConditions,
253
+ injectUserId
254
+ }) {
255
+ const loader = async ({ request }) => {
256
+ return {};
257
+ };
258
+ const action = withAuthAction((auth) => async ({ request }) => {
259
+ if (!auth || auth.role !== "admin") {
260
+ return redirect("/login");
261
+ }
262
+ switch (request.method) {
263
+ case "POST":
264
+ case "PUT": {
265
+ const serilaizedParams = await request.json();
266
+ const params = Object.entries(serilaizedParams).reduce(
267
+ function reducer(acc, [key, value]) {
268
+ const converter = (value2) => {
269
+ if (value2.type === "null") {
270
+ return null;
271
+ }
272
+ if (value2.type === "string") {
273
+ return value2.value;
274
+ }
275
+ if (value2.type === "number") {
276
+ return value2.value;
277
+ }
278
+ if (value2.type === "boolean") {
279
+ return value2.value;
280
+ }
281
+ if (value2.type === "date") {
282
+ return new Date(value2.value);
283
+ }
284
+ if (Array.isArray(value2)) {
285
+ return value2.map((v) => converter(v));
286
+ }
287
+ if (typeof value2 === "object") {
288
+ return Object.entries(value2).reduce(
289
+ reducer,
290
+ {}
291
+ );
292
+ }
293
+ };
294
+ const result = converter(value);
295
+ if (result === void 0) {
296
+ return acc;
297
+ }
298
+ return {
299
+ ...acc,
300
+ [key]: result
301
+ };
302
+ },
303
+ {}
304
+ );
305
+ if (validators) {
306
+ const paramsForValidation = Object.keys(validators).filter(
307
+ (key) => Object.prototype.hasOwnProperty.call(
308
+ validators,
309
+ key
310
+ )
311
+ );
312
+ for (const paramKey of paramsForValidation) {
313
+ const value = params[paramKey];
314
+ const validator = validators[paramKey];
315
+ if (validator?.validate && !validator.validate(value)) {
316
+ throw BAD_REQUEST(
317
+ validator.message ? validator.message(value) : void 0
318
+ );
319
+ }
320
+ }
321
+ }
322
+ const itemId = params.id || v4();
323
+ if (!params.id && existingConditions) {
324
+ const paramsForExistenceCheck = Object.keys(
325
+ existingConditions
326
+ ).filter(
327
+ (key) => Object.prototype.hasOwnProperty.call(params, key)
328
+ );
329
+ const where = and2(
330
+ ...paramsForExistenceCheck.reduce((acc, key) => {
331
+ const condition = existingConditions[key];
332
+ if (condition) {
333
+ acc.push(condition(params[key]));
334
+ }
335
+ return acc;
336
+ }, [])
337
+ );
338
+ const existing = await repository.findAll({
339
+ limit: 1,
340
+ where
341
+ });
342
+ if (existing.length > 0) {
343
+ throw CONFLICT("\uC790\uB8CC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4.");
344
+ }
345
+ }
346
+ const values = {
347
+ id: itemId,
348
+ userId: injectUserId ? auth.userId : void 0,
349
+ ...params
350
+ };
351
+ const item = await repository.save(values);
352
+ return CREATED(item);
353
+ }
354
+ default:
355
+ throw METHOD_NOT_ALLOWED();
356
+ }
357
+ });
358
+ return {
359
+ loader,
360
+ action
361
+ };
362
+ }
363
+
364
+ // src/api/item_api_handler.ts
365
+ import { UNAUTHORIZED } from "dn-react-toolkit/http";
366
+ import "react-router";
367
+ function itemApiHandler({
368
+ withAuthAction,
369
+ repository
370
+ }) {
371
+ const loader = async ({ request }) => {
372
+ return {};
373
+ };
374
+ const action = withAuthAction((auth) => async ({ params, request }) => {
375
+ if (!auth || auth.role !== "admin") {
376
+ return UNAUTHORIZED();
377
+ }
378
+ switch (request.method) {
379
+ case "DELETE": {
380
+ const itemId = params.itemId;
381
+ await repository.delete(itemId);
382
+ return {};
383
+ }
384
+ }
385
+ });
386
+ return {
387
+ loader,
388
+ action
389
+ };
390
+ }
391
+
392
+ // src/crud/crud_loader.tsx
393
+ function crudHandler({
394
+ repository,
395
+ apiHandlerOptions,
396
+ loaderOptions,
397
+ itemLoaderOptions
398
+ }) {
399
+ return (prefix2) => async (args) => {
400
+ const pattern = args.unstable_pattern;
401
+ if (pattern === `/api${prefix2}`) {
402
+ const { loader, action } = apiHandler({
403
+ repository,
404
+ ...apiHandlerOptions
405
+ });
406
+ if (args.request.method === "GET") {
407
+ return loader(args);
408
+ } else {
409
+ return action(args);
410
+ }
411
+ }
412
+ if (pattern.startsWith(`/api${prefix2}`)) {
413
+ const { loader, action } = itemApiHandler({
414
+ repository,
415
+ ...apiHandlerOptions
416
+ });
417
+ if (args.request.method === "GET") {
418
+ return loader(args);
419
+ } else {
420
+ return action(args);
421
+ }
422
+ }
423
+ if (pattern === prefix2) {
424
+ return tableLoader({
425
+ ...loaderOptions,
426
+ repository
427
+ })(args);
428
+ }
429
+ if (pattern.startsWith(prefix2)) {
430
+ return tableItemloader({ ...itemLoaderOptions, repository })(args);
431
+ }
432
+ };
433
+ }
434
+
435
+ // src/crud/crud_page.tsx
436
+ import { useLoaderData as useLoaderData2, useLocation as useLocation3 } from "react-router";
437
+
438
+ // src/table/page.tsx
439
+ import {
440
+ Link as Link3,
441
+ useLoaderData,
442
+ useLocation as useLocation2,
443
+ useNavigate as useNavigate2,
444
+ useSearchParams as useSearchParams3
445
+ } from "react-router";
446
+
447
+ // node_modules/react-icons/lib/iconBase.mjs
448
+ import React4 from "react";
449
+
450
+ // node_modules/react-icons/lib/iconContext.mjs
451
+ import React3 from "react";
452
+ var DefaultContext = {
453
+ color: void 0,
454
+ size: void 0,
455
+ className: void 0,
456
+ style: void 0,
457
+ attr: void 0
458
+ };
459
+ var IconContext = React3.createContext && /* @__PURE__ */ React3.createContext(DefaultContext);
460
+
461
+ // node_modules/react-icons/lib/iconBase.mjs
462
+ var _excluded = ["attr", "size", "title"];
463
+ function _objectWithoutProperties(source, excluded) {
464
+ if (source == null) return {};
465
+ var target = _objectWithoutPropertiesLoose(source, excluded);
466
+ var key, i;
467
+ if (Object.getOwnPropertySymbols) {
468
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
469
+ for (i = 0; i < sourceSymbolKeys.length; i++) {
470
+ key = sourceSymbolKeys[i];
471
+ if (excluded.indexOf(key) >= 0) continue;
472
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
473
+ target[key] = source[key];
474
+ }
475
+ }
476
+ return target;
477
+ }
478
+ function _objectWithoutPropertiesLoose(source, excluded) {
479
+ if (source == null) return {};
480
+ var target = {};
481
+ for (var key in source) {
482
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
483
+ if (excluded.indexOf(key) >= 0) continue;
484
+ target[key] = source[key];
485
+ }
486
+ }
487
+ return target;
488
+ }
489
+ function _extends() {
490
+ _extends = Object.assign ? Object.assign.bind() : function(target) {
491
+ for (var i = 1; i < arguments.length; i++) {
492
+ var source = arguments[i];
493
+ for (var key in source) {
494
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
495
+ target[key] = source[key];
496
+ }
497
+ }
498
+ }
499
+ return target;
500
+ };
501
+ return _extends.apply(this, arguments);
502
+ }
503
+ function ownKeys(e, r) {
504
+ var t = Object.keys(e);
505
+ if (Object.getOwnPropertySymbols) {
506
+ var o = Object.getOwnPropertySymbols(e);
507
+ r && (o = o.filter(function(r2) {
508
+ return Object.getOwnPropertyDescriptor(e, r2).enumerable;
509
+ })), t.push.apply(t, o);
510
+ }
511
+ return t;
512
+ }
513
+ function _objectSpread(e) {
514
+ for (var r = 1; r < arguments.length; r++) {
515
+ var t = null != arguments[r] ? arguments[r] : {};
516
+ r % 2 ? ownKeys(Object(t), true).forEach(function(r2) {
517
+ _defineProperty(e, r2, t[r2]);
518
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r2) {
519
+ Object.defineProperty(e, r2, Object.getOwnPropertyDescriptor(t, r2));
520
+ });
521
+ }
522
+ return e;
523
+ }
524
+ function _defineProperty(obj, key, value) {
525
+ key = _toPropertyKey(key);
526
+ if (key in obj) {
527
+ Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true });
528
+ } else {
529
+ obj[key] = value;
530
+ }
531
+ return obj;
532
+ }
533
+ function _toPropertyKey(t) {
534
+ var i = _toPrimitive(t, "string");
535
+ return "symbol" == typeof i ? i : i + "";
536
+ }
537
+ function _toPrimitive(t, r) {
538
+ if ("object" != typeof t || !t) return t;
539
+ var e = t[Symbol.toPrimitive];
540
+ if (void 0 !== e) {
541
+ var i = e.call(t, r || "default");
542
+ if ("object" != typeof i) return i;
543
+ throw new TypeError("@@toPrimitive must return a primitive value.");
544
+ }
545
+ return ("string" === r ? String : Number)(t);
546
+ }
547
+ function Tree2Element(tree) {
548
+ return tree && tree.map((node, i) => /* @__PURE__ */ React4.createElement(node.tag, _objectSpread({
549
+ key: i
550
+ }, node.attr), Tree2Element(node.child)));
551
+ }
552
+ function GenIcon(data) {
553
+ return (props) => /* @__PURE__ */ React4.createElement(IconBase, _extends({
554
+ attr: _objectSpread({}, data.attr)
555
+ }, props), Tree2Element(data.child));
556
+ }
557
+ function IconBase(props) {
558
+ var elem = (conf) => {
559
+ var {
560
+ attr,
561
+ size,
562
+ title
563
+ } = props, svgProps = _objectWithoutProperties(props, _excluded);
564
+ var computedSize = size || conf.size || "1em";
565
+ var className;
566
+ if (conf.className) className = conf.className;
567
+ if (props.className) className = (className ? className + " " : "") + props.className;
568
+ return /* @__PURE__ */ React4.createElement("svg", _extends({
569
+ stroke: "currentColor",
570
+ fill: "currentColor",
571
+ strokeWidth: "0"
572
+ }, conf.attr, attr, svgProps, {
573
+ className,
574
+ style: _objectSpread(_objectSpread({
575
+ color: props.color || conf.color
576
+ }, conf.style), props.style),
577
+ height: computedSize,
578
+ width: computedSize,
579
+ xmlns: "http://www.w3.org/2000/svg"
580
+ }), title && /* @__PURE__ */ React4.createElement("title", null, title), props.children);
581
+ };
582
+ return IconContext !== void 0 ? /* @__PURE__ */ React4.createElement(IconContext.Consumer, null, (conf) => elem(conf)) : elem(DefaultContext);
583
+ }
584
+
585
+ // node_modules/react-icons/go/index.mjs
586
+ function GoArrowDown(props) {
587
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "d": "M4.97 13.22a.75.75 0 0 1 1.06 0L11 18.19V3.75a.75.75 0 0 1 1.5 0v14.44l4.97-4.97a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734l-6.25 6.25a.75.75 0 0 1-1.06 0l-6.25-6.25a.75.75 0 0 1 0-1.06Z" }, "child": [] }] })(props);
588
+ }
589
+ function GoArrowUp(props) {
590
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "d": "M18.655 10.405a.75.75 0 0 1-1.06 0l-4.97-4.97v14.44a.75.75 0 0 1-1.5 0V5.435l-4.97 4.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l6.25-6.25a.75.75 0 0 1 1.06 0l6.25 6.25a.75.75 0 0 1 0 1.06Z" }, "child": [] }] })(props);
591
+ }
592
+ function GoSearch(props) {
593
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "d": "M10.25 2a8.25 8.25 0 0 1 6.34 13.53l5.69 5.69a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-5.69-5.69A8.25 8.25 0 1 1 10.25 2ZM3.5 10.25a6.75 6.75 0 1 0 13.5 0 6.75 6.75 0 0 0-13.5 0Z" }, "child": [] }] })(props);
594
+ }
595
+
596
+ // src/table/buttons.tsx
597
+ import { cn } from "dn-react-toolkit/utils";
598
+ import { Link, useLocation, useSearchParams } from "react-router";
599
+ import React5 from "react";
600
+ function TablePageButtons({
601
+ MAX_PAGES_TO_SHOW,
602
+ total,
603
+ limit,
604
+ offset
605
+ }) {
606
+ const pages = Math.ceil(total / limit);
607
+ const { pathname } = useLocation();
608
+ const [searchParams] = useSearchParams();
609
+ const currentPage = Math.floor(offset / limit) + 1;
610
+ const startButton = (Math.ceil(currentPage / MAX_PAGES_TO_SHOW) - 1) * MAX_PAGES_TO_SHOW;
611
+ const endButton = Math.min(startButton + MAX_PAGES_TO_SHOW - 1, pages);
612
+ return /* @__PURE__ */ React5.createElement(React5.Fragment, null, pages > 1 && /* @__PURE__ */ React5.createElement("div", { className: "flex justify-center items-center my-8 gap-4 text-neutral-400" }, startButton > 1 && /* @__PURE__ */ React5.createElement(
613
+ Link,
614
+ {
615
+ to: (() => {
616
+ searchParams.set(
617
+ "offset",
618
+ String((startButton - 1) * limit)
619
+ );
620
+ return `${pathname}?${searchParams.toString()}`;
621
+ })(),
622
+ className: "w-10 block text-center transition-colors hover:text-primary"
623
+ },
624
+ "\uC774\uC804"
625
+ ), Array.from({
626
+ length: Math.min(
627
+ MAX_PAGES_TO_SHOW,
628
+ pages - startButton
629
+ )
630
+ }).map((_, index2) => {
631
+ return /* @__PURE__ */ React5.createElement(
632
+ Link,
633
+ {
634
+ key: index2,
635
+ to: (() => {
636
+ searchParams.set(
637
+ "offset",
638
+ String((startButton + index2) * limit)
639
+ );
640
+ return `${pathname}?${searchParams.toString()}`;
641
+ })(),
642
+ className: cn(
643
+ "w-6 block text-center transition-colors",
644
+ currentPage === startButton + index2 + 1 ? "font-bold text-primary" : "hover:text-primary"
645
+ )
646
+ },
647
+ startButton + index2 + 1
648
+ );
649
+ }), endButton < pages && /* @__PURE__ */ React5.createElement(
650
+ Link,
651
+ {
652
+ to: (() => {
653
+ searchParams.set(
654
+ "offset",
655
+ String((endButton + 1) * limit)
656
+ );
657
+ return `${pathname}?${searchParams.toString()}`;
658
+ })(),
659
+ className: "w-10 block text-center transition-colors hover:text-primary"
660
+ },
661
+ "\uB2E4\uC74C"
662
+ )));
663
+ }
664
+
665
+ // src/table/table.tsx
666
+ import { cn as cn2 } from "dn-react-toolkit/utils";
667
+ import { Link as Link2, useSearchParams as useSearchParams2 } from "react-router";
668
+ import React6 from "react";
669
+ function Table({
670
+ className = "min-w-full whitespace-nowrap",
671
+ data,
672
+ columns,
673
+ mapper: Mapper,
674
+ getLink,
675
+ limit,
676
+ offset,
677
+ orderBy,
678
+ direction
679
+ }) {
680
+ const keys = Object.entries(columns).filter((entry) => entry[1]).map(([key]) => key);
681
+ const sortedArray = [...data];
682
+ const [_, setSearchParams] = useSearchParams2();
683
+ return /* @__PURE__ */ React6.createElement(
684
+ "table",
685
+ {
686
+ className: cn2(
687
+ className,
688
+ "text-[15px] border-separate border-spacing-0"
689
+ )
690
+ },
691
+ /* @__PURE__ */ React6.createElement("thead", null, /* @__PURE__ */ React6.createElement("tr", null, keys.map((key) => {
692
+ const value = columns[key];
693
+ function getReactNode() {
694
+ if (value && typeof value === "object" && "label" in value) {
695
+ return value.label;
696
+ }
697
+ return value;
698
+ }
699
+ function Head() {
700
+ const reactNode = getReactNode();
701
+ if (typeof reactNode === "string") {
702
+ return /* @__PURE__ */ React6.createElement(
703
+ "button",
704
+ {
705
+ className: cn2(
706
+ orderBy === key ? "text-neutral-900 font-medium" : "text-neutral-500",
707
+ "px-4 h-14 flex items-center w-full"
708
+ ),
709
+ onClick: () => {
710
+ let newDirection = "asc";
711
+ if (orderBy === key) {
712
+ newDirection = direction === "asc" ? "desc" : "asc";
713
+ }
714
+ setSearchParams({
715
+ orderBy: key,
716
+ direction: newDirection
717
+ });
718
+ }
719
+ },
720
+ reactNode,
721
+ orderBy === key && /* @__PURE__ */ React6.createElement("div", { className: "ml-0.5" }, direction === "asc" ? /* @__PURE__ */ React6.createElement(GoArrowUp, null) : /* @__PURE__ */ React6.createElement(GoArrowDown, null))
722
+ );
723
+ }
724
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, reactNode);
725
+ }
726
+ return /* @__PURE__ */ React6.createElement(
727
+ "th",
728
+ {
729
+ key,
730
+ className: cn2("border-y font-normal")
731
+ },
732
+ /* @__PURE__ */ React6.createElement(Head, null)
733
+ );
734
+ }))),
735
+ /* @__PURE__ */ React6.createElement("tbody", null, sortedArray.length === 0 && /* @__PURE__ */ React6.createElement("tr", null, /* @__PURE__ */ React6.createElement(
736
+ "td",
737
+ {
738
+ colSpan: keys.length,
739
+ className: "px-4 h-14 text-neutral-400 text-center"
740
+ },
741
+ "\uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."
742
+ )), sortedArray.map((item, i) => /* @__PURE__ */ React6.createElement("tr", { key: i, className: "hover:bg-gray-50 transition-colors" }, keys.map((key, i2) => {
743
+ const value = item[key];
744
+ function Content() {
745
+ if (key in columns) {
746
+ const column = columns[key];
747
+ if (column && typeof column === "object" && "mapper" in column) {
748
+ const mapper = column.mapper;
749
+ if (mapper) {
750
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, mapper(item));
751
+ }
752
+ }
753
+ }
754
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, String(value));
755
+ }
756
+ const linkedContent = getLink ? /* @__PURE__ */ React6.createElement(
757
+ Link2,
758
+ {
759
+ to: getLink(item),
760
+ className: "block content-center px-4 w-full h-full"
761
+ },
762
+ /* @__PURE__ */ React6.createElement(Content, null)
763
+ ) : /* @__PURE__ */ React6.createElement(Content, null);
764
+ const cell = Mapper ? /* @__PURE__ */ React6.createElement(Mapper, { item, index: i2 }, linkedContent) : linkedContent;
765
+ return /* @__PURE__ */ React6.createElement("td", { key, className: "px-0 h-14 border-b" }, cell);
766
+ }))))
767
+ );
768
+ }
769
+
770
+ // src/table/page.tsx
771
+ import React7 from "react";
772
+ function createTablePage({
773
+ name,
774
+ columns,
775
+ primaryKey = "id"
776
+ }) {
777
+ return function TablePage({
778
+ AdminLayout
779
+ }) {
780
+ const { pathname } = useLocation2();
781
+ const { table } = useLoaderData();
782
+ const { items, total, limit, offset, orderBy, direction, searchKey } = table;
783
+ const navigate = useNavigate2();
784
+ const search = (query) => {
785
+ const searchParams2 = new URLSearchParams(window.location.search);
786
+ searchParams2.set("query", query);
787
+ searchParams2.set("offset", "0");
788
+ navigate(`${pathname}?${searchParams2.toString()}`);
789
+ };
790
+ const [searchParams] = useSearchParams3();
791
+ return /* @__PURE__ */ React7.createElement(
792
+ AdminLayout,
793
+ {
794
+ title: name,
795
+ actions: /* @__PURE__ */ React7.createElement(Link3, { to: `${pathname}/new`, className: "button-primary" }, name, " \uCD94\uAC00")
796
+ },
797
+ searchKey && /* @__PURE__ */ React7.createElement(
798
+ "form",
799
+ {
800
+ className: "h-18 px-4 flex items-center border-t",
801
+ onSubmit: (e) => {
802
+ e.preventDefault();
803
+ const formData = new FormData(e.currentTarget);
804
+ const query = formData.get("query");
805
+ search(query);
806
+ }
807
+ },
808
+ /* @__PURE__ */ React7.createElement(
809
+ "button",
810
+ {
811
+ type: "submit",
812
+ className: "w-10 h-10 flex justify-center items-center"
813
+ },
814
+ /* @__PURE__ */ React7.createElement(GoSearch, { className: "text-xl mr-4" })
815
+ ),
816
+ /* @__PURE__ */ React7.createElement(
817
+ "input",
818
+ {
819
+ className: "outline-none h-full flex-1",
820
+ placeholder: "\uC5EC\uAE30\uC5D0 \uAC80\uC0C9\uD558\uC138\uC694...",
821
+ name: "query",
822
+ defaultValue: searchParams.get("query") ?? ""
823
+ }
824
+ )
825
+ ),
826
+ /* @__PURE__ */ React7.createElement(
827
+ Table,
828
+ {
829
+ data: items,
830
+ columns,
831
+ getLink: primaryKey ? (item) => `${pathname}/${item[primaryKey]}` : void 0,
832
+ limit,
833
+ offset,
834
+ orderBy,
835
+ direction
836
+ }
837
+ ),
838
+ /* @__PURE__ */ React7.createElement(
839
+ TablePageButtons,
840
+ {
841
+ total,
842
+ limit,
843
+ offset,
844
+ MAX_PAGES_TO_SHOW: 10
845
+ }
846
+ )
847
+ );
848
+ };
849
+ }
850
+
851
+ // src/crud/crud_page.tsx
852
+ import React8 from "react";
853
+ function crudPage({
854
+ name,
855
+ tablePageOptions,
856
+ formOptions,
857
+ AdminLayout
858
+ }) {
859
+ return (prefix2) => {
860
+ return function Page() {
861
+ const data = useLoaderData2();
862
+ const { pathname } = useLocation3();
863
+ if (pathname === prefix2) {
864
+ const Component = createTablePage({
865
+ ...tablePageOptions,
866
+ name
867
+ });
868
+ return /* @__PURE__ */ React8.createElement(Component, { AdminLayout });
869
+ }
870
+ if (pathname.startsWith(prefix2)) {
871
+ return /* @__PURE__ */ React8.createElement(
872
+ CrudFormProvider,
873
+ {
874
+ item: data?.item,
875
+ prefix: prefix2,
876
+ name,
877
+ columns: formOptions.columns
878
+ },
879
+ formOptions.form ? /* @__PURE__ */ React8.createElement(formOptions.form, null) : /* @__PURE__ */ React8.createElement(CrudForm, { AdminLayout })
880
+ );
881
+ }
882
+ };
883
+ };
884
+ }
885
+
886
+ // src/crud/generate_handlers.ts
887
+ var generateHandlers = (handlers) => {
888
+ return async (args) => {
889
+ const pattern = args.unstable_pattern;
890
+ for (const route2 of Object.keys(handlers)) {
891
+ if (pattern.startsWith(route2) || pattern.startsWith(`/api${route2}`)) {
892
+ return handlers[route2](route2)(args);
893
+ }
894
+ }
895
+ };
896
+ };
897
+
898
+ // src/crud/generate_pages.tsx
899
+ import { useLocation as useLocation4 } from "react-router";
900
+ import React9 from "react";
901
+ var generatePages = (pages) => {
902
+ return function Page() {
903
+ const { pathname } = useLocation4();
904
+ for (const route2 of Object.keys(pages)) {
905
+ if (pathname.startsWith(route2)) {
906
+ const Page2 = pages[route2](route2);
907
+ return /* @__PURE__ */ React9.createElement(Page2, null);
908
+ }
909
+ }
910
+ };
911
+ };
912
+
913
+ // src/crud/generate_routes.tsx
914
+ import * as Routes from "@react-router/dev/routes";
915
+ function generateCrudRoutes(routes, apiFile, file) {
916
+ return routes.flatMap((prefix2) => {
917
+ return [
918
+ ...Routes.prefix(`/api${prefix2}`, [
919
+ Routes.index(apiFile, {
920
+ id: `api_${prefix2}_list`
921
+ }),
922
+ Routes.route(`:itemId`, apiFile, {
923
+ id: `api_${prefix2}_item`
924
+ })
925
+ ]),
926
+ ...Routes.prefix(prefix2, [
927
+ Routes.index(file, {
928
+ id: `${prefix2}_list`
929
+ }),
930
+ Routes.route(`:itemId`, file, {
931
+ id: `${prefix2}_item`
932
+ })
933
+ ])
934
+ ];
935
+ });
936
+ }
937
+ export {
938
+ FormContext,
939
+ crudHandler,
940
+ crudPage,
941
+ generateCrudRoutes,
942
+ generateHandlers,
943
+ generatePages,
944
+ useFormContext
945
+ };