appflare 0.2.30 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Documentation.md +758 -758
- package/cli/commands/index.ts +238 -238
- package/cli/generate.ts +243 -178
- package/cli/index.ts +120 -120
- package/cli/load-config.ts +184 -184
- package/cli/schema-compiler.ts +1183 -1183
- package/cli/templates/auth/README.md +156 -156
- package/cli/templates/auth/config.ts +61 -61
- package/cli/templates/auth/route-config.ts +1 -1
- package/cli/templates/auth/route-handler.ts +1 -1
- package/cli/templates/auth/route-request-utils.ts +5 -5
- package/cli/templates/auth/route.config.ts +18 -18
- package/cli/templates/auth/route.handler.ts +18 -18
- package/cli/templates/auth/route.request-utils.ts +55 -55
- package/cli/templates/auth/route.ts +14 -14
- package/cli/templates/core/README.md +266 -266
- package/cli/templates/core/app-creation.ts +19 -19
- package/cli/templates/core/client/appflare.ts +112 -112
- package/cli/templates/core/client/handlers/index.ts +748 -748
- package/cli/templates/core/client/handlers.ts +1 -1
- package/cli/templates/core/client/index.ts +7 -7
- package/cli/templates/core/client/storage.ts +195 -195
- package/cli/templates/core/client/types.ts +186 -186
- package/cli/templates/core/client-modules/appflare.ts +1 -1
- package/cli/templates/core/client-modules/handlers.ts +1 -1
- package/cli/templates/core/client-modules/index.ts +1 -1
- package/cli/templates/core/client-modules/storage.ts +1 -1
- package/cli/templates/core/client-modules/types.ts +1 -1
- package/cli/templates/core/client.artifacts.ts +39 -39
- package/cli/templates/core/client.ts +4 -4
- package/cli/templates/core/drizzle.ts +15 -15
- package/cli/templates/core/export.ts +14 -14
- package/cli/templates/core/handlers.route.ts +24 -24
- package/cli/templates/core/handlers.ts +1 -1
- package/cli/templates/core/imports.ts +9 -9
- package/cli/templates/core/server.ts +38 -38
- package/cli/templates/core/types.ts +6 -6
- package/cli/templates/core/wrangler.ts +109 -109
- package/cli/templates/dashboard/builders/functions/index.ts +17 -17
- package/cli/templates/dashboard/builders/functions/render-page/header.ts +20 -20
- package/cli/templates/dashboard/builders/functions/render-page/index.ts +33 -33
- package/cli/templates/dashboard/builders/functions/render-page/request-panel.ts +171 -171
- package/cli/templates/dashboard/builders/functions/render-page/result-panel.ts +85 -85
- package/cli/templates/dashboard/builders/functions/render-page/scripts.ts +554 -554
- package/cli/templates/dashboard/builders/navigation.ts +122 -122
- package/cli/templates/dashboard/builders/storage/index.ts +13 -13
- package/cli/templates/dashboard/builders/storage/routes/create-directory-route.ts +29 -29
- package/cli/templates/dashboard/builders/storage/routes/delete-route.ts +18 -18
- package/cli/templates/dashboard/builders/storage/routes/download-route.ts +23 -23
- package/cli/templates/dashboard/builders/storage/routes/index.ts +22 -22
- package/cli/templates/dashboard/builders/storage/routes/list-route.ts +25 -25
- package/cli/templates/dashboard/builders/storage/routes/preview-route.ts +21 -21
- package/cli/templates/dashboard/builders/storage/routes/upload-route.ts +21 -21
- package/cli/templates/dashboard/builders/storage/runtime/helpers.ts +72 -72
- package/cli/templates/dashboard/builders/storage/runtime/storage-page.ts +130 -130
- package/cli/templates/dashboard/builders/table-routes/common/drawer-panel.ts +27 -27
- package/cli/templates/dashboard/builders/table-routes/common/pagination.ts +30 -30
- package/cli/templates/dashboard/builders/table-routes/common/search-bar.ts +23 -23
- package/cli/templates/dashboard/builders/table-routes/fragments.ts +217 -217
- package/cli/templates/dashboard/builders/table-routes/helpers.ts +45 -45
- package/cli/templates/dashboard/builders/table-routes/index.ts +8 -8
- package/cli/templates/dashboard/builders/table-routes/table/actions-cell.ts +71 -71
- package/cli/templates/dashboard/builders/table-routes/table/get-route.ts +291 -291
- package/cli/templates/dashboard/builders/table-routes/table/index.ts +80 -80
- package/cli/templates/dashboard/builders/table-routes/table/post-routes.ts +163 -163
- package/cli/templates/dashboard/builders/table-routes/table-route.ts +7 -7
- package/cli/templates/dashboard/builders/table-routes/users/get-route.ts +69 -69
- package/cli/templates/dashboard/builders/table-routes/users/html/modals.ts +57 -57
- package/cli/templates/dashboard/builders/table-routes/users/html/page.ts +27 -27
- package/cli/templates/dashboard/builders/table-routes/users/html/table.ts +128 -128
- package/cli/templates/dashboard/builders/table-routes/users/index.ts +32 -32
- package/cli/templates/dashboard/builders/table-routes/users/post-routes.ts +150 -150
- package/cli/templates/dashboard/builders/table-routes/users/redirect.ts +14 -14
- package/cli/templates/dashboard/builders/table-routes/users-route.ts +10 -10
- package/cli/templates/dashboard/components/dashboard-home.ts +23 -23
- package/cli/templates/dashboard/components/layout.ts +388 -388
- package/cli/templates/dashboard/components/login-page.ts +65 -65
- package/cli/templates/dashboard/index.ts +61 -61
- package/cli/templates/dashboard/types.ts +9 -9
- package/cli/templates/handlers/README.md +353 -353
- package/cli/templates/handlers/auth.ts +37 -37
- package/cli/templates/handlers/execution.ts +42 -42
- package/cli/templates/handlers/generators/context/context-creation.ts +101 -101
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -11
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -24
- package/cli/templates/handlers/generators/context/storage-api.ts +82 -82
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -59
- package/cli/templates/handlers/generators/context/types.ts +40 -40
- package/cli/templates/handlers/generators/context.ts +43 -43
- package/cli/templates/handlers/generators/execution.ts +15 -15
- package/cli/templates/handlers/generators/handlers.ts +13 -13
- package/cli/templates/handlers/generators/registration/modules/cron.ts +26 -26
- package/cli/templates/handlers/generators/registration/modules/realtime/auth.ts +75 -75
- package/cli/templates/handlers/generators/registration/modules/realtime/durable-object.ts +144 -144
- package/cli/templates/handlers/generators/registration/modules/realtime/index.ts +14 -14
- package/cli/templates/handlers/generators/registration/modules/realtime/publisher.ts +102 -102
- package/cli/templates/handlers/generators/registration/modules/realtime/routes.ts +164 -164
- package/cli/templates/handlers/generators/registration/modules/realtime/types.ts +30 -30
- package/cli/templates/handlers/generators/registration/modules/realtime/utils.ts +516 -516
- package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -56
- package/cli/templates/handlers/generators/registration/modules/storage.ts +199 -199
- package/cli/templates/handlers/generators/registration/sections.ts +210 -210
- package/cli/templates/handlers/generators/types/context.ts +92 -92
- package/cli/templates/handlers/generators/types/core.ts +106 -106
- package/cli/templates/handlers/generators/types/operations.ts +135 -135
- package/cli/templates/handlers/generators/types/query-definitions/filter-and-where-types.ts +281 -259
- package/cli/templates/handlers/generators/types/query-definitions/query-api-types.ts +135 -135
- package/cli/templates/handlers/generators/types/query-definitions/query-helper-functions.ts +1103 -1031
- package/cli/templates/handlers/generators/types/query-definitions/schema-and-table-types.ts +278 -246
- package/cli/templates/handlers/generators/types/query-definitions.ts +13 -13
- package/cli/templates/handlers/generators/types/query-runtime/handled-error.ts +13 -13
- package/cli/templates/handlers/generators/types/query-runtime/runtime-aggregate-and-footer.ts +174 -174
- package/cli/templates/handlers/generators/types/query-runtime/runtime-read.ts +157 -121
- package/cli/templates/handlers/generators/types/query-runtime/runtime-setup.ts +45 -45
- package/cli/templates/handlers/generators/types/query-runtime/runtime-write.ts +697 -697
- package/cli/templates/handlers/generators/types/query-runtime.ts +15 -15
- package/cli/templates/handlers/index.ts +43 -43
- package/cli/templates/handlers/operations.ts +116 -116
- package/cli/templates/handlers/registration.ts +91 -91
- package/cli/templates/handlers/types.ts +15 -15
- package/cli/templates/handlers/utils.ts +48 -48
- package/cli/types.ts +110 -110
- package/cli/utils/handler-discovery.ts +466 -466
- package/cli/utils/json-utils.ts +24 -24
- package/cli/utils/path-utils.ts +19 -19
- package/cli/utils/schema-discovery.ts +399 -399
- package/dist/cli/index.d.mts +2 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +366 -203
- package/dist/cli/index.mjs +366 -203
- package/index.ts +18 -18
- package/package.json +58 -58
- package/react/index.ts +5 -5
- package/react/use-infinite-query.ts +252 -252
- package/react/use-mutation.ts +89 -89
- package/react/use-query.ts +207 -207
- package/schema.ts +415 -415
- package/test-better-auth-hash.ts +2 -2
- package/tsconfig.json +6 -6
- package/tsup.config.ts +82 -82
|
@@ -1,291 +1,291 @@
|
|
|
1
|
-
import { DiscoveredTable } from "../../../types";
|
|
2
|
-
import { buildPaginationHtml } from "../common/pagination";
|
|
3
|
-
import { buildSearchBarHtml } from "../common/search-bar";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Builds the GET route handler code string for /admin/table/:tableName
|
|
7
|
-
* PocketBase-style layout with breadcrumb header, filter bar, and clean table.
|
|
8
|
-
*/
|
|
9
|
-
export function buildTableGetRoute(
|
|
10
|
-
table: DiscoveredTable,
|
|
11
|
-
defaultSort: string,
|
|
12
|
-
primaryKey: string,
|
|
13
|
-
hasPrimaryKey: boolean,
|
|
14
|
-
columns: string[],
|
|
15
|
-
searchConditions: string,
|
|
16
|
-
headers: string,
|
|
17
|
-
rowCells: string,
|
|
18
|
-
actionsCell: string,
|
|
19
|
-
createInputs: string,
|
|
20
|
-
): string {
|
|
21
|
-
const paginationHtml = buildPaginationHtml(
|
|
22
|
-
`/admin/table/${table.exportName}`,
|
|
23
|
-
);
|
|
24
|
-
const searchBarHtml = buildSearchBarHtml(
|
|
25
|
-
`/admin/table/${table.exportName}`,
|
|
26
|
-
"Search term or filter...",
|
|
27
|
-
);
|
|
28
|
-
const headerSelectionCell = hasPrimaryKey
|
|
29
|
-
? `<th class="w-10"><input id="select-all-${table.exportName}" type="checkbox" class="checkbox checkbox-xs" /></th>`
|
|
30
|
-
: `<th class="w-10"><input type="checkbox" class="checkbox checkbox-xs opacity-30" disabled /></th>`;
|
|
31
|
-
const rowSelectionCell = hasPrimaryKey
|
|
32
|
-
? `<td><input type="checkbox" class="checkbox checkbox-xs row-select-checkbox" value="\${String((row as any).${primaryKey} ?? '')}" /></td>`
|
|
33
|
-
: `<td><input type="checkbox" class="checkbox checkbox-xs opacity-30" disabled /></td>`;
|
|
34
|
-
const bulkDeleteUi = hasPrimaryKey
|
|
35
|
-
? `
|
|
36
|
-
\t\t\t\t\t\t<div id="bulk-delete-bar-${table.exportName}" class="fixed bottom-4 left-1/2 -translate-x-1/2 z-40 hidden">
|
|
37
|
-
\t\t\t\t\t\t\t<div class="bg-base-100 border border-base-200 rounded-xl shadow-lg px-3 py-2 flex items-center gap-3">
|
|
38
|
-
\t\t\t\t\t\t\t\t<div class="text-xs text-base-content/70">
|
|
39
|
-
\t\t\t\t\t\t\t\t\t<span id="bulk-selected-count-${table.exportName}" class="font-medium text-base-content">0</span> selected
|
|
40
|
-
\t\t\t\t\t\t\t\t</div>
|
|
41
|
-
\t\t\t\t\t\t\t\t<label class="label cursor-pointer gap-2 py-0 px-1">
|
|
42
|
-
\t\t\t\t\t\t\t\t\t<span class="label-text text-xs">All matching (\${total})</span>
|
|
43
|
-
\t\t\t\t\t\t\t\t\t<input id="bulk-all-matching-${table.exportName}" type="checkbox" class="checkbox checkbox-xs" />
|
|
44
|
-
\t\t\t\t\t\t\t\t</label>
|
|
45
|
-
\t\t\t\t\t\t\t\t<button id="bulk-delete-trigger-${table.exportName}" type="button" class="btn btn-error btn-xs gap-1">
|
|
46
|
-
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:delete-outline" width="14" height="14"></iconify-icon>
|
|
47
|
-
\t\t\t\t\t\t\t\t\tDelete selected
|
|
48
|
-
\t\t\t\t\t\t\t\t</button>
|
|
49
|
-
\t\t\t\t\t\t\t</div>
|
|
50
|
-
\t\t\t\t\t\t</div>
|
|
51
|
-
|
|
52
|
-
\t\t\t\t\t\t<dialog id="bulk-delete-modal-${table.exportName}" class="modal">
|
|
53
|
-
\t\t\t\t\t\t\t<div class="modal-box max-w-sm p-6 space-y-4">
|
|
54
|
-
\t\t\t\t\t\t\t\t<h3 class="font-semibold text-base">Delete selected rows?</h3>
|
|
55
|
-
\t\t\t\t\t\t\t\t<p class="text-sm text-base-content/70" id="bulk-delete-description-${table.exportName}">This action cannot be undone.</p>
|
|
56
|
-
\t\t\t\t\t\t\t\t<form id="bulk-delete-form-${table.exportName}" hx-post="/admin/table/${table.exportName}/delete-bulk" hx-target="#main-content" hx-swap="outerHTML" class="space-y-3">
|
|
57
|
-
\t\t\t\t\t\t\t\t\t<input type="hidden" name="bulkMode" id="bulk-delete-mode-${table.exportName}" value="selected" />
|
|
58
|
-
\t\t\t\t\t\t\t\t\t<input type="hidden" name="selectedIds" id="bulk-delete-ids-${table.exportName}" value="" />
|
|
59
|
-
\t\t\t\t\t\t\t\t\t<input type="hidden" name="sort" value="\${sort}" />
|
|
60
|
-
\t\t\t\t\t\t\t\t\t<input type="hidden" name="order" value="\${order}" />
|
|
61
|
-
\t\t\t\t\t\t\t\t\t<input type="hidden" name="search" value="\${search}" />
|
|
62
|
-
\t\t\t\t\t\t\t\t\t<input type="hidden" name="page" value="\${page}" />
|
|
63
|
-
\t\t\t\t\t\t\t\t\t<div class="flex justify-end gap-2 pt-1">
|
|
64
|
-
\t\t\t\t\t\t\t\t\t\t<button type="button" class="btn btn-ghost btn-sm" onclick="this.closest('dialog')?.close()">Cancel</button>
|
|
65
|
-
\t\t\t\t\t\t\t\t\t\t<button type="submit" class="btn btn-error btn-sm">Delete</button>
|
|
66
|
-
\t\t\t\t\t\t\t\t\t</div>
|
|
67
|
-
\t\t\t\t\t\t\t\t</form>
|
|
68
|
-
\t\t\t\t\t\t\t</div>
|
|
69
|
-
\t\t\t\t\t\t\t<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
70
|
-
\t\t\t\t\t\t</dialog>
|
|
71
|
-
|
|
72
|
-
\t\t\t\t\t\t<script>
|
|
73
|
-
\t\t\t\t\t\t\t(() => {
|
|
74
|
-
\t\t\t\t\t\t\t\tconst container = document.querySelector('#main-content');
|
|
75
|
-
\t\t\t\t\t\t\t\tif (!container) return;
|
|
76
|
-
|
|
77
|
-
\t\t\t\t\t\t\t\tconst rowCheckboxes = Array.from(container.querySelectorAll('.row-select-checkbox')).filter((node) => node instanceof HTMLInputElement);
|
|
78
|
-
\t\t\t\t\t\t\t\tif (rowCheckboxes.length === 0) return;
|
|
79
|
-
|
|
80
|
-
\t\t\t\t\t\t\t\tconst selectAll = container.querySelector('#select-all-${table.exportName}');
|
|
81
|
-
\t\t\t\t\t\t\t\tconst bar = container.querySelector('#bulk-delete-bar-${table.exportName}');
|
|
82
|
-
\t\t\t\t\t\t\t\tconst selectedCount = container.querySelector('#bulk-selected-count-${table.exportName}');
|
|
83
|
-
\t\t\t\t\t\t\t\tconst allMatching = container.querySelector('#bulk-all-matching-${table.exportName}');
|
|
84
|
-
\t\t\t\t\t\t\t\tconst trigger = container.querySelector('#bulk-delete-trigger-${table.exportName}');
|
|
85
|
-
\t\t\t\t\t\t\t\tconst modal = container.querySelector('#bulk-delete-modal-${table.exportName}');
|
|
86
|
-
\t\t\t\t\t\t\t\tconst modeInput = container.querySelector('#bulk-delete-mode-${table.exportName}');
|
|
87
|
-
\t\t\t\t\t\t\t\tconst idsInput = container.querySelector('#bulk-delete-ids-${table.exportName}');
|
|
88
|
-
\t\t\t\t\t\t\t\tconst description = container.querySelector('#bulk-delete-description-${table.exportName}');
|
|
89
|
-
|
|
90
|
-
\t\t\t\t\t\t\t\tif (!(selectAll instanceof HTMLInputElement) ||
|
|
91
|
-
\t\t\t\t\t\t\t\t\t!(bar instanceof HTMLElement) ||
|
|
92
|
-
\t\t\t\t\t\t\t\t\t!(selectedCount instanceof HTMLElement) ||
|
|
93
|
-
\t\t\t\t\t\t\t\t\t!(allMatching instanceof HTMLInputElement) ||
|
|
94
|
-
\t\t\t\t\t\t\t\t\t!(trigger instanceof HTMLButtonElement) ||
|
|
95
|
-
\t\t\t\t\t\t\t\t\t!(modal instanceof HTMLDialogElement) ||
|
|
96
|
-
\t\t\t\t\t\t\t\t\t!(modeInput instanceof HTMLInputElement) ||
|
|
97
|
-
\t\t\t\t\t\t\t\t\t!(idsInput instanceof HTMLInputElement) ||
|
|
98
|
-
\t\t\t\t\t\t\t\t\t!(description instanceof HTMLElement)) {
|
|
99
|
-
\t\t\t\t\t\t\t\t\treturn;
|
|
100
|
-
\t\t\t\t\t\t\t\t}
|
|
101
|
-
|
|
102
|
-
\t\t\t\t\t\t\t\tconst totalRows = Number(\${total});
|
|
103
|
-
\t\t\t\t\t\t\t\tconst getSelectedIds = () => rowCheckboxes.filter((checkbox) => checkbox.checked).map((checkbox) => checkbox.value).filter((value) => value.length > 0);
|
|
104
|
-
|
|
105
|
-
\t\t\t\t\t\t\t\tconst updateUi = () => {
|
|
106
|
-
\t\t\t\t\t\t\t\t\tconst ids = getSelectedIds();
|
|
107
|
-
\t\t\t\t\t\t\t\t\tconst selected = allMatching.checked ? totalRows : ids.length;
|
|
108
|
-
\t\t\t\t\t\t\t\t\tselectedCount.textContent = String(selected);
|
|
109
|
-
\t\t\t\t\t\t\t\t\tbar.classList.toggle('hidden', selected === 0);
|
|
110
|
-
\t\t\t\t\t\t\t\t\tconst checkedCount = ids.length;
|
|
111
|
-
\t\t\t\t\t\t\t\t\tselectAll.checked = checkedCount > 0 && checkedCount === rowCheckboxes.length;
|
|
112
|
-
\t\t\t\t\t\t\t\t\tselectAll.indeterminate = checkedCount > 0 && checkedCount < rowCheckboxes.length;
|
|
113
|
-
\t\t\t\t\t\t\t\t};
|
|
114
|
-
|
|
115
|
-
\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
116
|
-
\t\t\t\t\t\t\t\t\tcheckbox.addEventListener('change', () => {
|
|
117
|
-
\t\t\t\t\t\t\t\t\t\tif (allMatching.checked) allMatching.checked = false;
|
|
118
|
-
\t\t\t\t\t\t\t\t\t\tupdateUi();
|
|
119
|
-
\t\t\t\t\t\t\t\t\t});
|
|
120
|
-
\t\t\t\t\t\t\t\t});
|
|
121
|
-
|
|
122
|
-
\t\t\t\t\t\t\t\tselectAll.addEventListener('change', () => {
|
|
123
|
-
\t\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
124
|
-
\t\t\t\t\t\t\t\t\t\tcheckbox.checked = selectAll.checked;
|
|
125
|
-
\t\t\t\t\t\t\t\t\t});
|
|
126
|
-
\t\t\t\t\t\t\t\t\tif (allMatching.checked && !selectAll.checked) allMatching.checked = false;
|
|
127
|
-
\t\t\t\t\t\t\t\t\tupdateUi();
|
|
128
|
-
\t\t\t\t\t\t\t\t});
|
|
129
|
-
|
|
130
|
-
\t\t\t\t\t\t\t\tallMatching.addEventListener('change', () => {
|
|
131
|
-
\t\t\t\t\t\t\t\t\tif (allMatching.checked) {
|
|
132
|
-
\t\t\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
133
|
-
\t\t\t\t\t\t\t\t\t\t\tcheckbox.checked = true;
|
|
134
|
-
\t\t\t\t\t\t\t\t\t\t});
|
|
135
|
-
\t\t\t\t\t\t\t\t\t}
|
|
136
|
-
\t\t\t\t\t\t\t\t\tupdateUi();
|
|
137
|
-
\t\t\t\t\t\t\t\t});
|
|
138
|
-
|
|
139
|
-
\t\t\t\t\t\t\t\ttrigger.addEventListener('click', () => {
|
|
140
|
-
\t\t\t\t\t\t\t\t\tconst ids = getSelectedIds();
|
|
141
|
-
\t\t\t\t\t\t\t\t\tif (!allMatching.checked && ids.length === 0) return;
|
|
142
|
-
\t\t\t\t\t\t\t\t\tmodeInput.value = allMatching.checked ? 'all-matching' : 'selected';
|
|
143
|
-
\t\t\t\t\t\t\t\t\tidsInput.value = ids.join(',');
|
|
144
|
-
\t\t\t\t\t\t\t\t\tdescription.textContent = allMatching.checked
|
|
145
|
-
\t\t\t\t\t\t\t\t\t\t? 'This will permanently delete all rows matching the current search context.'
|
|
146
|
-
\t\t\t\t\t\t\t\t\t\t: 'This action cannot be undone.';
|
|
147
|
-
\t\t\t\t\t\t\t\t\tmodal.showModal();
|
|
148
|
-
\t\t\t\t\t\t\t\t});
|
|
149
|
-
|
|
150
|
-
\t\t\t\t\t\t\t\tupdateUi();
|
|
151
|
-
\t\t\t\t\t\t\t})();
|
|
152
|
-
\t\t\t\t\t\t</script>
|
|
153
|
-
`
|
|
154
|
-
: "";
|
|
155
|
-
|
|
156
|
-
return `
|
|
157
|
-
\tadminApp.get('/table/${table.exportName}', async (c) => {
|
|
158
|
-
\t\tconst db = drizzle(c.env[options.databaseBinding], { schema });
|
|
159
|
-
\t\tconst page = parseInt(c.req.query('page') || '1');
|
|
160
|
-
\t\tconst limit = 20;
|
|
161
|
-
\t\tconst offset = (page - 1) * limit;
|
|
162
|
-
\t\tconst sort = c.req.query('sort') || '${defaultSort}';
|
|
163
|
-
\t\tconst order = c.req.query('order') || 'desc';
|
|
164
|
-
\t\tconst search = c.req.query('search') || '';
|
|
165
|
-
|
|
166
|
-
\t\tlet tableSchema = (schema as any).${table.exportName};
|
|
167
|
-
\t\tif (!tableSchema) return c.text("Table missing", 404);
|
|
168
|
-
|
|
169
|
-
\t\tlet query = db.select().from(tableSchema);
|
|
170
|
-
\t\tlet countQuery = db.select({ count: sql\`count(*)\` }).from(tableSchema);
|
|
171
|
-
|
|
172
|
-
\t\tif (search) {
|
|
173
|
-
const searchConditions: any[] = [];
|
|
174
|
-
\t\t\t${searchConditions}
|
|
175
|
-
\t\t\tif (searchConditions.length > 0) {
|
|
176
|
-
\t\t\t\tquery = query.where(or(...searchConditions)) as any;
|
|
177
|
-
\t\t\t\tcountQuery = countQuery.where(or(...searchConditions)) as any;
|
|
178
|
-
\t\t\t}
|
|
179
|
-
\t\t}
|
|
180
|
-
|
|
181
|
-
\t\tif (sort && tableSchema[sort]) {
|
|
182
|
-
\t\t\tquery = query.orderBy(order === 'asc' ? asc(tableSchema[sort]) : desc(tableSchema[sort])) as any;
|
|
183
|
-
\t\t}
|
|
184
|
-
|
|
185
|
-
\t\tconst data = await query.limit(limit).offset(offset).execute();
|
|
186
|
-
\t\tconst totalResult = await countQuery.execute();
|
|
187
|
-
\t\tconst total = Number(totalResult[0]?.count || 0);
|
|
188
|
-
\t\tconst totalPages = Math.ceil(total / limit);
|
|
189
|
-
|
|
190
|
-
\t\tconst tableHtml = html\`
|
|
191
|
-
\t\t\t<div class="bg-base-100 rounded-xl border border-base-200 overflow-hidden">
|
|
192
|
-
\t\t\t\t<div class="overflow-x-auto">
|
|
193
|
-
\t\t\t\t\t<table class="table table-sm md:table-md w-full">
|
|
194
|
-
\t\t\t\t\t\t<thead>
|
|
195
|
-
\t\t\t\t\t\t\t<tr class="border-b border-base-200">
|
|
196
|
-
\t\t\t\t\t\t\t\t${headerSelectionCell}
|
|
197
|
-
\t\t\t\t\t\t\t\t${headers}
|
|
198
|
-
\t\t\t\t\t\t\t\t<th class="w-[100px] text-right">
|
|
199
|
-
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:dots-horizontal" width="16" height="16" class="opacity-30"></iconify-icon>
|
|
200
|
-
\t\t\t\t\t\t\t\t</th>
|
|
201
|
-
\t\t\t\t\t\t\t</tr>
|
|
202
|
-
\t\t\t\t\t\t</thead>
|
|
203
|
-
\t\t\t\t\t\t<tbody>
|
|
204
|
-
\t\t\t\t\t\t\t\${data.map((row, rowIndex) => html\`
|
|
205
|
-
\t\t\t\t\t\t\t<tr class="hover:bg-base-200/30 transition-colors">
|
|
206
|
-
\t\t\t\t\t\t\t\t${rowSelectionCell}
|
|
207
|
-
\t\t\t\t\t\t\t\t${rowCells}
|
|
208
|
-
\t\t\t\t\t\t\t\t${actionsCell}
|
|
209
|
-
\t\t\t\t\t\t\t</tr>
|
|
210
|
-
\t\t\t\t\t\t\t\`)}
|
|
211
|
-
\t\t\t\t\t\t\t\${data.length === 0 ? html\`
|
|
212
|
-
\t\t\t\t\t\t\t\t<tr>
|
|
213
|
-
\t\t\t\t\t\t\t\t\t<td colspan="${columns.length + 2}" class="text-center py-12">
|
|
214
|
-
\t\t\t\t\t\t\t\t\t\t<div class="flex flex-col items-center gap-3">
|
|
215
|
-
\t\t\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:database-off-outline" width="40" height="40" class="opacity-20"></iconify-icon>
|
|
216
|
-
\t\t\t\t\t\t\t\t\t\t\t<p class="text-sm text-base-content/40">No records found.</p>
|
|
217
|
-
\t\t\t\t\t\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" class="btn btn-sm btn-primary gap-1">
|
|
218
|
-
\t\t\t\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:plus" width="16" height="16"></iconify-icon>
|
|
219
|
-
\t\t\t\t\t\t\t\t\t\t\t\tNew record
|
|
220
|
-
\t\t\t\t\t\t\t\t\t\t\t</label>
|
|
221
|
-
\t\t\t\t\t\t\t\t\t\t</div>
|
|
222
|
-
\t\t\t\t\t\t\t\t\t</td>
|
|
223
|
-
\t\t\t\t\t\t\t\t</tr>
|
|
224
|
-
\t\t\t\t\t\t\t\` : ''}
|
|
225
|
-
\t\t\t\t\t\t</tbody>
|
|
226
|
-
\t\t\t\t\t</table>
|
|
227
|
-
\t\t\t\t</div>
|
|
228
|
-
\t\t\t\t${paginationHtml}
|
|
229
|
-
\t\t\t</div>
|
|
230
|
-
\t\t\`;
|
|
231
|
-
|
|
232
|
-
\t\tconst content = html\`
|
|
233
|
-
\t\t\t<div id="main-content">
|
|
234
|
-
\t\t\t\t<div class="drawer drawer-end">
|
|
235
|
-
\t\t\t\t\t<input id="create-drawer-${table.exportName}" type="checkbox" class="drawer-toggle" />
|
|
236
|
-
\t\t\t\t\t<div class="drawer-content">
|
|
237
|
-
\t\t\t\t\t\t<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-5 gap-3">
|
|
238
|
-
\t\t\t\t\t\t\t<div class="flex items-center gap-2 text-sm">
|
|
239
|
-
\t\t\t\t\t\t\t\t<a href="/admin" class="text-base-content/40 hover:text-primary transition-colors">Collections</a>
|
|
240
|
-
\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:chevron-right" width="14" height="14" class="opacity-30"></iconify-icon>
|
|
241
|
-
\t\t\t\t\t\t\t\t<span class="font-semibold capitalize">${table.tableName}</span>
|
|
242
|
-
\t\t\t\t\t\t\t\t<button class="btn btn-ghost btn-xs btn-square opacity-40 hover:opacity-100" onclick="window.location.reload()">
|
|
243
|
-
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:refresh" width="14" height="14"></iconify-icon>
|
|
244
|
-
\t\t\t\t\t\t\t\t</button>
|
|
245
|
-
\t\t\t\t\t\t\t</div>
|
|
246
|
-
\t\t\t\t\t\t\t<div class="flex items-center gap-2 w-full md:w-auto">
|
|
247
|
-
\t\t\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" class="btn btn-primary btn-sm gap-1">
|
|
248
|
-
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:plus" width="16" height="16"></iconify-icon>
|
|
249
|
-
\t\t\t\t\t\t\t\t\tNew record
|
|
250
|
-
\t\t\t\t\t\t\t\t</label>
|
|
251
|
-
\t\t\t\t\t\t\t</div>
|
|
252
|
-
\t\t\t\t\t\t</div>
|
|
253
|
-
\t\t\t\t\t\t<div class="mb-4">
|
|
254
|
-
\t\t\t\t\t\t\t${searchBarHtml}
|
|
255
|
-
\t\t\t\t\t\t</div>
|
|
256
|
-
\t\t\t\t\t\t\${tableHtml}
|
|
257
|
-
\t\t\t\t\t\t${bulkDeleteUi}
|
|
258
|
-
\t\t\t\t\t</div>
|
|
259
|
-
\t\t\t\t\t<div class="drawer-side z-50">
|
|
260
|
-
\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
261
|
-
\t\t\t\t\t\t<div class="w-full max-w-md min-h-full bg-base-100 p-6 border-l border-base-200 overflow-y-auto shadow-lg">
|
|
262
|
-
\t\t\t\t\t\t\t<div class="flex justify-between items-center mb-5">
|
|
263
|
-
\t\t\t\t\t\t\t\t<h3 class="text-base font-semibold">Create ${table.tableName}</h3>
|
|
264
|
-
\t\t\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" class="btn btn-sm btn-ghost btn-square">
|
|
265
|
-
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:close" width="18" height="18"></iconify-icon>
|
|
266
|
-
\t\t\t\t\t\t\t\t</label>
|
|
267
|
-
\t\t\t\t\t\t\t</div>
|
|
268
|
-
\t\t\t\t\t\t\t<form hx-post="/admin/table/${table.exportName}/create" hx-target="#main-content" hx-swap="outerHTML" class="flex flex-col gap-4">
|
|
269
|
-
\t\t\t\t\t\t\t\t<input type="hidden" name="sort" value="\${sort}" />
|
|
270
|
-
\t\t\t\t\t\t\t\t<input type="hidden" name="order" value="\${order}" />
|
|
271
|
-
\t\t\t\t\t\t\t\t<input type="hidden" name="search" value="\${search}" />
|
|
272
|
-
\t\t\t\t\t\t\t\t<input type="hidden" name="page" value="\${page}" />
|
|
273
|
-
\t\t\t\t\t\t\t\t${createInputs}
|
|
274
|
-
\t\t\t\t\t\t\t\t<button type="submit" class="btn btn-primary btn-sm mt-2">Create record</button>
|
|
275
|
-
\t\t\t\t\t\t\t</form>
|
|
276
|
-
\t\t\t\t\t\t</div>
|
|
277
|
-
\t\t\t\t\t</div>
|
|
278
|
-
\t\t\t\t</div>
|
|
279
|
-
\t\t\t</div>
|
|
280
|
-
\t\t\`;
|
|
281
|
-
|
|
282
|
-
\t\tif (c.req.header('hx-request')) {
|
|
283
|
-
\t\t\treturn c.html(content);
|
|
284
|
-
\t\t}
|
|
285
|
-
|
|
286
|
-
\t\treturn c.html(Layout({
|
|
287
|
-
\t\t\ttitle: "${table.tableName} - Admin Dashboard",
|
|
288
|
-
\t\t\tchildren: content
|
|
289
|
-
\t\t}));
|
|
290
|
-
\t});`;
|
|
291
|
-
}
|
|
1
|
+
import { DiscoveredTable } from "../../../types";
|
|
2
|
+
import { buildPaginationHtml } from "../common/pagination";
|
|
3
|
+
import { buildSearchBarHtml } from "../common/search-bar";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds the GET route handler code string for /admin/table/:tableName
|
|
7
|
+
* PocketBase-style layout with breadcrumb header, filter bar, and clean table.
|
|
8
|
+
*/
|
|
9
|
+
export function buildTableGetRoute(
|
|
10
|
+
table: DiscoveredTable,
|
|
11
|
+
defaultSort: string,
|
|
12
|
+
primaryKey: string,
|
|
13
|
+
hasPrimaryKey: boolean,
|
|
14
|
+
columns: string[],
|
|
15
|
+
searchConditions: string,
|
|
16
|
+
headers: string,
|
|
17
|
+
rowCells: string,
|
|
18
|
+
actionsCell: string,
|
|
19
|
+
createInputs: string,
|
|
20
|
+
): string {
|
|
21
|
+
const paginationHtml = buildPaginationHtml(
|
|
22
|
+
`/admin/table/${table.exportName}`,
|
|
23
|
+
);
|
|
24
|
+
const searchBarHtml = buildSearchBarHtml(
|
|
25
|
+
`/admin/table/${table.exportName}`,
|
|
26
|
+
"Search term or filter...",
|
|
27
|
+
);
|
|
28
|
+
const headerSelectionCell = hasPrimaryKey
|
|
29
|
+
? `<th class="w-10"><input id="select-all-${table.exportName}" type="checkbox" class="checkbox checkbox-xs" /></th>`
|
|
30
|
+
: `<th class="w-10"><input type="checkbox" class="checkbox checkbox-xs opacity-30" disabled /></th>`;
|
|
31
|
+
const rowSelectionCell = hasPrimaryKey
|
|
32
|
+
? `<td><input type="checkbox" class="checkbox checkbox-xs row-select-checkbox" value="\${String((row as any).${primaryKey} ?? '')}" /></td>`
|
|
33
|
+
: `<td><input type="checkbox" class="checkbox checkbox-xs opacity-30" disabled /></td>`;
|
|
34
|
+
const bulkDeleteUi = hasPrimaryKey
|
|
35
|
+
? `
|
|
36
|
+
\t\t\t\t\t\t<div id="bulk-delete-bar-${table.exportName}" class="fixed bottom-4 left-1/2 -translate-x-1/2 z-40 hidden">
|
|
37
|
+
\t\t\t\t\t\t\t<div class="bg-base-100 border border-base-200 rounded-xl shadow-lg px-3 py-2 flex items-center gap-3">
|
|
38
|
+
\t\t\t\t\t\t\t\t<div class="text-xs text-base-content/70">
|
|
39
|
+
\t\t\t\t\t\t\t\t\t<span id="bulk-selected-count-${table.exportName}" class="font-medium text-base-content">0</span> selected
|
|
40
|
+
\t\t\t\t\t\t\t\t</div>
|
|
41
|
+
\t\t\t\t\t\t\t\t<label class="label cursor-pointer gap-2 py-0 px-1">
|
|
42
|
+
\t\t\t\t\t\t\t\t\t<span class="label-text text-xs">All matching (\${total})</span>
|
|
43
|
+
\t\t\t\t\t\t\t\t\t<input id="bulk-all-matching-${table.exportName}" type="checkbox" class="checkbox checkbox-xs" />
|
|
44
|
+
\t\t\t\t\t\t\t\t</label>
|
|
45
|
+
\t\t\t\t\t\t\t\t<button id="bulk-delete-trigger-${table.exportName}" type="button" class="btn btn-error btn-xs gap-1">
|
|
46
|
+
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:delete-outline" width="14" height="14"></iconify-icon>
|
|
47
|
+
\t\t\t\t\t\t\t\t\tDelete selected
|
|
48
|
+
\t\t\t\t\t\t\t\t</button>
|
|
49
|
+
\t\t\t\t\t\t\t</div>
|
|
50
|
+
\t\t\t\t\t\t</div>
|
|
51
|
+
|
|
52
|
+
\t\t\t\t\t\t<dialog id="bulk-delete-modal-${table.exportName}" class="modal">
|
|
53
|
+
\t\t\t\t\t\t\t<div class="modal-box max-w-sm p-6 space-y-4">
|
|
54
|
+
\t\t\t\t\t\t\t\t<h3 class="font-semibold text-base">Delete selected rows?</h3>
|
|
55
|
+
\t\t\t\t\t\t\t\t<p class="text-sm text-base-content/70" id="bulk-delete-description-${table.exportName}">This action cannot be undone.</p>
|
|
56
|
+
\t\t\t\t\t\t\t\t<form id="bulk-delete-form-${table.exportName}" hx-post="/admin/table/${table.exportName}/delete-bulk" hx-target="#main-content" hx-swap="outerHTML" class="space-y-3">
|
|
57
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="bulkMode" id="bulk-delete-mode-${table.exportName}" value="selected" />
|
|
58
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="selectedIds" id="bulk-delete-ids-${table.exportName}" value="" />
|
|
59
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="sort" value="\${sort}" />
|
|
60
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="order" value="\${order}" />
|
|
61
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="search" value="\${search}" />
|
|
62
|
+
\t\t\t\t\t\t\t\t\t<input type="hidden" name="page" value="\${page}" />
|
|
63
|
+
\t\t\t\t\t\t\t\t\t<div class="flex justify-end gap-2 pt-1">
|
|
64
|
+
\t\t\t\t\t\t\t\t\t\t<button type="button" class="btn btn-ghost btn-sm" onclick="this.closest('dialog')?.close()">Cancel</button>
|
|
65
|
+
\t\t\t\t\t\t\t\t\t\t<button type="submit" class="btn btn-error btn-sm">Delete</button>
|
|
66
|
+
\t\t\t\t\t\t\t\t\t</div>
|
|
67
|
+
\t\t\t\t\t\t\t\t</form>
|
|
68
|
+
\t\t\t\t\t\t\t</div>
|
|
69
|
+
\t\t\t\t\t\t\t<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
70
|
+
\t\t\t\t\t\t</dialog>
|
|
71
|
+
|
|
72
|
+
\t\t\t\t\t\t<script>
|
|
73
|
+
\t\t\t\t\t\t\t(() => {
|
|
74
|
+
\t\t\t\t\t\t\t\tconst container = document.querySelector('#main-content');
|
|
75
|
+
\t\t\t\t\t\t\t\tif (!container) return;
|
|
76
|
+
|
|
77
|
+
\t\t\t\t\t\t\t\tconst rowCheckboxes = Array.from(container.querySelectorAll('.row-select-checkbox')).filter((node) => node instanceof HTMLInputElement);
|
|
78
|
+
\t\t\t\t\t\t\t\tif (rowCheckboxes.length === 0) return;
|
|
79
|
+
|
|
80
|
+
\t\t\t\t\t\t\t\tconst selectAll = container.querySelector('#select-all-${table.exportName}');
|
|
81
|
+
\t\t\t\t\t\t\t\tconst bar = container.querySelector('#bulk-delete-bar-${table.exportName}');
|
|
82
|
+
\t\t\t\t\t\t\t\tconst selectedCount = container.querySelector('#bulk-selected-count-${table.exportName}');
|
|
83
|
+
\t\t\t\t\t\t\t\tconst allMatching = container.querySelector('#bulk-all-matching-${table.exportName}');
|
|
84
|
+
\t\t\t\t\t\t\t\tconst trigger = container.querySelector('#bulk-delete-trigger-${table.exportName}');
|
|
85
|
+
\t\t\t\t\t\t\t\tconst modal = container.querySelector('#bulk-delete-modal-${table.exportName}');
|
|
86
|
+
\t\t\t\t\t\t\t\tconst modeInput = container.querySelector('#bulk-delete-mode-${table.exportName}');
|
|
87
|
+
\t\t\t\t\t\t\t\tconst idsInput = container.querySelector('#bulk-delete-ids-${table.exportName}');
|
|
88
|
+
\t\t\t\t\t\t\t\tconst description = container.querySelector('#bulk-delete-description-${table.exportName}');
|
|
89
|
+
|
|
90
|
+
\t\t\t\t\t\t\t\tif (!(selectAll instanceof HTMLInputElement) ||
|
|
91
|
+
\t\t\t\t\t\t\t\t\t!(bar instanceof HTMLElement) ||
|
|
92
|
+
\t\t\t\t\t\t\t\t\t!(selectedCount instanceof HTMLElement) ||
|
|
93
|
+
\t\t\t\t\t\t\t\t\t!(allMatching instanceof HTMLInputElement) ||
|
|
94
|
+
\t\t\t\t\t\t\t\t\t!(trigger instanceof HTMLButtonElement) ||
|
|
95
|
+
\t\t\t\t\t\t\t\t\t!(modal instanceof HTMLDialogElement) ||
|
|
96
|
+
\t\t\t\t\t\t\t\t\t!(modeInput instanceof HTMLInputElement) ||
|
|
97
|
+
\t\t\t\t\t\t\t\t\t!(idsInput instanceof HTMLInputElement) ||
|
|
98
|
+
\t\t\t\t\t\t\t\t\t!(description instanceof HTMLElement)) {
|
|
99
|
+
\t\t\t\t\t\t\t\t\treturn;
|
|
100
|
+
\t\t\t\t\t\t\t\t}
|
|
101
|
+
|
|
102
|
+
\t\t\t\t\t\t\t\tconst totalRows = Number(\${total});
|
|
103
|
+
\t\t\t\t\t\t\t\tconst getSelectedIds = () => rowCheckboxes.filter((checkbox) => checkbox.checked).map((checkbox) => checkbox.value).filter((value) => value.length > 0);
|
|
104
|
+
|
|
105
|
+
\t\t\t\t\t\t\t\tconst updateUi = () => {
|
|
106
|
+
\t\t\t\t\t\t\t\t\tconst ids = getSelectedIds();
|
|
107
|
+
\t\t\t\t\t\t\t\t\tconst selected = allMatching.checked ? totalRows : ids.length;
|
|
108
|
+
\t\t\t\t\t\t\t\t\tselectedCount.textContent = String(selected);
|
|
109
|
+
\t\t\t\t\t\t\t\t\tbar.classList.toggle('hidden', selected === 0);
|
|
110
|
+
\t\t\t\t\t\t\t\t\tconst checkedCount = ids.length;
|
|
111
|
+
\t\t\t\t\t\t\t\t\tselectAll.checked = checkedCount > 0 && checkedCount === rowCheckboxes.length;
|
|
112
|
+
\t\t\t\t\t\t\t\t\tselectAll.indeterminate = checkedCount > 0 && checkedCount < rowCheckboxes.length;
|
|
113
|
+
\t\t\t\t\t\t\t\t};
|
|
114
|
+
|
|
115
|
+
\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
116
|
+
\t\t\t\t\t\t\t\t\tcheckbox.addEventListener('change', () => {
|
|
117
|
+
\t\t\t\t\t\t\t\t\t\tif (allMatching.checked) allMatching.checked = false;
|
|
118
|
+
\t\t\t\t\t\t\t\t\t\tupdateUi();
|
|
119
|
+
\t\t\t\t\t\t\t\t\t});
|
|
120
|
+
\t\t\t\t\t\t\t\t});
|
|
121
|
+
|
|
122
|
+
\t\t\t\t\t\t\t\tselectAll.addEventListener('change', () => {
|
|
123
|
+
\t\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
124
|
+
\t\t\t\t\t\t\t\t\t\tcheckbox.checked = selectAll.checked;
|
|
125
|
+
\t\t\t\t\t\t\t\t\t});
|
|
126
|
+
\t\t\t\t\t\t\t\t\tif (allMatching.checked && !selectAll.checked) allMatching.checked = false;
|
|
127
|
+
\t\t\t\t\t\t\t\t\tupdateUi();
|
|
128
|
+
\t\t\t\t\t\t\t\t});
|
|
129
|
+
|
|
130
|
+
\t\t\t\t\t\t\t\tallMatching.addEventListener('change', () => {
|
|
131
|
+
\t\t\t\t\t\t\t\t\tif (allMatching.checked) {
|
|
132
|
+
\t\t\t\t\t\t\t\t\t\trowCheckboxes.forEach((checkbox) => {
|
|
133
|
+
\t\t\t\t\t\t\t\t\t\t\tcheckbox.checked = true;
|
|
134
|
+
\t\t\t\t\t\t\t\t\t\t});
|
|
135
|
+
\t\t\t\t\t\t\t\t\t}
|
|
136
|
+
\t\t\t\t\t\t\t\t\tupdateUi();
|
|
137
|
+
\t\t\t\t\t\t\t\t});
|
|
138
|
+
|
|
139
|
+
\t\t\t\t\t\t\t\ttrigger.addEventListener('click', () => {
|
|
140
|
+
\t\t\t\t\t\t\t\t\tconst ids = getSelectedIds();
|
|
141
|
+
\t\t\t\t\t\t\t\t\tif (!allMatching.checked && ids.length === 0) return;
|
|
142
|
+
\t\t\t\t\t\t\t\t\tmodeInput.value = allMatching.checked ? 'all-matching' : 'selected';
|
|
143
|
+
\t\t\t\t\t\t\t\t\tidsInput.value = ids.join(',');
|
|
144
|
+
\t\t\t\t\t\t\t\t\tdescription.textContent = allMatching.checked
|
|
145
|
+
\t\t\t\t\t\t\t\t\t\t? 'This will permanently delete all rows matching the current search context.'
|
|
146
|
+
\t\t\t\t\t\t\t\t\t\t: 'This action cannot be undone.';
|
|
147
|
+
\t\t\t\t\t\t\t\t\tmodal.showModal();
|
|
148
|
+
\t\t\t\t\t\t\t\t});
|
|
149
|
+
|
|
150
|
+
\t\t\t\t\t\t\t\tupdateUi();
|
|
151
|
+
\t\t\t\t\t\t\t})();
|
|
152
|
+
\t\t\t\t\t\t</script>
|
|
153
|
+
`
|
|
154
|
+
: "";
|
|
155
|
+
|
|
156
|
+
return `
|
|
157
|
+
\tadminApp.get('/table/${table.exportName}', async (c) => {
|
|
158
|
+
\t\tconst db = drizzle(c.env[options.databaseBinding], { schema });
|
|
159
|
+
\t\tconst page = parseInt(c.req.query('page') || '1');
|
|
160
|
+
\t\tconst limit = 20;
|
|
161
|
+
\t\tconst offset = (page - 1) * limit;
|
|
162
|
+
\t\tconst sort = c.req.query('sort') || '${defaultSort}';
|
|
163
|
+
\t\tconst order = c.req.query('order') || 'desc';
|
|
164
|
+
\t\tconst search = c.req.query('search') || '';
|
|
165
|
+
|
|
166
|
+
\t\tlet tableSchema = (schema as any).${table.exportName};
|
|
167
|
+
\t\tif (!tableSchema) return c.text("Table missing", 404);
|
|
168
|
+
|
|
169
|
+
\t\tlet query = db.select().from(tableSchema);
|
|
170
|
+
\t\tlet countQuery = db.select({ count: sql\`count(*)\` }).from(tableSchema);
|
|
171
|
+
|
|
172
|
+
\t\tif (search) {
|
|
173
|
+
const searchConditions: any[] = [];
|
|
174
|
+
\t\t\t${searchConditions}
|
|
175
|
+
\t\t\tif (searchConditions.length > 0) {
|
|
176
|
+
\t\t\t\tquery = query.where(or(...searchConditions)) as any;
|
|
177
|
+
\t\t\t\tcountQuery = countQuery.where(or(...searchConditions)) as any;
|
|
178
|
+
\t\t\t}
|
|
179
|
+
\t\t}
|
|
180
|
+
|
|
181
|
+
\t\tif (sort && tableSchema[sort]) {
|
|
182
|
+
\t\t\tquery = query.orderBy(order === 'asc' ? asc(tableSchema[sort]) : desc(tableSchema[sort])) as any;
|
|
183
|
+
\t\t}
|
|
184
|
+
|
|
185
|
+
\t\tconst data = await query.limit(limit).offset(offset).execute();
|
|
186
|
+
\t\tconst totalResult = await countQuery.execute();
|
|
187
|
+
\t\tconst total = Number(totalResult[0]?.count || 0);
|
|
188
|
+
\t\tconst totalPages = Math.ceil(total / limit);
|
|
189
|
+
|
|
190
|
+
\t\tconst tableHtml = html\`
|
|
191
|
+
\t\t\t<div class="bg-base-100 rounded-xl border border-base-200 overflow-hidden">
|
|
192
|
+
\t\t\t\t<div class="overflow-x-auto">
|
|
193
|
+
\t\t\t\t\t<table class="table table-sm md:table-md w-full">
|
|
194
|
+
\t\t\t\t\t\t<thead>
|
|
195
|
+
\t\t\t\t\t\t\t<tr class="border-b border-base-200">
|
|
196
|
+
\t\t\t\t\t\t\t\t${headerSelectionCell}
|
|
197
|
+
\t\t\t\t\t\t\t\t${headers}
|
|
198
|
+
\t\t\t\t\t\t\t\t<th class="w-[100px] text-right">
|
|
199
|
+
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:dots-horizontal" width="16" height="16" class="opacity-30"></iconify-icon>
|
|
200
|
+
\t\t\t\t\t\t\t\t</th>
|
|
201
|
+
\t\t\t\t\t\t\t</tr>
|
|
202
|
+
\t\t\t\t\t\t</thead>
|
|
203
|
+
\t\t\t\t\t\t<tbody>
|
|
204
|
+
\t\t\t\t\t\t\t\${data.map((row, rowIndex) => html\`
|
|
205
|
+
\t\t\t\t\t\t\t<tr class="hover:bg-base-200/30 transition-colors">
|
|
206
|
+
\t\t\t\t\t\t\t\t${rowSelectionCell}
|
|
207
|
+
\t\t\t\t\t\t\t\t${rowCells}
|
|
208
|
+
\t\t\t\t\t\t\t\t${actionsCell}
|
|
209
|
+
\t\t\t\t\t\t\t</tr>
|
|
210
|
+
\t\t\t\t\t\t\t\`)}
|
|
211
|
+
\t\t\t\t\t\t\t\${data.length === 0 ? html\`
|
|
212
|
+
\t\t\t\t\t\t\t\t<tr>
|
|
213
|
+
\t\t\t\t\t\t\t\t\t<td colspan="${columns.length + 2}" class="text-center py-12">
|
|
214
|
+
\t\t\t\t\t\t\t\t\t\t<div class="flex flex-col items-center gap-3">
|
|
215
|
+
\t\t\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:database-off-outline" width="40" height="40" class="opacity-20"></iconify-icon>
|
|
216
|
+
\t\t\t\t\t\t\t\t\t\t\t<p class="text-sm text-base-content/40">No records found.</p>
|
|
217
|
+
\t\t\t\t\t\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" class="btn btn-sm btn-primary gap-1">
|
|
218
|
+
\t\t\t\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:plus" width="16" height="16"></iconify-icon>
|
|
219
|
+
\t\t\t\t\t\t\t\t\t\t\t\tNew record
|
|
220
|
+
\t\t\t\t\t\t\t\t\t\t\t</label>
|
|
221
|
+
\t\t\t\t\t\t\t\t\t\t</div>
|
|
222
|
+
\t\t\t\t\t\t\t\t\t</td>
|
|
223
|
+
\t\t\t\t\t\t\t\t</tr>
|
|
224
|
+
\t\t\t\t\t\t\t\` : ''}
|
|
225
|
+
\t\t\t\t\t\t</tbody>
|
|
226
|
+
\t\t\t\t\t</table>
|
|
227
|
+
\t\t\t\t</div>
|
|
228
|
+
\t\t\t\t${paginationHtml}
|
|
229
|
+
\t\t\t</div>
|
|
230
|
+
\t\t\`;
|
|
231
|
+
|
|
232
|
+
\t\tconst content = html\`
|
|
233
|
+
\t\t\t<div id="main-content">
|
|
234
|
+
\t\t\t\t<div class="drawer drawer-end">
|
|
235
|
+
\t\t\t\t\t<input id="create-drawer-${table.exportName}" type="checkbox" class="drawer-toggle" />
|
|
236
|
+
\t\t\t\t\t<div class="drawer-content">
|
|
237
|
+
\t\t\t\t\t\t<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-5 gap-3">
|
|
238
|
+
\t\t\t\t\t\t\t<div class="flex items-center gap-2 text-sm">
|
|
239
|
+
\t\t\t\t\t\t\t\t<a href="/admin" class="text-base-content/40 hover:text-primary transition-colors">Collections</a>
|
|
240
|
+
\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:chevron-right" width="14" height="14" class="opacity-30"></iconify-icon>
|
|
241
|
+
\t\t\t\t\t\t\t\t<span class="font-semibold capitalize">${table.tableName}</span>
|
|
242
|
+
\t\t\t\t\t\t\t\t<button class="btn btn-ghost btn-xs btn-square opacity-40 hover:opacity-100" onclick="window.location.reload()">
|
|
243
|
+
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:refresh" width="14" height="14"></iconify-icon>
|
|
244
|
+
\t\t\t\t\t\t\t\t</button>
|
|
245
|
+
\t\t\t\t\t\t\t</div>
|
|
246
|
+
\t\t\t\t\t\t\t<div class="flex items-center gap-2 w-full md:w-auto">
|
|
247
|
+
\t\t\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" class="btn btn-primary btn-sm gap-1">
|
|
248
|
+
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:plus" width="16" height="16"></iconify-icon>
|
|
249
|
+
\t\t\t\t\t\t\t\t\tNew record
|
|
250
|
+
\t\t\t\t\t\t\t\t</label>
|
|
251
|
+
\t\t\t\t\t\t\t</div>
|
|
252
|
+
\t\t\t\t\t\t</div>
|
|
253
|
+
\t\t\t\t\t\t<div class="mb-4">
|
|
254
|
+
\t\t\t\t\t\t\t${searchBarHtml}
|
|
255
|
+
\t\t\t\t\t\t</div>
|
|
256
|
+
\t\t\t\t\t\t\${tableHtml}
|
|
257
|
+
\t\t\t\t\t\t${bulkDeleteUi}
|
|
258
|
+
\t\t\t\t\t</div>
|
|
259
|
+
\t\t\t\t\t<div class="drawer-side z-50">
|
|
260
|
+
\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
261
|
+
\t\t\t\t\t\t<div class="w-full max-w-md min-h-full bg-base-100 p-6 border-l border-base-200 overflow-y-auto shadow-lg">
|
|
262
|
+
\t\t\t\t\t\t\t<div class="flex justify-between items-center mb-5">
|
|
263
|
+
\t\t\t\t\t\t\t\t<h3 class="text-base font-semibold">Create ${table.tableName}</h3>
|
|
264
|
+
\t\t\t\t\t\t\t\t<label for="create-drawer-${table.exportName}" class="btn btn-sm btn-ghost btn-square">
|
|
265
|
+
\t\t\t\t\t\t\t\t\t<iconify-icon icon="mdi:close" width="18" height="18"></iconify-icon>
|
|
266
|
+
\t\t\t\t\t\t\t\t</label>
|
|
267
|
+
\t\t\t\t\t\t\t</div>
|
|
268
|
+
\t\t\t\t\t\t\t<form hx-post="/admin/table/${table.exportName}/create" hx-target="#main-content" hx-swap="outerHTML" class="flex flex-col gap-4">
|
|
269
|
+
\t\t\t\t\t\t\t\t<input type="hidden" name="sort" value="\${sort}" />
|
|
270
|
+
\t\t\t\t\t\t\t\t<input type="hidden" name="order" value="\${order}" />
|
|
271
|
+
\t\t\t\t\t\t\t\t<input type="hidden" name="search" value="\${search}" />
|
|
272
|
+
\t\t\t\t\t\t\t\t<input type="hidden" name="page" value="\${page}" />
|
|
273
|
+
\t\t\t\t\t\t\t\t${createInputs}
|
|
274
|
+
\t\t\t\t\t\t\t\t<button type="submit" class="btn btn-primary btn-sm mt-2">Create record</button>
|
|
275
|
+
\t\t\t\t\t\t\t</form>
|
|
276
|
+
\t\t\t\t\t\t</div>
|
|
277
|
+
\t\t\t\t\t</div>
|
|
278
|
+
\t\t\t\t</div>
|
|
279
|
+
\t\t\t</div>
|
|
280
|
+
\t\t\`;
|
|
281
|
+
|
|
282
|
+
\t\tif (c.req.header('hx-request')) {
|
|
283
|
+
\t\t\treturn c.html(content);
|
|
284
|
+
\t\t}
|
|
285
|
+
|
|
286
|
+
\t\treturn c.html(Layout({
|
|
287
|
+
\t\t\ttitle: "${table.tableName} - Admin Dashboard",
|
|
288
|
+
\t\t\tchildren: content
|
|
289
|
+
\t\t}));
|
|
290
|
+
\t});`;
|
|
291
|
+
}
|