admindb 2.6.0 → 2.6.2

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.
@@ -1,10 +1 @@
1
- <div class="mx-auto mt-16 max-w-md space-y-6 text-center">
2
- <div class="mx-auto grid h-20 w-20 place-items-center rounded-3xl bg-error/10 text-error">
3
- {{{icon "warning" class="h-10 w-10"}}}
4
- </div>
5
- <div>
6
- <h1 class="text-5xl font-extrabold tracking-tight text-error">{{status}}</h1>
7
- <p class="mt-2 text-base-content/60">{{error}}</p>
8
- </div>
9
- <a href="{{basePath}}/" class="btn btn-primary">Back to home</a>
10
- </div>
1
+ <div class="mx-auto mt-16 max-w-md space-y-6 text-center">
11
2
  <div class="mx-auto grid h-20 w-20 place-items-center rounded-3xl bg-error/10 text-error">
12
3
  {{{icon "warning" class="h-10 w-10"}}}
13
4
  </div>
14
5
  <div>
15
6
  <h1 class="text-5xl font-extrabold tracking-tight text-error">{{status}}</h1>
16
7
  <p class="mt-2 text-base-content/60">{{error}}</p>
17
8
  </div>
18
9
  <a href="{{basePath}}/" class="btn btn-primary">Back to home</a>
@@ -1 +1,252 @@
1
- <div class="space-y-6 max-w-5xl mx-auto pb-16">
2
1
  <div class="page-header">
3
2
  <div class="page-header-row">
4
3
  <div>
5
4
  <div class="page-pretitle">Developer Tools</div>
6
5
  <h1 class="page-title">Seed Data Generator</h1>
7
6
  <p class="mt-0.5 text-xs text-base-content/60">
8
7
  Generate realistic mock datasets with column heuristics or populate complete relational entity graphs.
9
8
  </p>
10
9
  </div>
11
10
  </div>
12
11
  </div>
13
12
  {{#if hasTables}}
14
13
  <div class="grid gap-6 lg:grid-cols-[1fr_380px]">
15
14
  <div class="space-y-4">
16
15
  <div class="app-card overflow-hidden">
17
16
  <div class="p-4 sm:p-5 border-b border-base-200 bg-base-200/30 flex flex-wrap items-center justify-between gap-3">
18
17
  <div>
19
18
  <h2 class="text-sm font-bold text-base-content flex items-center gap-2">
20
19
  <span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">1</span>
21
20
  Choose Target Table
22
21
  </h2>
23
22
  <p class="text-[11px] text-base-content/50 mt-0.5">Select the root table to generate rows for.</p>
24
23
  </div>
25
24
  <div class="w-full sm:w-56">
26
25
  <div class="relative">
27
26
  <span class="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-base-content/40">
28
27
  {{{icon "search" class="h-3.5 w-3.5"}}}
29
28
  </span>
30
29
  <input id="table-search-input" type="text" placeholder="Filter tables…" autocomplete="off"
31
30
  class="field-input text-xs !py-1 !pl-8 w-full" />
32
31
  </div>
33
32
  </div>
34
33
  </div>
35
34
  <div id="table-cards-list" class="p-3 sm:p-4 space-y-2 max-h-[480px] overflow-y-auto">
36
35
  {{#each (or seedTables tables)}}
37
36
  <label class="table-option-card block cursor-pointer select-none rounded-xl border p-3 transition-all {{#if (eq this.name ../defaultTable)}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
38
37
  data-table="{{this.name}}">
39
38
  <div class="flex items-center justify-between gap-3">
40
39
  <div class="flex items-center gap-3 min-w-0">
41
40
  <input type="radio" name="selected_table" value="{{this.name}}" class="radio radio-primary radio-xs"
42
41
  {{#if (eq this.name ../defaultTable)}}checked{{/if}} />
43
42
  <div class="min-w-0">
44
43
  <div class="font-mono font-bold text-sm text-base-content truncate flex items-center gap-2">
45
44
  {{{icon "table" class="h-3.5 w-3.5 text-base-content/50 shrink-0"}}}
46
45
  <span>{{this.name}}</span>
47
46
  </div>
48
47
  <div class="flex items-center gap-2 mt-0.5 text-[11px] text-base-content/50">
49
48
  <span>{{this.cols}} cols</span>
50
49
  <span>·</span>
51
50
  <span>{{this.rowCount}} existing rows</span>
52
51
  {{#if this.fks}}
53
52
  <span>·</span>
54
53
  <span class="text-amber-500 font-medium">{{this.fks}} FK refs</span>
55
54
  {{/if}}
56
55
  </div>
57
56
  </div>
58
57
  </div>
59
58
  <div class="flex items-center gap-1 shrink-0">
60
59
  <button type="button" class="btn btn-ghost btn-xs text-[11px] text-base-content/70 hover:text-primary quick-seed-btn"
61
60
  data-table="{{this.name}}" data-mode="single" title="Seed this table directly">
62
61
  Single
63
62
  </button>
64
63
  <button type="button" class="btn btn-ghost btn-xs text-[11px] text-emerald-600 hover:text-emerald-500 quick-seed-btn"
65
64
  data-table="{{this.name}}" data-mode="chain" title="Seed relational ER chain directly">
66
65
  ER Chain
67
66
  </button>
68
67
  </div>
69
68
  </div>
70
69
  </label>
71
70
  {{/each}}
72
71
  </div>
73
72
  </div>
74
73
  </div>
75
74
  <div class="space-y-4 lg:sticky lg:top-20 self-start">
76
75
  <div class="app-card overflow-hidden">
77
76
  <div class="p-4 border-b border-base-200 bg-base-200/30">
78
77
  <h2 class="text-sm font-bold text-base-content flex items-center gap-2">
79
78
  <span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">2</span>
80
79
  Choose Generation Mode
81
80
  </h2>
82
81
  <p class="text-[11px] text-base-content/50 mt-0.5">Select how rows should be populated.</p>
83
82
  </div>
84
83
  <div class="p-3.5 space-y-2.5">
85
84
  <label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'single')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
86
85
  data-mode="single">
87
86
  <div class="flex items-start gap-3">
88
87
  <input type="radio" name="selected_mode" value="single" class="radio radio-primary radio-xs mt-0.5"
89
88
  {{#if (eq defaultMode 'single')}}checked{{/if}} />
90
89
  <div class="space-y-1">
91
90
  <div class="flex items-center gap-2">
92
91
  <span class="font-bold text-xs text-base-content">Single Table Seeder</span>
93
92
  <span class="badge badge-xs badge-ghost text-[10px] font-mono">Standalone</span>
94
93
  </div>
95
94
  <p class="text-[11px] text-base-content/60 leading-relaxed">
96
95
  Generates realistic rows for the selected table with automated column heuristics (names, emails, dates, addresses, UUIDs) and live formula templates.
97
96
  </p>
98
97
  </div>
99
98
  </div>
100
99
  </label>
101
100
  <label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'chain')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
102
101
  data-mode="chain">
103
102
  <div class="flex items-start gap-3">
104
103
  <input type="radio" name="selected_mode" value="chain" class="radio radio-primary radio-xs mt-0.5"
105
104
  {{#if (eq defaultMode 'chain')}}checked{{/if}} />
106
105
  <div class="space-y-1">
107
106
  <div class="flex items-center gap-2">
108
107
  <span class="font-bold text-xs text-base-content">Relational ER Chain Seeder</span>
109
108
  <span class="badge badge-xs badge-outline badge-success text-[10px] font-mono">Full Graph</span>
110
109
  </div>
111
110
  <p class="text-[11px] text-base-content/60 leading-relaxed">
112
111
  Analyzes schema graph foreign keys and seeds related parent & child entities in topological order to guarantee full relational integrity.
113
112
  </p>
114
113
  </div>
115
114
  </div>
116
115
  </label>
117
116
  </div>
118
117
  <div class="p-3.5 border-t border-base-200 bg-base-200/20">
119
118
  <button id="btn-launch-seed" type="button" class="btn btn-primary btn-sm w-full gap-2 font-bold shadow-sm">
120
119
  {{{icon "seed" class="h-4 w-4"}}}
121
120
  <span id="launch-btn-label">Open Seed Generator</span>
122
121
  {{{icon "arrowRight" class="h-3.5 w-3.5"}}}
123
122
  </button>
124
123
  </div>
125
124
  </div>
126
125
  <div class="p-3.5 rounded-xl bg-base-200/50 border border-base-300 text-xs space-y-1 text-base-content/70">
127
126
  <div class="font-semibold text-base-content flex items-center gap-1.5 text-[11px]">
128
127
  {{{icon "info" class="h-3.5 w-3.5 text-primary"}}}
129
128
  Deterministic Generation
130
129
  </div>
131
130
  <p class="text-[11px] leading-relaxed text-base-content/60">
132
131
  You can specify an exact numeric seed for deterministic reproducible data, or import/export seed profiles.
133
132
  </p>
134
133
  </div>
135
134
  </div>
136
135
  </div>
137
136
  {{else}}
138
137
  <div class="empty-state">
139
138
  <span class="empty-icon">{{{icon "table" class="h-7 w-7"}}}</span>
140
139
  <p class="text-sm font-medium text-base-content/70">No tables available in the database</p>
141
140
  <p class="text-xs text-base-content/50 mt-1 max-w-sm">Create a table using the visual designer or run SQL migrations before generating seed data.</p>
142
141
  <div class="mt-4 flex gap-2">
143
142
  <a href="{{basePath}}/designer/{{encodeUri dbId}}" class="btn btn-primary btn-sm gap-1.5">
144
143
  {{{icon "plus" class="h-4 w-4"}}}
145
144
  Create table
146
145
  </a>
147
146
  <a href="{{basePath}}/query/{{encodeUri dbId}}" class="btn btn-outline btn-sm gap-1.5">
148
147
  {{{icon "query" class="h-4 w-4"}}}
149
148
  Query editor
150
149
  </a>
151
150
  </div>
152
151
  </div>
153
152
  {{/if}}
154
153
  const BASE = '{{basePath}}';
155
154
  const DB_ID = '{{dbId}}';
156
155
  let selectedTable = '{{defaultTable}}';
157
156
  let selectedMode = '{{defaultMode}}' || 'single';
158
157
  const tableCards = document.querySelectorAll('.table-option-card');
159
158
  const modeCards = document.querySelectorAll('.mode-option-card');
160
159
  const launchBtn = document.getElementById('btn-launch-seed');
161
160
  const launchLabel = document.getElementById('launch-btn-label');
162
161
  const searchInput = document.getElementById('table-search-input');
163
162
  function updateLaunchLabel() {
164
163
  if (launchLabel) {
165
164
  launchLabel.textContent = selectedTable
166
165
  ? `Seed "${selectedTable}" (${selectedMode === 'chain' ? 'ER Chain' : 'Single Table'})`
167
166
  : 'Open Seed Generator';
168
167
  }
169
168
  }
170
169
  function setSelectedTable(tableName) {
171
170
  selectedTable = tableName;
172
171
  tableCards.forEach(card => {
173
172
  const isMatch = card.dataset.table === tableName;
174
173
  const radio = card.querySelector('input[type="radio"]');
175
174
  if (radio) radio.checked = isMatch;
176
175
  if (isMatch) {
177
176
  card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
178
177
  card.classList.remove('border-base-200', 'bg-base-100');
179
178
  } else {
180
179
  card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
181
180
  card.classList.add('border-base-200', 'bg-base-100');
182
181
  }
183
182
  });
184
183
  updateLaunchLabel();
185
184
  }
186
185
  function setSelectedMode(mode) {
187
186
  selectedMode = mode;
188
187
  modeCards.forEach(card => {
189
188
  const isMatch = card.dataset.mode === mode;
190
189
  const radio = card.querySelector('input[type="radio"]');
191
190
  if (radio) radio.checked = isMatch;
192
191
  if (isMatch) {
193
192
  card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
194
193
  card.classList.remove('border-base-200', 'bg-base-100');
195
194
  } else {
196
195
  card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
197
196
  card.classList.add('border-base-200', 'bg-base-100');
198
197
  }
199
198
  });
200
199
  updateLaunchLabel();
201
200
  }
202
201
  tableCards.forEach(card => {
203
202
  card.addEventListener('click', (e) => {
204
203
  if (e.target.closest('.quick-seed-btn')) return;
205
204
  const table = card.dataset.table;
206
205
  if (table) setSelectedTable(table);
207
206
  });
208
207
  });
209
208
  modeCards.forEach(card => {
210
209
  card.addEventListener('click', () => {
211
210
  const mode = card.dataset.mode;
212
211
  if (mode) setSelectedMode(mode);
213
212
  });
214
213
  });
215
214
  // Direct quick-seed buttons
216
215
  document.querySelectorAll('.quick-seed-btn').forEach(btn => {
217
216
  btn.addEventListener('click', (e) => {
218
217
  e.stopPropagation();
219
218
  const table = btn.dataset.table;
220
219
  const mode = btn.dataset.mode || 'single';
221
220
  if (table) {
222
221
  window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(table)}?mode=${encodeURIComponent(mode)}`;
223
222
  }
224
223
  });
225
224
  });
226
225
  // Launch button
227
226
  if (launchBtn) {
228
227
  launchBtn.addEventListener('click', () => {
229
228
  if (!selectedTable) return;
230
229
  window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(selectedTable)}?mode=${encodeURIComponent(selectedMode)}`;
231
230
  });
232
231
  }
233
232
  // Real-time table search filter
234
233
  if (searchInput) {
235
234
  searchInput.addEventListener('input', () => {
236
235
  const q = searchInput.value.trim().toLowerCase();
237
236
  tableCards.forEach(card => {
238
237
  const name = (card.dataset.table || '').toLowerCase();
239
238
  if (!q || name.includes(q)) {
240
239
  card.classList.remove('hidden');
241
240
  } else {
242
241
  card.classList.add('hidden');
243
242
  }
244
243
  });
245
244
  });
246
245
  }
247
246
  updateLaunchLabel();
247
+ <div class="space-y-6 max-w-5xl mx-auto pb-16">
248
+ <div class="page-header">
249
+ <div class="page-header-row">
250
+ <div>
251
+ <div class="page-pretitle">Developer Tools</div>
252
+ <h1 class="page-title">Seed Data Generator</h1>
253
+ <p class="mt-0.5 text-xs text-base-content/60">
254
+ Generate realistic mock datasets with column heuristics or populate complete relational entity graphs.
255
+ </p>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ {{#if hasTables}}
260
+ <div class="grid gap-6 lg:grid-cols-[1fr_380px]">
261
+ <div class="space-y-4">
262
+ <div class="app-card overflow-hidden">
263
+ <div class="p-4 sm:p-5 border-b border-base-200 bg-base-200/30 flex flex-wrap items-center justify-between gap-3">
264
+ <div>
265
+ <h2 class="text-sm font-bold text-base-content flex items-center gap-2">
266
+ <span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">1</span>
267
+ Choose Target Table
268
+ </h2>
269
+ <p class="text-[11px] text-base-content/50 mt-0.5">Select the root table to generate rows for.</p>
270
+ </div>
271
+ <div class="w-full sm:w-56">
272
+ <div class="relative">
273
+ <span class="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-base-content/40">
274
+ {{{icon "search" class="h-3.5 w-3.5"}}}
275
+ </span>
276
+ <input id="table-search-input" type="text" placeholder="Filter tables…" autocomplete="off"
277
+ class="field-input text-xs !py-1 !pl-8 w-full" />
278
+ </div>
279
+ </div>
280
+ </div>
281
+ <div id="table-cards-list" class="p-3 sm:p-4 space-y-2 max-h-[480px] overflow-y-auto">
282
+ {{#each (or seedTables tables)}}
283
+ <label class="table-option-card block cursor-pointer select-none rounded-xl border p-3 transition-all {{#if (eq this.name ../defaultTable)}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
284
+ data-table="{{this.name}}">
285
+ <div class="flex items-center justify-between gap-3">
286
+ <div class="flex items-center gap-3 min-w-0">
287
+ <input type="radio" name="selected_table" value="{{this.name}}" class="radio radio-primary radio-xs"
288
+ {{#if (eq this.name ../defaultTable)}}checked{{/if}} />
289
+ <div class="min-w-0">
290
+ <div class="font-mono font-bold text-sm text-base-content truncate flex items-center gap-2">
291
+ {{{icon "table" class="h-3.5 w-3.5 text-base-content/50 shrink-0"}}}
292
+ <span>{{this.name}}</span>
293
+ </div>
294
+ <div class="flex items-center gap-2 mt-0.5 text-[11px] text-base-content/50">
295
+ <span>{{this.cols}} cols</span>
296
+ <span>·</span>
297
+ <span>{{this.rowCount}} existing rows</span>
298
+ {{#if this.fks}}
299
+ <span>·</span>
300
+ <span class="text-amber-500 font-medium">{{this.fks}} FK refs</span>
301
+ {{/if}}
302
+ </div>
303
+ </div>
304
+ </div>
305
+ <div class="flex items-center gap-1 shrink-0">
306
+ <button type="button" class="btn btn-ghost btn-xs text-[11px] text-base-content/70 hover:text-primary quick-seed-btn"
307
+ data-table="{{this.name}}" data-mode="single" title="Seed this table directly">
308
+ Single
309
+ </button>
310
+ <button type="button" class="btn btn-ghost btn-xs text-[11px] text-emerald-600 hover:text-emerald-500 quick-seed-btn"
311
+ data-table="{{this.name}}" data-mode="chain" title="Seed relational ER chain directly">
312
+ ER Chain
313
+ </button>
314
+ </div>
315
+ </div>
316
+ </label>
317
+ {{/each}}
318
+ </div>
319
+ </div>
320
+ </div>
321
+ <div class="space-y-4 lg:sticky lg:top-20 self-start">
322
+ <div class="app-card overflow-hidden">
323
+ <div class="p-4 border-b border-base-200 bg-base-200/30">
324
+ <h2 class="text-sm font-bold text-base-content flex items-center gap-2">
325
+ <span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">2</span>
326
+ Choose Generation Mode
327
+ </h2>
328
+ <p class="text-[11px] text-base-content/50 mt-0.5">Select how rows should be populated.</p>
329
+ </div>
330
+ <div class="p-3.5 space-y-2.5">
331
+ <label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'single')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
332
+ data-mode="single">
333
+ <div class="flex items-start gap-3">
334
+ <input type="radio" name="selected_mode" value="single" class="radio radio-primary radio-xs mt-0.5"
335
+ {{#if (eq defaultMode 'single')}}checked{{/if}} />
336
+ <div class="space-y-1">
337
+ <div class="flex items-center gap-2">
338
+ <span class="font-bold text-xs text-base-content">Single Table Seeder</span>
339
+ <span class="badge badge-xs badge-ghost text-[10px] font-mono">Standalone</span>
340
+ </div>
341
+ <p class="text-[11px] text-base-content/60 leading-relaxed">
342
+ Generates realistic rows for the selected table with automated column heuristics (names, emails, dates, addresses, UUIDs) and live formula templates.
343
+ </p>
344
+ </div>
345
+ </div>
346
+ </label>
347
+ <label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'chain')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
348
+ data-mode="chain">
349
+ <div class="flex items-start gap-3">
350
+ <input type="radio" name="selected_mode" value="chain" class="radio radio-primary radio-xs mt-0.5"
351
+ {{#if (eq defaultMode 'chain')}}checked{{/if}} />
352
+ <div class="space-y-1">
353
+ <div class="flex items-center gap-2">
354
+ <span class="font-bold text-xs text-base-content">Relational ER Chain Seeder</span>
355
+ <span class="badge badge-xs badge-outline badge-success text-[10px] font-mono">Full Graph</span>
356
+ </div>
357
+ <p class="text-[11px] text-base-content/60 leading-relaxed">
358
+ Analyzes schema graph foreign keys and seeds related parent & child entities in topological order to guarantee full relational integrity.
359
+ </p>
360
+ </div>
361
+ </div>
362
+ </label>
363
+ </div>
364
+ <div class="p-3.5 border-t border-base-200 bg-base-200/20">
365
+ <button id="btn-launch-seed" type="button" class="btn btn-primary btn-sm w-full gap-2 font-bold shadow-sm">
366
+ {{{icon "seed" class="h-4 w-4"}}}
367
+ <span id="launch-btn-label">Open Seed Generator</span>
368
+ {{{icon "arrowRight" class="h-3.5 w-3.5"}}}
369
+ </button>
370
+ </div>
371
+ </div>
372
+ <div class="p-3.5 rounded-xl bg-base-200/50 border border-base-300 text-xs space-y-1 text-base-content/70">
373
+ <div class="font-semibold text-base-content flex items-center gap-1.5 text-[11px]">
374
+ {{{icon "info" class="h-3.5 w-3.5 text-primary"}}}
375
+ Deterministic Generation
376
+ </div>
377
+ <p class="text-[11px] leading-relaxed text-base-content/60">
378
+ You can specify an exact numeric seed for deterministic reproducible data, or import/export seed profiles.
379
+ </p>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ {{else}}
384
+ <div class="empty-state">
385
+ <span class="empty-icon">{{{icon "table" class="h-7 w-7"}}}</span>
386
+ <p class="text-sm font-medium text-base-content/70">No tables available in the database</p>
387
+ <p class="text-xs text-base-content/50 mt-1 max-w-sm">Create a table using the visual designer or run SQL migrations before generating seed data.</p>
388
+ <div class="mt-4 flex gap-2">
389
+ <a href="{{basePath}}/designer/{{encodeUri dbId}}" class="btn btn-primary btn-sm gap-1.5">
390
+ {{{icon "plus" class="h-4 w-4"}}}
391
+ Create table
392
+ </a>
393
+ <a href="{{basePath}}/query/{{encodeUri dbId}}" class="btn btn-outline btn-sm gap-1.5">
394
+ {{{icon "query" class="h-4 w-4"}}}
395
+ Query editor
396
+ </a>
397
+ </div>
398
+ </div>
399
+ {{/if}}
400
+ </div>
401
+ <script>
402
+ document.addEventListener('DOMContentLoaded', () => {
403
+ const BASE = '{{basePath}}';
404
+ const DB_ID = '{{dbId}}';
405
+ let selectedTable = '{{defaultTable}}';
406
+ let selectedMode = '{{defaultMode}}' || 'single';
407
+ const tableCards = document.querySelectorAll('.table-option-card');
408
+ const modeCards = document.querySelectorAll('.mode-option-card');
409
+ const launchBtn = document.getElementById('btn-launch-seed');
410
+ const launchLabel = document.getElementById('launch-btn-label');
411
+ const searchInput = document.getElementById('table-search-input');
412
+ function updateLaunchLabel() {
413
+ if (launchLabel) {
414
+ launchLabel.textContent = selectedTable
415
+ ? `Seed "${selectedTable}" (${selectedMode === 'chain' ? 'ER Chain' : 'Single Table'})`
416
+ : 'Open Seed Generator';
417
+ }
418
+ }
419
+ function setSelectedTable(tableName) {
420
+ selectedTable = tableName;
421
+ tableCards.forEach(card => {
422
+ const isMatch = card.dataset.table === tableName;
423
+ const radio = card.querySelector('input[type="radio"]');
424
+ if (radio) radio.checked = isMatch;
425
+ if (isMatch) {
426
+ card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
427
+ card.classList.remove('border-base-200', 'bg-base-100');
428
+ } else {
429
+ card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
430
+ card.classList.add('border-base-200', 'bg-base-100');
431
+ }
432
+ });
433
+ updateLaunchLabel();
434
+ }
435
+ function setSelectedMode(mode) {
436
+ selectedMode = mode;
437
+ modeCards.forEach(card => {
438
+ const isMatch = card.dataset.mode === mode;
439
+ const radio = card.querySelector('input[type="radio"]');
440
+ if (radio) radio.checked = isMatch;
441
+ if (isMatch) {
442
+ card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
443
+ card.classList.remove('border-base-200', 'bg-base-100');
444
+ } else {
445
+ card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
446
+ card.classList.add('border-base-200', 'bg-base-100');
447
+ }
448
+ });
449
+ updateLaunchLabel();
450
+ }
451
+ tableCards.forEach(card => {
452
+ card.addEventListener('click', (e) => {
453
+ if (e.target.closest('.quick-seed-btn')) return;
454
+ const table = card.dataset.table;
455
+ if (table) setSelectedTable(table);
456
+ });
457
+ });
458
+ modeCards.forEach(card => {
459
+ card.addEventListener('click', () => {
460
+ const mode = card.dataset.mode;
461
+ if (mode) setSelectedMode(mode);
462
+ });
463
+ });
464
+ // Direct quick-seed buttons
465
+ document.querySelectorAll('.quick-seed-btn').forEach(btn => {
466
+ btn.addEventListener('click', (e) => {
467
+ e.stopPropagation();
468
+ const table = btn.dataset.table;
469
+ const mode = btn.dataset.mode || 'single';
470
+ if (table) {
471
+ window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(table)}?mode=${encodeURIComponent(mode)}`;
472
+ }
473
+ });
474
+ });
475
+ // Launch button
476
+ if (launchBtn) {
477
+ launchBtn.addEventListener('click', () => {
478
+ if (!selectedTable) return;
479
+ window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(selectedTable)}?mode=${encodeURIComponent(selectedMode)}`;
480
+ });
481
+ }
482
+ // Real-time table search filter
483
+ if (searchInput) {
484
+ searchInput.addEventListener('input', () => {
485
+ const q = searchInput.value.trim().toLowerCase();
486
+ tableCards.forEach(card => {
487
+ const name = (card.dataset.table || '').toLowerCase();
488
+ if (!q || name.includes(q)) {
489
+ card.classList.remove('hidden');
490
+ } else {
491
+ card.classList.add('hidden');
492
+ }
493
+ });
494
+ });
495
+ }
496
+ updateLaunchLabel();
497
+ });
498
+ </script>