@7365admin1/layer-common 4.2.5-staging.270 → 4.2.5-staging.271

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.
@@ -433,6 +433,13 @@ const prop = defineProps({
433
433
  type: Object as PropType<Partial<TVehicle> | null>,
434
434
  default: null,
435
435
  },
436
+ // Id -> name for this record's block/level/unit, taken from the list row,
437
+ // which the API already resolved. Lets the dialog name a value its own
438
+ // dropdown cannot offer, instead of falling through to the raw id.
439
+ nameHints: {
440
+ type: Object as PropType<Record<string, string>>,
441
+ default: () => ({}),
442
+ },
436
443
  plateNumberId: {
437
444
  type: String,
438
445
  default: "",
@@ -538,14 +545,23 @@ const shouldShowField = (fieldKey: string): boolean => {
538
545
  * that list, which is how the Season Pass edit dialog came to show
539
546
  * `6a31fd37917fb18ef1a21a0a` in all three fields instead of names.
540
547
  *
541
- * Two different causes end in the same place, and neither is fixable in the
542
- * repository: the site list has simply not arrived yet, and - measured on
543
- * staging - a stored unit id that points at a building unit which has since
544
- * been DELETED, so it can NEVER appear in the list however the row is fetched.
548
+ * A Mongo id is never shown to a person. The name is available: the vehicle
549
+ * LIST resolves it server-side (`getVehicles` $lookup's buildings /
550
+ * building-levels / building-units) while the edit dialog's own fetch
551
+ * (`getSpecificVehicleById`, a plain findOne) returns raw ids. `VehicleManagement`
552
+ * therefore hands the already-resolved names down as `nameHints`, an id -> name
553
+ * map taken from the list row - no extra request, no change to what is stored.
554
+ *
555
+ * Three cases, in order:
556
+ * - the list has not arrived yet -> "Loading unit..."
557
+ * - a name is known but not in the list -> "Tower Ten (no longer selectable)"
558
+ * - no name can be found at all -> plain sentence, still no id
545
559
  *
546
- * So the fix is here, at the display layer: an id with no name gets an explicit
547
- * item saying why there is no name. A Mongo id is not a name, and a blank field
548
- * with no explanation is not better than one.
560
+ * Measured on staging, both non-loading cases are real. A block drops out of
561
+ * the picker once it has no ACTIVE units left (`getBuildingWithUnitsList` lists
562
+ * only buildings that still have units), so "Tower Ten" is a live building the
563
+ * form cannot offer; and a stored unit id can point at a building unit that has
564
+ * since been deleted, which no name lookup can answer.
549
565
  *
550
566
  * The stored value is left untouched - the field still submits the same id, so
551
567
  * saving an unrelated edit does not silently rewrite someone's unit.
@@ -554,29 +570,47 @@ function withUnresolved(
554
570
  items: TDefaultOptionObj[],
555
571
  value: unknown,
556
572
  pending: boolean,
557
- noun: string
573
+ noun: string,
574
+ nameHints: Record<string, string>
558
575
  ): TDefaultOptionObj[] {
559
- const id =
560
- typeof value === "string" ? value : ((value as any)?._id ?? "");
576
+ const id = idOf(value);
561
577
  if (!id || items.some((item) => item.value === id)) return items;
562
578
 
579
+ const name = nameHints[id];
580
+
581
+ // The id belongs in a log, not on screen. Support needs it; the property
582
+ // manager does not.
583
+ if (!pending && !name) {
584
+ console.warn(`[VehicleForm] no name resolved for ${noun} id ${id}`);
585
+ }
586
+
563
587
  return [
564
588
  {
565
589
  title: pending
566
- ? `Loading ${noun}…`
567
- : `This ${noun} is no longer on the site list (${id})`,
590
+ ? `Loading ${noun}...`
591
+ : name
592
+ ? `${name} (no longer selectable)`
593
+ : `This ${noun} is no longer on the site list`,
568
594
  value: id,
569
595
  },
570
596
  ...items,
571
597
  ];
572
598
  }
573
599
 
600
+ /** Block/level/unit arrive either as a raw id string or as a `{_id, name}` object. */
601
+ function idOf(value: unknown): string {
602
+ return typeof value === "string"
603
+ ? value
604
+ : String((value as any)?._id ?? "");
605
+ }
606
+
574
607
  const blockItems = computed(() =>
575
608
  withUnresolved(
576
609
  blocksArray.value,
577
610
  vehicle.block,
578
611
  blockListDataPending.value,
579
- "block"
612
+ "block",
613
+ prop.nameHints
580
614
  )
581
615
  );
582
616
 
@@ -585,7 +619,8 @@ const levelItems = computed(() =>
585
619
  levelsArray.value,
586
620
  vehicle.level,
587
621
  levelListDataPending.value,
588
- "level"
622
+ "level",
623
+ prop.nameHints
589
624
  )
590
625
  );
591
626
 
@@ -594,7 +629,8 @@ const unitItems = computed(() =>
594
629
  unitsArray.value,
595
630
  vehicle.unit,
596
631
  unitListDataPending.value,
597
- "unit"
632
+ "unit",
633
+ prop.nameHints
598
634
  )
599
635
  );
600
636
 
@@ -602,8 +638,11 @@ const searchUnitName = computed(() => {
602
638
  // Same rule as the fields: never put a raw id in front of a person. The
603
639
  // dialog title used to fall through to `vehicle.unit`, so a deleted unit
604
640
  // titled the panel with its ObjectId.
641
+ const id = idOf(vehicle.unit);
605
642
  return (
606
- unitsArray.value.find((u) => u.value === vehicle.unit)?.title || "-"
643
+ unitsArray.value.find((u) => u.value === id)?.title ||
644
+ prop.nameHints[id] ||
645
+ "-"
607
646
  );
608
647
  });
609
648
 
@@ -85,13 +85,13 @@
85
85
 
86
86
  <v-dialog v-model="dialog.createVehicle" v-if="vehicleType" width="450" persistent>
87
87
  <VehicleForm :type="vehicleType" :mode="mode" :vehicle-data="selectedVehicleObject"
88
- :plate-number-id="selectedPlateNumberId" @back="handleBackToSelection" @done="handleAddVehicleComplete"
88
+ :plate-number-id="selectedPlateNumberId" :name-hints="nameHints" @back="handleBackToSelection" @done="handleAddVehicleComplete"
89
89
  :org="org" :site="props.site" @close:all="handleCloseAll" />
90
90
  </v-dialog>
91
91
 
92
92
  <v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
93
93
  <VehicleForm :type="vehicleType" :mode="mode" :vehicle-data="selectedPlateNumberObject"
94
- :plate-number-id="selectedPlateNumberId" @back="handleBackToSelection" @done="handleAddVehicleComplete"
94
+ :plate-number-id="selectedPlateNumberId" :name-hints="nameHints" @back="handleBackToSelection" @done="handleAddVehicleComplete"
95
95
  :org="org" :site="props.site" @close:all="handleCloseAll" @close="handleCloseAll" />
96
96
  </v-dialog>
97
97
 
@@ -371,6 +371,34 @@ const selectedVehicleObject = computed(() => {
371
371
  return items.value.find(item => item?._id === selectedVehicleId.value) || {}
372
372
  })
373
373
 
374
+ /**
375
+ * The edit dialog fetches its record with `getSpecificVehicleById`, a plain
376
+ * findOne, so block/level/unit come back as raw ObjectIds. The LIST does the
377
+ * work already - `getVehicles` $lookup's buildings, building-levels and
378
+ * building-units, which is why this table can print "Tower Ten" for the same
379
+ * record. Hand those resolved names down so the dialog never has to fall back
380
+ * to an id it cannot name.
381
+ *
382
+ * Id -> name, not the objects themselves: the form binds ids, and passing a
383
+ * `{_id, name}` object into its autocompletes is what broke it before.
384
+ */
385
+ const nameHints = computed<Record<string, string>>(() => {
386
+ const plateId = selectedPlateNumberId.value;
387
+ const row: any =
388
+ items.value.find(
389
+ (r: any) =>
390
+ r?._id === plateId ||
391
+ (Array.isArray(r?.plates) && r.plates.some((p: any) => p?._id === plateId))
392
+ ) || selectedVehicleObject.value;
393
+
394
+ const hints: Record<string, string> = {};
395
+ for (const key of ["block", "level", "unit"]) {
396
+ const field = row?.[key];
397
+ if (field?._id && field?.name) hints[String(field._id)] = String(field.name);
398
+ }
399
+ return hints;
400
+ })
401
+
374
402
  const { data: getVehiclesReq, refresh: getVehiclesRefresh, pending: getVehiclesPending } =
375
403
  await useLazyAsyncData(
376
404
  `get-all-vehicles-${props.site}`,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.2.5-staging.270",
5
+ "version": "4.2.5-staging.271",
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.",