@7365admin1/layer-common 3.2.2-staging.182 → 3.2.2-staging.183
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.
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
<v-icon size="13">mdi-close</v-icon>
|
|
337
337
|
</v-btn>
|
|
338
338
|
</div>
|
|
339
|
-
<v-btn size="x-small" variant="text" color="primary" class="text-none" prepend-icon="mdi-plus" @click="block
|
|
339
|
+
<v-btn size="x-small" variant="text" color="primary" class="text-none" prepend-icon="mdi-plus" @click="addCheckboxItem(block)">
|
|
340
340
|
Add checkbox
|
|
341
341
|
</v-btn>
|
|
342
342
|
</template>
|
|
@@ -351,7 +351,7 @@
|
|
|
351
351
|
density="compact"
|
|
352
352
|
variant="outlined"
|
|
353
353
|
hide-details
|
|
354
|
-
@update:model-value="block
|
|
354
|
+
@update:model-value="onSignatureLabelChange(block, $event)"
|
|
355
355
|
/>
|
|
356
356
|
</v-col>
|
|
357
357
|
<v-col cols="12">
|
|
@@ -373,6 +373,15 @@
|
|
|
373
373
|
</div>
|
|
374
374
|
</div>
|
|
375
375
|
|
|
376
|
+
<!-- At least one fillable block is required — a form with only
|
|
377
|
+
headings/paragraphs/dividers has nothing for a resident to
|
|
378
|
+
actually submit. -->
|
|
379
|
+
<div v-if="blocks.length > 0 && !hasFillableBlock" class="px-4 pb-3">
|
|
380
|
+
<span class="text-caption text-error">
|
|
381
|
+
Add at least one Field, Checkbox Group, or Signature block before saving.
|
|
382
|
+
</span>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
376
385
|
<!-- Error -->
|
|
377
386
|
<div v-if="message" class="px-4 pb-3">
|
|
378
387
|
<span class="text-caption text-error">{{ message }}</span>
|
|
@@ -520,6 +529,39 @@ function labelToKey(label: string): string {
|
|
|
520
529
|
);
|
|
521
530
|
}
|
|
522
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Every field key currently in use across the form, so a newly-generated key
|
|
534
|
+
* never collides with a sibling's — otherwise two fields left blank (or given
|
|
535
|
+
* the same label) end up bound to the same value in the resident app, and
|
|
536
|
+
* filling one silently fills the other.
|
|
537
|
+
*/
|
|
538
|
+
function collectUsedKeys(excludeRef?: any): Set<string> {
|
|
539
|
+
const keys = new Set<string>();
|
|
540
|
+
for (const b of blocks.value) {
|
|
541
|
+
if (b.type === "field" && b.field && b.field !== excludeRef && b.field.key) {
|
|
542
|
+
keys.add(b.field.key);
|
|
543
|
+
}
|
|
544
|
+
if (b.type === "checkbox-group" && Array.isArray(b.items)) {
|
|
545
|
+
for (const item of b.items) {
|
|
546
|
+
if (item !== excludeRef && item?.key) keys.add(item.key);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (b.type === "signature-row" && b !== excludeRef && b.key) {
|
|
550
|
+
keys.add(b.key);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return keys;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** `base` (or "field" when blank) made unique against `used` via a numeric suffix. */
|
|
557
|
+
function uniqueKey(base: string, used: Set<string>): string {
|
|
558
|
+
const root = base || "field";
|
|
559
|
+
if (!used.has(root)) return root;
|
|
560
|
+
let i = 2;
|
|
561
|
+
while (used.has(`${root}${i}`)) i++;
|
|
562
|
+
return `${root}${i}`;
|
|
563
|
+
}
|
|
564
|
+
|
|
523
565
|
const fieldTypeOptions = [
|
|
524
566
|
{ title: "Text", value: "text" },
|
|
525
567
|
{ title: "Textarea (multi-line)", value: "textarea" },
|
|
@@ -603,18 +645,26 @@ function addBlock(type: string) {
|
|
|
603
645
|
case "divider":
|
|
604
646
|
b = { _uid: nextUid(), type: "divider" };
|
|
605
647
|
break;
|
|
606
|
-
case "field":
|
|
607
|
-
|
|
648
|
+
case "field": {
|
|
649
|
+
const uid = nextUid();
|
|
650
|
+
b = { _uid: uid, type: "field", field: { key: uniqueKey(`field${uid}`, collectUsedKeys()), label: "", fieldType: "text", required: false } };
|
|
608
651
|
break;
|
|
609
|
-
|
|
610
|
-
|
|
652
|
+
}
|
|
653
|
+
case "field-select": {
|
|
654
|
+
const uid = nextUid();
|
|
655
|
+
b = { _uid: uid, type: "field", field: { key: uniqueKey(`field${uid}`, collectUsedKeys()), label: "", fieldType: "select", required: false, options: [] } };
|
|
611
656
|
break;
|
|
612
|
-
|
|
613
|
-
|
|
657
|
+
}
|
|
658
|
+
case "checkbox-group": {
|
|
659
|
+
const uid = nextUid();
|
|
660
|
+
b = { _uid: uid, type: "checkbox-group", title: "", single: false, items: [{ key: uniqueKey(`item${uid}`, collectUsedKeys()), label: "" }] };
|
|
614
661
|
break;
|
|
615
|
-
|
|
616
|
-
|
|
662
|
+
}
|
|
663
|
+
case "signature-row": {
|
|
664
|
+
const uid = nextUid();
|
|
665
|
+
b = { _uid: uid, type: "signature-row", label: "", key: uniqueKey(`signature${uid}`, collectUsedKeys()) };
|
|
617
666
|
break;
|
|
667
|
+
}
|
|
618
668
|
default:
|
|
619
669
|
return;
|
|
620
670
|
}
|
|
@@ -624,7 +674,9 @@ function addBlock(type: string) {
|
|
|
624
674
|
|
|
625
675
|
// Auto-generate key from label (field blocks)
|
|
626
676
|
function onLabelChange(block: AnyBlock, newLabel: string) {
|
|
627
|
-
if (!block._keyEdited)
|
|
677
|
+
if (!block._keyEdited) {
|
|
678
|
+
block.field.key = uniqueKey(labelToKey(newLabel), collectUsedKeys(block.field));
|
|
679
|
+
}
|
|
628
680
|
}
|
|
629
681
|
|
|
630
682
|
function onFieldTypeChange(block: AnyBlock, newType: string) {
|
|
@@ -633,7 +685,16 @@ function onFieldTypeChange(block: AnyBlock, newType: string) {
|
|
|
633
685
|
|
|
634
686
|
// Auto-generate key from label (checkbox items)
|
|
635
687
|
function onCheckboxLabelChange(item: AnyBlock, newLabel: string) {
|
|
636
|
-
if (!item._keyEdited) item.key = labelToKey(newLabel);
|
|
688
|
+
if (!item._keyEdited) item.key = uniqueKey(labelToKey(newLabel), collectUsedKeys(item));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function addCheckboxItem(block: AnyBlock) {
|
|
692
|
+
block.items.push({ key: uniqueKey(`item${nextUid()}`, collectUsedKeys()), label: "" });
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Auto-generate key from label (signature-row)
|
|
696
|
+
function onSignatureLabelChange(block: AnyBlock, newLabel: string) {
|
|
697
|
+
block.key = uniqueKey(labelToKey(newLabel), collectUsedKeys(block));
|
|
637
698
|
}
|
|
638
699
|
|
|
639
700
|
// ── Template loading ──────────────────────────────────────────────────────────
|
|
@@ -650,10 +711,18 @@ function loadTemplate(name: string) {
|
|
|
650
711
|
}
|
|
651
712
|
|
|
652
713
|
// ── Computed ──────────────────────────────────────────────────────────────────
|
|
714
|
+
// Only these block types actually collect something from the resident — a
|
|
715
|
+
// form built entirely out of headings/paragraphs/dividers has nothing to
|
|
716
|
+
// submit, so it isn't a usable form yet.
|
|
717
|
+
const FILLABLE_BLOCK_TYPES = ["field", "checkbox-group", "signature-row"];
|
|
718
|
+
const hasFillableBlock = computed(() =>
|
|
719
|
+
blocks.value.some((b) => FILLABLE_BLOCK_TYPES.includes(b.type)),
|
|
720
|
+
);
|
|
721
|
+
|
|
653
722
|
const canSubmit = computed(() => {
|
|
654
723
|
const nameOk = prop.mode === "edit" || !!formName.value.trim();
|
|
655
724
|
const payOk = !requiresPayment.value || paymentAmount.value > 0;
|
|
656
|
-
return nameOk && payOk && !loading.value;
|
|
725
|
+
return nameOk && payOk && hasFillableBlock.value && !loading.value;
|
|
657
726
|
});
|
|
658
727
|
|
|
659
728
|
// ── Payload builder ───────────────────────────────────────────────────────────
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
<slot name="title-actions" />
|
|
10
10
|
</v-card-title>
|
|
11
11
|
|
|
12
|
-
<!-- Submission metadata bar (readonly only)
|
|
12
|
+
<!-- Submission metadata bar (readonly only). Shown for an actual
|
|
13
|
+
submission (submissionStatus) OR a pure template preview that
|
|
14
|
+
requires payment — a preview has no submission yet, but should
|
|
15
|
+
still tell the admin the form carries a fee. -->
|
|
13
16
|
<div
|
|
14
|
-
v-if="readonly && (submissionStatus)"
|
|
17
|
+
v-if="readonly && (submissionStatus || requiresPayment)"
|
|
15
18
|
class="online-form-fill__meta px-6 py-2 d-flex align-center ga-4"
|
|
16
19
|
>
|
|
17
20
|
<!-- `StatusChip` IS the design's status pill: it maps the WORD to a tone
|
|
@@ -35,9 +38,16 @@
|
|
|
35
38
|
</span>
|
|
36
39
|
<template v-if="requiresPayment">
|
|
37
40
|
<!-- The payment pair is not a status WORD the shared map knows, so the
|
|
38
|
-
tone is given directly - the same pill, the same measured pair.
|
|
41
|
+
tone is given directly - the same pill, the same measured pair.
|
|
42
|
+
A preview has no submission to be paid/unpaid yet, so it shows
|
|
43
|
+
the configured fee instead of a status that doesn't apply. -->
|
|
39
44
|
<StatusChip
|
|
40
|
-
v-if="
|
|
45
|
+
v-if="!submissionStatus"
|
|
46
|
+
tone="info"
|
|
47
|
+
:label="`Requires Payment: $${paymentAmount}`"
|
|
48
|
+
/>
|
|
49
|
+
<StatusChip
|
|
50
|
+
v-else-if="initialManagementValues?.paymentStatus"
|
|
41
51
|
tone="ok"
|
|
42
52
|
:label="`Payment ${initialManagementValues.paymentStatus}`"
|
|
43
53
|
/>
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "3.2.2-staging.
|
|
5
|
+
"version": "3.2.2-staging.183",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|