@peanut-admin/admin 0.1.0-alpha.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.
Files changed (67) hide show
  1. package/LICENSE +202 -0
  2. package/admin-core/src/access/access.ts +27 -0
  3. package/admin-core/src/api/client.ts +200 -0
  4. package/admin-core/src/api/problem.ts +67 -0
  5. package/admin-core/src/api/refresh.ts +122 -0
  6. package/admin-core/src/auth/stores.ts +121 -0
  7. package/admin-core/src/generated/api.d.ts +22106 -0
  8. package/admin-core/src/governance/audit.ts +56 -0
  9. package/admin-core/src/governance/catalog.ts +86 -0
  10. package/admin-core/src/governance/index.ts +26 -0
  11. package/admin-core/src/governance/menu.ts +63 -0
  12. package/admin-core/src/governance/roles.ts +144 -0
  13. package/admin-core/src/governance/types.ts +64 -0
  14. package/admin-core/src/index.ts +105 -0
  15. package/admin-core/src/lifecycle/tenant.ts +59 -0
  16. package/admin-core/src/module/contribution.ts +124 -0
  17. package/admin-core/src/runtime/config.ts +55 -0
  18. package/admin-core/src/runtime/errors.ts +81 -0
  19. package/admin-core/src/runtime/guard.ts +40 -0
  20. package/admin-core/src/runtime/navigation.ts +105 -0
  21. package/admin-core/src/runtime/overrides.ts +214 -0
  22. package/admin-core/src/targets/store.ts +153 -0
  23. package/admin-shell/src/config.ts +84 -0
  24. package/admin-shell/src/index.ts +40 -0
  25. package/admin-shell/src/layout.ts +332 -0
  26. package/admin-shell/src/overrides.ts +53 -0
  27. package/admin-shell/src/states.ts +93 -0
  28. package/admin-shell/src/targets.ts +128 -0
  29. package/admin-shell/src/theme.ts +15 -0
  30. package/client-core/src/index.ts +325 -0
  31. package/client-nuxt/src/index.ts +40 -0
  32. package/client-uniapp/src/index.ts +50 -0
  33. package/file-media/src/FileAssetSelector.vue +117 -0
  34. package/file-media/src/FileMediaPage.vue +158 -0
  35. package/file-media/src/contracts.ts +220 -0
  36. package/file-media/src/index.ts +19 -0
  37. package/file-media/src/runtime.ts +210 -0
  38. package/import-export/src/ImportExportPage.vue +155 -0
  39. package/import-export/src/contracts.ts +96 -0
  40. package/import-export/src/index.ts +3 -0
  41. package/import-export/src/runtime.ts +128 -0
  42. package/integration-security/src/IntegrationSecurityPage.vue +402 -0
  43. package/integration-security/src/contracts.ts +171 -0
  44. package/integration-security/src/index.ts +3 -0
  45. package/integration-security/src/runtime.ts +180 -0
  46. package/notification-sms/src/NotificationInboxPage.vue +266 -0
  47. package/notification-sms/src/contracts.ts +195 -0
  48. package/notification-sms/src/index.ts +4 -0
  49. package/notification-sms/src/runtime.ts +143 -0
  50. package/ops-console/src/OpsConsolePage.vue +337 -0
  51. package/ops-console/src/contracts.ts +169 -0
  52. package/ops-console/src/index.ts +3 -0
  53. package/ops-console/src/runtime.ts +199 -0
  54. package/package.json +108 -0
  55. package/reference-codes/src/ReferenceCodesPage.vue +942 -0
  56. package/reference-codes/src/contracts.ts +484 -0
  57. package/reference-codes/src/index.ts +53 -0
  58. package/reference-codes/src/runtime.ts +855 -0
  59. package/settings/src/SettingsPage.vue +536 -0
  60. package/settings/src/contracts.ts +331 -0
  61. package/settings/src/index.ts +45 -0
  62. package/settings/src/runtime.ts +545 -0
  63. package/task-job/src/TaskJobPage.vue +120 -0
  64. package/task-job/src/contracts.ts +117 -0
  65. package/task-job/src/index.ts +2 -0
  66. package/task-job/src/runtime.ts +105 -0
  67. package/testing/src/index.ts +141 -0
@@ -0,0 +1,942 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ EmptyState,
4
+ ForbiddenState,
5
+ ModuleUnavailableState,
6
+ PageContent,
7
+ PageHeader,
8
+ PageToolbar,
9
+ SessionExpiredState,
10
+ } from '@peanut-admin/admin/shell'
11
+ import {
12
+ ElButton,
13
+ ElCheckbox,
14
+ ElDialog,
15
+ ElInput,
16
+ ElInputNumber,
17
+ } from 'element-plus'
18
+ import { computed, onMounted, ref } from 'vue'
19
+
20
+ import type { ReferenceCodeEntry } from './contracts'
21
+ import { useReferenceCodesRuntime } from './runtime'
22
+
23
+ const runtime = useReferenceCodesRuntime()
24
+ const state = runtime.state
25
+ const asOfInput = ref('')
26
+ const canManage = computed(() => runtime.canManage())
27
+ const hasPendingMutation = computed(() => state.pendingResources.size > 0)
28
+ const selectedSetValue = computed(() => state.selectedSet === null
29
+ ? ''
30
+ : `${state.selectedSet.moduleKey}\u001f${state.selectedSet.setKey}`)
31
+ const pageCount = computed(() => Math.max(1, Math.ceil(state.total / state.pageSize)))
32
+
33
+ const run = async (operation: () => Promise<void>): Promise<void> => {
34
+ try {
35
+ await operation()
36
+ } catch {
37
+ return
38
+ }
39
+ }
40
+
41
+ const loadSets = async (): Promise<void> => {
42
+ await run(runtime.loadSets)
43
+ asOfInput.value = state.asOf
44
+ }
45
+
46
+ const selectSet = async (value: unknown): Promise<void> => {
47
+ if (typeof value !== 'string' || value === '') return
48
+ const [moduleKey, setKey] = value.split('\u001f')
49
+ if (moduleKey === undefined || setKey === undefined) return
50
+ await run(() => runtime.selectSet(moduleKey, setKey))
51
+ }
52
+
53
+ const selectValue = (event: Event): string | null => {
54
+ const target = event.currentTarget
55
+ return target instanceof HTMLSelectElement ? target.value : null
56
+ }
57
+
58
+ const selectSetFromEvent = async (event: Event): Promise<void> => {
59
+ const value = selectValue(event)
60
+ if (value !== null) await selectSet(value)
61
+ }
62
+
63
+ const applyAsOf = async (): Promise<void> => {
64
+ await run(async () => {
65
+ await runtime.setAsOf(asOfInput.value)
66
+ asOfInput.value = state.asOf
67
+ })
68
+ }
69
+
70
+ const setEffectiveStatus = async (value: unknown): Promise<void> => {
71
+ if (value !== 'active' && value !== 'inactive' && value !== 'all') return
72
+ await run(() => runtime.setFilters(value, state.includeRetired))
73
+ }
74
+
75
+ const setIncludeRetired = async (value: unknown): Promise<void> => {
76
+ await run(() => runtime.setFilters(state.effectiveStatus, value === true))
77
+ }
78
+
79
+ const setPage = async (page: number): Promise<void> => {
80
+ await run(() => runtime.setPage(page))
81
+ }
82
+
83
+ const dateLabel = (value: string | null): string => value ?? 'none'
84
+ const effectiveLabel = (entry: ReferenceCodeEntry): string => entry.effective?.label ?? 'No effective version'
85
+ const statusLabel = (entry: ReferenceCodeEntry): string => entry.effective?.status ?? 'none'
86
+ const metadataLabel = (entry: ReferenceCodeEntry): string => JSON.stringify(entry.effective?.metadata ?? {})
87
+ const canAppend = (entry: ReferenceCodeEntry): boolean => (
88
+ canManage.value && entry.lifecycle === 'active' && entry.effective !== null && !runtime.isPending(entry.code)
89
+ )
90
+
91
+ const updateCreateStatus = (value: unknown): void => {
92
+ if (value === 'active' || value === 'inactive') runtime.updateCreateDraft({ status: value })
93
+ }
94
+
95
+ const updateCreateStatusFromEvent = (event: Event): void => updateCreateStatus(selectValue(event))
96
+
97
+ const updateAppendStatus = (value: unknown): void => {
98
+ if (value === 'active' || value === 'inactive') runtime.updateAppendDraft({ status: value })
99
+ }
100
+
101
+ const updateAppendStatusFromEvent = (event: Event): void => updateAppendStatus(selectValue(event))
102
+
103
+ const normalizedNullable = (value: string): string | null => value === '' ? null : value
104
+
105
+ onMounted(loadSets)
106
+ </script>
107
+
108
+ <template>
109
+ <PageContent class="reference-codes-page">
110
+ <PageHeader>
111
+ Reference codes
112
+ <template #actions>
113
+ <ElButton
114
+ aria-label="Reload reference codes"
115
+ :aria-disabled="hasPendingMutation"
116
+ :disabled="hasPendingMutation"
117
+ :loading="state.loading"
118
+ @click="state.selectedSet === null ? loadSets() : run(runtime.loadEntries)"
119
+ >
120
+ Reload
121
+ </ElButton>
122
+ <ElButton
123
+ aria-label="Create code"
124
+ type="primary"
125
+ :disabled="!canManage || state.selectedSet === null || hasPendingMutation"
126
+ @click="runtime.beginCreate"
127
+ >
128
+ Create code
129
+ </ElButton>
130
+ </template>
131
+ </PageHeader>
132
+
133
+ <PageToolbar
134
+ v-if="!state.errors.page"
135
+ label="Reference-code controls"
136
+ >
137
+ <div class="reference-controls">
138
+ <label for="reference-set">Owner and set</label>
139
+ <select
140
+ id="reference-set"
141
+ :value="selectedSetValue"
142
+ @change="selectSetFromEvent"
143
+ >
144
+ <option
145
+ disabled
146
+ value=""
147
+ >
148
+ Select an owner and set
149
+ </option>
150
+ <option
151
+ v-for="set in state.sets"
152
+ :key="`${set.moduleKey}/${set.setKey}`"
153
+ :value="`${set.moduleKey}\u001f${set.setKey}`"
154
+ >
155
+ {{ set.moduleKey }} / {{ set.name }}
156
+ </option>
157
+ </select>
158
+
159
+ <label for="reference-as-of">As of</label>
160
+ <ElInput
161
+ id="reference-as-of"
162
+ v-model="asOfInput"
163
+ placeholder="2026-07-20T00:00:00.000Z"
164
+ :disabled="state.selectedSet === null || hasPendingMutation"
165
+ />
166
+ <ElButton
167
+ :disabled="state.selectedSet === null || hasPendingMutation"
168
+ @click="applyAsOf"
169
+ >
170
+ Apply as-of
171
+ </ElButton>
172
+
173
+ <span id="reference-status-label">Effective status</span>
174
+ <div
175
+ class="reference-segmented"
176
+ aria-labelledby="reference-status-label"
177
+ role="group"
178
+ >
179
+ <ElButton
180
+ :aria-pressed="state.effectiveStatus === 'all'"
181
+ :disabled="state.selectedSet === null || hasPendingMutation"
182
+ :type="state.effectiveStatus === 'all' ? 'primary' : 'default'"
183
+ @click="setEffectiveStatus('all')"
184
+ >
185
+ All
186
+ </ElButton>
187
+ <ElButton
188
+ :aria-pressed="state.effectiveStatus === 'active'"
189
+ :disabled="state.selectedSet === null || hasPendingMutation"
190
+ :type="state.effectiveStatus === 'active' ? 'primary' : 'default'"
191
+ @click="setEffectiveStatus('active')"
192
+ >
193
+ Active
194
+ </ElButton>
195
+ <ElButton
196
+ :aria-pressed="state.effectiveStatus === 'inactive'"
197
+ :disabled="state.selectedSet === null || hasPendingMutation"
198
+ :type="state.effectiveStatus === 'inactive' ? 'primary' : 'default'"
199
+ @click="setEffectiveStatus('inactive')"
200
+ >
201
+ Inactive
202
+ </ElButton>
203
+ </div>
204
+
205
+ <ElCheckbox
206
+ class="retired-checkbox"
207
+ :disabled="state.selectedSet === null || hasPendingMutation"
208
+ :model-value="state.includeRetired"
209
+ @update:model-value="setIncludeRetired"
210
+ >
211
+ Include retired
212
+ </ElCheckbox>
213
+ </div>
214
+ </PageToolbar>
215
+
216
+ <SessionExpiredState
217
+ v-if="state.errors.page?.status === 401"
218
+ v-bind="state.errors.page.requestId === null ? {} : { requestId: state.errors.page.requestId }"
219
+ :message="state.errors.page.message"
220
+ />
221
+ <ForbiddenState
222
+ v-else-if="state.errors.page?.status === 403"
223
+ v-bind="state.errors.page.requestId === null ? {} : { requestId: state.errors.page.requestId }"
224
+ :message="state.errors.page.message"
225
+ />
226
+ <EmptyState
227
+ v-else-if="state.errors.page?.status === 404"
228
+ data-reference-codes-state="not-found"
229
+ title="Reference-code set not available"
230
+ v-bind="state.errors.page.requestId === null ? {} : { requestId: state.errors.page.requestId }"
231
+ :message="state.errors.page.message"
232
+ />
233
+ <ModuleUnavailableState
234
+ v-else-if="state.errors.page?.status === 503"
235
+ v-bind="state.errors.page.requestId === null ? {} : { requestId: state.errors.page.requestId }"
236
+ :message="state.errors.page.message"
237
+ @action="loadSets"
238
+ />
239
+ <section
240
+ v-else-if="state.errors.page"
241
+ class="reference-state"
242
+ data-reference-codes-state="error"
243
+ role="alert"
244
+ aria-live="polite"
245
+ >
246
+ <h2>Unable to load reference codes</h2>
247
+ <p>{{ state.errors.page.message }}</p>
248
+ <p v-if="state.errors.page.requestId !== null">
249
+ Request ID: {{ state.errors.page.requestId }}
250
+ </p>
251
+ <ElButton @click="state.selectedSet === null ? loadSets() : run(runtime.loadEntries)">
252
+ Retry
253
+ </ElButton>
254
+ </section>
255
+
256
+ <div
257
+ v-else-if="state.loading"
258
+ class="reference-state"
259
+ data-reference-codes-state="loading"
260
+ role="status"
261
+ aria-live="polite"
262
+ >
263
+ Loading reference codes...
264
+ </div>
265
+
266
+ <EmptyState
267
+ v-else-if="state.sets.length === 0"
268
+ data-reference-codes-state="empty-sets"
269
+ title="No reference-code sets"
270
+ message="No enabled owner Module declares a reference-code set."
271
+ />
272
+ <EmptyState
273
+ v-else-if="state.selectedSet === null"
274
+ data-reference-codes-state="select-set"
275
+ title="Select a reference-code set"
276
+ message="Choose an owner Module and set to view this Tenant's entries."
277
+ />
278
+ <EmptyState
279
+ v-else-if="state.entries.length === 0"
280
+ data-reference-codes-state="empty-entries"
281
+ title="No reference codes"
282
+ message="No entries match the fixed as-of instant and filters."
283
+ />
284
+
285
+ <section
286
+ v-else
287
+ class="reference-results"
288
+ aria-label="Reference-code entries"
289
+ >
290
+ <div class="reference-results__summary">
291
+ <span>{{ state.total }} entries</span>
292
+ <span>As of {{ state.asOf }}</span>
293
+ </div>
294
+ <div class="reference-table-wrap">
295
+ <table class="reference-table">
296
+ <thead>
297
+ <tr>
298
+ <th scope="col">
299
+ Code
300
+ </th>
301
+ <th scope="col">
302
+ Label
303
+ </th>
304
+ <th scope="col">
305
+ Status
306
+ </th>
307
+ <th scope="col">
308
+ Sort
309
+ </th>
310
+ <th scope="col">
311
+ Effective interval
312
+ </th>
313
+ <th scope="col">
314
+ Revision
315
+ </th>
316
+ <th scope="col">
317
+ Lifecycle
318
+ </th>
319
+ <th scope="col">
320
+ Metadata
321
+ </th>
322
+ <th scope="col">
323
+ Actions
324
+ </th>
325
+ </tr>
326
+ </thead>
327
+ <tbody>
328
+ <tr
329
+ v-for="entry in state.entries"
330
+ :key="entry.code"
331
+ :data-reference-code="entry.code"
332
+ >
333
+ <td data-label="Code">
334
+ <code>{{ entry.code }}</code>
335
+ </td>
336
+ <td data-label="Label">
337
+ {{ effectiveLabel(entry) }}
338
+ </td>
339
+ <td data-label="Status">
340
+ {{ statusLabel(entry) }}
341
+ </td>
342
+ <td data-label="Sort">
343
+ {{ entry.effective?.sortOrder ?? 'none' }}
344
+ </td>
345
+ <td data-label="Effective interval">
346
+ <span>{{ dateLabel(entry.effective?.effectiveAt ?? null) }}</span>
347
+ <span> to {{ dateLabel(entry.effective?.expiresAt ?? null) }}</span>
348
+ </td>
349
+ <td data-label="Revision">
350
+ {{ entry.revision }}
351
+ </td>
352
+ <td data-label="Lifecycle">
353
+ {{ entry.lifecycle }}
354
+ </td>
355
+ <td data-label="Metadata">
356
+ <code>{{ metadataLabel(entry) }}</code>
357
+ </td>
358
+ <td data-label="Actions">
359
+ <div class="reference-actions">
360
+ <ElButton
361
+ aria-label="Append version"
362
+ :disabled="!canAppend(entry)"
363
+ @click="runtime.beginAppend(entry)"
364
+ >
365
+ Append version
366
+ </ElButton>
367
+ <ElButton
368
+ aria-label="Retire"
369
+ :disabled="!canManage || entry.lifecycle === 'retired' || runtime.isPending(entry.code)"
370
+ @click="runtime.beginRetire(entry)"
371
+ >
372
+ Retire
373
+ </ElButton>
374
+ </div>
375
+ <div
376
+ v-if="state.stale[entry.code]"
377
+ class="reference-stale"
378
+ role="alert"
379
+ >
380
+ <span>{{ state.stale[entry.code]?.message }}</span>
381
+ <ElButton
382
+ :disabled="hasPendingMutation"
383
+ @click="run(() => runtime.reloadStale(entry.code))"
384
+ >
385
+ Reload stale record
386
+ </ElButton>
387
+ </div>
388
+ <p
389
+ v-if="state.errors[entry.code]"
390
+ class="reference-error"
391
+ role="alert"
392
+ >
393
+ {{ state.errors[entry.code]?.message }}
394
+ </p>
395
+ </td>
396
+ </tr>
397
+ </tbody>
398
+ </table>
399
+ </div>
400
+
401
+ <nav
402
+ class="reference-pagination"
403
+ aria-label="Reference-code pages"
404
+ >
405
+ <ElButton
406
+ aria-label="Previous page"
407
+ :disabled="state.page <= 1 || state.loading || hasPendingMutation"
408
+ @click="setPage(state.page - 1)"
409
+ >
410
+ Previous
411
+ </ElButton>
412
+ <span>Page {{ state.page }} of {{ pageCount }}</span>
413
+ <ElButton
414
+ aria-label="Next page"
415
+ :disabled="state.page >= pageCount || state.loading || hasPendingMutation"
416
+ @click="setPage(state.page + 1)"
417
+ >
418
+ Next
419
+ </ElButton>
420
+ </nav>
421
+ </section>
422
+
423
+ <ElDialog
424
+ class="reference-codes-dialog"
425
+ :model-value="state.createDraft !== null"
426
+ title="Create reference code"
427
+ width="min(640px, calc(100vw - 32px))"
428
+ :close-on-click-modal="false"
429
+ :transition="{ css: false }"
430
+ @close="runtime.cancelCreate"
431
+ >
432
+ <form
433
+ v-if="state.createDraft !== null"
434
+ class="reference-form"
435
+ @submit.prevent="run(runtime.create)"
436
+ >
437
+ <label for="reference-create-code">Code</label>
438
+ <ElInput
439
+ id="reference-create-code"
440
+ :model-value="state.createDraft.code"
441
+ autocomplete="off"
442
+ @update:model-value="runtime.updateCreateDraft({ code: $event })"
443
+ />
444
+ <label for="reference-create-label">Label</label>
445
+ <ElInput
446
+ id="reference-create-label"
447
+ :model-value="state.createDraft.label"
448
+ @update:model-value="runtime.updateCreateDraft({ label: $event })"
449
+ />
450
+ <label for="reference-create-metadata">Metadata JSON</label>
451
+ <ElInput
452
+ id="reference-create-metadata"
453
+ :model-value="state.createDraft.metadataText"
454
+ :rows="4"
455
+ type="textarea"
456
+ @update:model-value="runtime.updateCreateDraft({ metadataText: $event })"
457
+ />
458
+ <label for="reference-create-status">Status</label>
459
+ <select
460
+ id="reference-create-status"
461
+ :value="state.createDraft.status"
462
+ @change="updateCreateStatusFromEvent"
463
+ >
464
+ <option value="active">
465
+ active
466
+ </option>
467
+ <option value="inactive">
468
+ inactive
469
+ </option>
470
+ </select>
471
+ <label for="reference-create-sort">Sort order</label>
472
+ <ElInputNumber
473
+ id="reference-create-sort"
474
+ :max="1000000"
475
+ :min="-1000000"
476
+ :model-value="state.createDraft.sortOrder"
477
+ @update:model-value="$event !== undefined && runtime.updateCreateDraft({ sortOrder: $event })"
478
+ />
479
+ <label for="reference-create-effective">Effective at</label>
480
+ <ElInput
481
+ id="reference-create-effective"
482
+ :model-value="state.createDraft.effectiveAt"
483
+ @update:model-value="runtime.updateCreateDraft({ effectiveAt: $event })"
484
+ />
485
+ <label for="reference-create-expires">Expires at</label>
486
+ <ElInput
487
+ id="reference-create-expires"
488
+ :model-value="state.createDraft.expiresAt ?? ''"
489
+ placeholder="none"
490
+ @update:model-value="runtime.updateCreateDraft({ expiresAt: normalizedNullable($event) })"
491
+ />
492
+ <p
493
+ v-if="state.errors.create"
494
+ class="reference-error"
495
+ role="alert"
496
+ >
497
+ {{ state.errors.create.message }}
498
+ </p>
499
+ <div
500
+ v-if="state.stale[state.createDraft.code]"
501
+ class="reference-stale"
502
+ data-reference-create-reload
503
+ role="alert"
504
+ >
505
+ <span>{{ state.stale[state.createDraft.code]?.message }}</span>
506
+ <ElButton
507
+ :disabled="hasPendingMutation"
508
+ @click="run(runtime.loadEntries)"
509
+ >
510
+ Reload entries
511
+ </ElButton>
512
+ </div>
513
+ <div class="reference-form__actions">
514
+ <ElButton @click="runtime.cancelCreate">
515
+ Cancel
516
+ </ElButton>
517
+ <ElButton
518
+ native-type="submit"
519
+ type="primary"
520
+ :loading="runtime.isPending(state.createDraft.code)"
521
+ >
522
+ Create
523
+ </ElButton>
524
+ </div>
525
+ </form>
526
+ </ElDialog>
527
+
528
+ <ElDialog
529
+ class="reference-codes-dialog"
530
+ :model-value="state.appendDraft !== null"
531
+ title="Append reference-code version"
532
+ width="min(640px, calc(100vw - 32px))"
533
+ :close-on-click-modal="false"
534
+ :transition="{ css: false }"
535
+ @close="runtime.cancelAppend"
536
+ >
537
+ <form
538
+ v-if="state.appendDraft !== null"
539
+ class="reference-form"
540
+ @submit.prevent="run(runtime.appendVersion)"
541
+ >
542
+ <label for="reference-append-code">Code</label>
543
+ <ElInput
544
+ id="reference-append-code"
545
+ :model-value="state.appendDraft.code"
546
+ disabled
547
+ />
548
+ <label for="reference-append-label">Label</label>
549
+ <ElInput
550
+ id="reference-append-label"
551
+ :model-value="state.appendDraft.label"
552
+ @update:model-value="runtime.updateAppendDraft({ label: $event })"
553
+ />
554
+ <label for="reference-append-metadata">Metadata JSON</label>
555
+ <ElInput
556
+ id="reference-append-metadata"
557
+ :model-value="state.appendDraft.metadataText"
558
+ :rows="4"
559
+ type="textarea"
560
+ @update:model-value="runtime.updateAppendDraft({ metadataText: $event })"
561
+ />
562
+ <label for="reference-append-status">Status</label>
563
+ <select
564
+ id="reference-append-status"
565
+ :value="state.appendDraft.status"
566
+ @change="updateAppendStatusFromEvent"
567
+ >
568
+ <option value="active">
569
+ active
570
+ </option>
571
+ <option value="inactive">
572
+ inactive
573
+ </option>
574
+ </select>
575
+ <label for="reference-append-sort">Sort order</label>
576
+ <ElInputNumber
577
+ id="reference-append-sort"
578
+ :max="1000000"
579
+ :min="-1000000"
580
+ :model-value="state.appendDraft.sortOrder"
581
+ @update:model-value="$event !== undefined && runtime.updateAppendDraft({ sortOrder: $event })"
582
+ />
583
+ <label for="reference-append-effective">Effective at</label>
584
+ <ElInput
585
+ id="reference-append-effective"
586
+ :model-value="state.appendDraft.effectiveAt"
587
+ @update:model-value="runtime.updateAppendDraft({ effectiveAt: $event })"
588
+ />
589
+ <label for="reference-append-expires">Expires at</label>
590
+ <ElInput
591
+ id="reference-append-expires"
592
+ :model-value="state.appendDraft.expiresAt ?? ''"
593
+ placeholder="none"
594
+ @update:model-value="runtime.updateAppendDraft({ expiresAt: normalizedNullable($event) })"
595
+ />
596
+ <div
597
+ v-if="state.stale[state.appendDraft.code]"
598
+ class="reference-stale"
599
+ role="alert"
600
+ >
601
+ <span>{{ state.stale[state.appendDraft.code]?.message }}</span>
602
+ <ElButton
603
+ :disabled="hasPendingMutation"
604
+ @click="run(() => runtime.reloadStale(state.appendDraft!.code))"
605
+ >
606
+ Reload stale record
607
+ </ElButton>
608
+ </div>
609
+ <p
610
+ v-if="state.errors[state.appendDraft.code]"
611
+ class="reference-error"
612
+ role="alert"
613
+ >
614
+ {{ state.errors[state.appendDraft.code]?.message }}
615
+ </p>
616
+ <div class="reference-form__actions">
617
+ <ElButton @click="runtime.cancelAppend">
618
+ Cancel
619
+ </ElButton>
620
+ <ElButton
621
+ native-type="submit"
622
+ type="primary"
623
+ :loading="runtime.isPending(state.appendDraft.code)"
624
+ >
625
+ Append version
626
+ </ElButton>
627
+ </div>
628
+ </form>
629
+ </ElDialog>
630
+
631
+ <ElDialog
632
+ class="reference-codes-dialog"
633
+ :model-value="state.retireCode !== null"
634
+ title="Retire reference code"
635
+ width="min(520px, calc(100vw - 32px))"
636
+ :close-on-click-modal="false"
637
+ :transition="{ css: false }"
638
+ @close="runtime.cancelRetire"
639
+ >
640
+ <div
641
+ v-if="state.retireCode !== null"
642
+ class="reference-retire"
643
+ >
644
+ <p>
645
+ Retiring <code>{{ state.retireCode }}</code> is permanent. This identity cannot be reused or reactivated.
646
+ </p>
647
+ <p
648
+ v-if="state.errors[state.retireCode]"
649
+ class="reference-error"
650
+ role="alert"
651
+ >
652
+ {{ state.errors[state.retireCode]?.message }}
653
+ </p>
654
+ <div
655
+ v-if="state.stale[state.retireCode]"
656
+ class="reference-stale"
657
+ role="alert"
658
+ >
659
+ <span>{{ state.stale[state.retireCode]?.message }}</span>
660
+ <ElButton
661
+ :disabled="hasPendingMutation"
662
+ @click="run(() => runtime.reloadStale(state.retireCode!))"
663
+ >
664
+ Reload stale record
665
+ </ElButton>
666
+ </div>
667
+ <div class="reference-form__actions">
668
+ <ElButton @click="runtime.cancelRetire">
669
+ Cancel
670
+ </ElButton>
671
+ <ElButton
672
+ type="danger"
673
+ :loading="runtime.isPending(state.retireCode)"
674
+ @click="run(runtime.retire)"
675
+ >
676
+ Retire permanently
677
+ </ElButton>
678
+ </div>
679
+ </div>
680
+ </ElDialog>
681
+ </PageContent>
682
+ </template>
683
+
684
+ <style scoped>
685
+ .retired-checkbox {
686
+ position: relative;
687
+ }
688
+
689
+ .retired-checkbox :deep(.el-checkbox__original) {
690
+ inset: 0;
691
+ width: 100%;
692
+ height: 100%;
693
+ z-index: 2;
694
+ cursor: pointer;
695
+ }
696
+
697
+ .retired-checkbox.is-disabled :deep(.el-checkbox__original) {
698
+ cursor: not-allowed;
699
+ }
700
+
701
+ .reference-codes-page {
702
+ min-width: 0;
703
+ }
704
+
705
+ .reference-controls {
706
+ display: grid;
707
+ grid-template-columns: auto minmax(220px, 1fr) auto minmax(250px, 1fr) auto;
708
+ align-items: center;
709
+ gap: 10px;
710
+ min-width: 0;
711
+ width: 100%;
712
+ }
713
+
714
+ .reference-controls :deep(.el-select),
715
+ .reference-controls :deep(.el-input),
716
+ .reference-controls select {
717
+ min-width: 0;
718
+ width: 100%;
719
+ }
720
+
721
+ .reference-controls select,
722
+ .reference-form select {
723
+ min-height: 32px;
724
+ padding: 0 10px;
725
+ border: 1px solid var(--el-border-color);
726
+ border-radius: 4px;
727
+ background: var(--el-bg-color);
728
+ color: var(--el-text-color-primary);
729
+ }
730
+
731
+ .reference-segmented {
732
+ display: inline-flex;
733
+ }
734
+
735
+ .reference-segmented :deep(.el-button) {
736
+ margin-left: 0;
737
+ border-radius: 0;
738
+ }
739
+
740
+ .reference-segmented :deep(.el-button:first-child) {
741
+ border-radius: 4px 0 0 4px;
742
+ }
743
+
744
+ .reference-segmented :deep(.el-button:last-child) {
745
+ border-radius: 0 4px 4px 0;
746
+ }
747
+
748
+ .reference-state {
749
+ padding: 18px 0;
750
+ }
751
+
752
+ .reference-state h2,
753
+ .reference-state p {
754
+ margin: 0 0 8px;
755
+ letter-spacing: 0;
756
+ overflow-wrap: anywhere;
757
+ }
758
+
759
+ .reference-results {
760
+ min-width: 0;
761
+ }
762
+
763
+ .reference-results__summary {
764
+ display: flex;
765
+ flex-wrap: wrap;
766
+ justify-content: space-between;
767
+ gap: 8px 16px;
768
+ margin: 16px 0 10px;
769
+ color: var(--el-text-color-secondary);
770
+ font-size: 13px;
771
+ }
772
+
773
+ .reference-table-wrap {
774
+ max-width: 100%;
775
+ overflow-x: auto;
776
+ }
777
+
778
+ .reference-table {
779
+ width: 100%;
780
+ min-width: 1080px;
781
+ border-collapse: collapse;
782
+ table-layout: fixed;
783
+ }
784
+
785
+ .reference-table th,
786
+ .reference-table td {
787
+ padding: 12px 10px;
788
+ border-bottom: 1px solid var(--el-border-color-lighter);
789
+ text-align: left;
790
+ vertical-align: top;
791
+ overflow-wrap: anywhere;
792
+ }
793
+
794
+ .reference-table th {
795
+ color: var(--el-text-color-secondary);
796
+ font-size: 12px;
797
+ font-weight: 600;
798
+ }
799
+
800
+ .reference-table code,
801
+ .reference-retire code {
802
+ white-space: normal;
803
+ overflow-wrap: anywhere;
804
+ }
805
+
806
+ .reference-actions,
807
+ .reference-form__actions,
808
+ .reference-pagination {
809
+ display: flex;
810
+ flex-wrap: wrap;
811
+ gap: 8px;
812
+ }
813
+
814
+ .reference-stale,
815
+ .reference-error {
816
+ margin: 8px 0 0;
817
+ color: var(--el-color-danger);
818
+ }
819
+
820
+ .reference-stale {
821
+ display: grid;
822
+ gap: 8px;
823
+ padding: 10px;
824
+ border: 1px solid var(--el-color-danger-light-5);
825
+ border-radius: 6px;
826
+ background: var(--el-color-danger-light-9);
827
+ }
828
+
829
+ .reference-pagination {
830
+ align-items: center;
831
+ justify-content: flex-end;
832
+ margin-top: 16px;
833
+ }
834
+
835
+ .reference-form {
836
+ display: grid;
837
+ grid-template-columns: minmax(110px, 150px) minmax(0, 1fr);
838
+ align-items: center;
839
+ gap: 12px;
840
+ min-width: 0;
841
+ }
842
+
843
+ .reference-form :deep(.el-select),
844
+ .reference-form :deep(.el-input),
845
+ .reference-form :deep(.el-input-number),
846
+ .reference-form select {
847
+ width: 100%;
848
+ max-width: 100%;
849
+ }
850
+
851
+ .reference-form .reference-error,
852
+ .reference-form .reference-stale,
853
+ .reference-form__actions {
854
+ grid-column: 1 / -1;
855
+ }
856
+
857
+ .reference-form__actions {
858
+ justify-content: flex-end;
859
+ margin-top: 4px;
860
+ }
861
+
862
+ .reference-retire p {
863
+ margin: 0 0 14px;
864
+ letter-spacing: 0;
865
+ overflow-wrap: anywhere;
866
+ }
867
+
868
+ :global(.reference-codes-dialog) {
869
+ display: flex;
870
+ max-height: calc(100dvh - 32px);
871
+ flex-direction: column;
872
+ }
873
+
874
+ :global(.reference-codes-dialog .el-dialog__body) {
875
+ min-height: 0;
876
+ overflow-y: auto;
877
+ }
878
+
879
+ @media (max-width: 860px) {
880
+ .reference-controls {
881
+ grid-template-columns: minmax(0, 1fr);
882
+ align-items: stretch;
883
+ }
884
+
885
+ .reference-table {
886
+ min-width: 0;
887
+ }
888
+
889
+ .reference-table thead {
890
+ display: none;
891
+ }
892
+
893
+ .reference-table,
894
+ .reference-table tbody,
895
+ .reference-table tr,
896
+ .reference-table td {
897
+ display: block;
898
+ width: 100%;
899
+ }
900
+
901
+ .reference-table tr {
902
+ margin-bottom: 12px;
903
+ padding: 8px 12px;
904
+ border: 1px solid var(--el-border-color-lighter);
905
+ border-radius: 6px;
906
+ }
907
+
908
+ .reference-table td {
909
+ display: grid;
910
+ grid-template-columns: minmax(100px, 35%) minmax(0, 1fr);
911
+ gap: 10px;
912
+ padding: 8px 0;
913
+ }
914
+
915
+ .reference-table td::before {
916
+ content: attr(data-label);
917
+ color: var(--el-text-color-secondary);
918
+ font-size: 12px;
919
+ font-weight: 600;
920
+ }
921
+
922
+ .reference-pagination {
923
+ justify-content: space-between;
924
+ }
925
+ }
926
+
927
+ @media (max-width: 560px) {
928
+ .reference-form {
929
+ grid-template-columns: minmax(0, 1fr);
930
+ }
931
+
932
+ .reference-form .reference-error,
933
+ .reference-form .reference-stale,
934
+ .reference-form__actions {
935
+ grid-column: 1;
936
+ }
937
+
938
+ .reference-table td {
939
+ grid-template-columns: minmax(84px, 32%) minmax(0, 1fr);
940
+ }
941
+ }
942
+ </style>