@7365admin1/layer-common 4.97.1-staging.591 → 4.97.1-staging.592

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.
@@ -68,6 +68,8 @@
68
68
  -->
69
69
  <!-- "Admin · St. Regis Residences": names are unique per site (2026-10-01) -->
70
70
  <template #item.name="{ item }">{{ roleLabel(item) }}</template>
71
+ <!-- "App" column: plain app name, not the stored code (security_agency) -->
72
+ <template #item.type="{ item }">{{ roleAppLabel(item.type) }}</template>
71
73
  <template #item.scope="{ item }">
72
74
  <StatusChip
73
75
  :dot="false"
@@ -214,7 +216,7 @@
214
216
 
215
217
  <script setup lang="ts">
216
218
  // Relative import: this layer's `utils/` is not auto-imported into consumers.
217
- import { roleLabel, safeReturnTo, showSiteField, sitePickerRoleLabel } from "../utils/role";
219
+ import { roleAppLabel, roleLabel, safeReturnTo, showSiteField, sitePickerRoleLabel } from "../utils/role";
218
220
  import { holdsPermission } from "../utils/permission-spellings";
219
221
  import { countRealPermissions } from "../utils/security-catalogue";
220
222
  import {
@@ -163,7 +163,11 @@
163
163
  "
164
164
  value="toggle-status"
165
165
  class="text-body-2"
166
- @click="togglePlanStatus(item)"
166
+ @click="
167
+ item.status === 'active'
168
+ ? (planToDeactivate = item)
169
+ : togglePlanStatus(item)
170
+ "
167
171
  />
168
172
  </v-list>
169
173
  </v-card>
@@ -197,6 +201,32 @@
197
201
  />
198
202
  </v-dialog>
199
203
 
204
+ <ConfirmDialog
205
+ :model-value="!!planToDeactivate"
206
+ @update:model-value="(v: boolean) => { if (!v) planToDeactivate = null; }"
207
+ >
208
+ <template #title>Deactivate plan</template>
209
+ <template #description>
210
+ Deactivate {{ planToDeactivate?.name || "this plan" }}? You can activate
211
+ it again from the Deactivated tab.
212
+ </template>
213
+ <template #footer>
214
+ <v-spacer />
215
+ <v-btn
216
+ variant="outlined"
217
+ :disabled="isDeactivating"
218
+ @click="planToDeactivate = null"
219
+ >Cancel</v-btn
220
+ >
221
+ <v-btn
222
+ color="error"
223
+ variant="flat"
224
+ :loading="isDeactivating"
225
+ @click="confirmDeactivate"
226
+ >Deactivate</v-btn
227
+ >
228
+ </template>
229
+ </ConfirmDialog>
200
230
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
201
231
  </v-container>
202
232
  </template>
@@ -417,6 +447,20 @@ async function handleSubmit(payload: PlanFormState) {
417
447
  }
418
448
  }
419
449
 
450
+ // Deactivate asks first; Activate stays one tap.
451
+ const planToDeactivate = ref<SubscriptionPlan | null>(null);
452
+ const isDeactivating = ref(false);
453
+ async function confirmDeactivate() {
454
+ if (!planToDeactivate.value) return;
455
+ isDeactivating.value = true;
456
+ try {
457
+ await togglePlanStatus(planToDeactivate.value);
458
+ } finally {
459
+ isDeactivating.value = false;
460
+ planToDeactivate.value = null;
461
+ }
462
+ }
463
+
420
464
  async function togglePlanStatus(plan: SubscriptionPlan) {
421
465
  const nextStatus = plan.status === "active" ? "deactive" : "active";
422
466
 
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.97.1-staging.591",
5
+ "version": "4.97.1-staging.592",
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.",
package/utils/role.ts CHANGED
@@ -158,3 +158,27 @@ export function siteAccessLabel(item: { limitedTo?: unknown } | null | undefined
158
158
  const names = limitedSites(item).map((l) => l.siteName || "a site");
159
159
  return names.length ? `Limited to: ${names.join(", ")}` : "All sites";
160
160
  }
161
+
162
+ /**
163
+ * A role's `type` is the app it belongs to, stored as a code
164
+ * (`security_agency`). The roles table shows the app's plain name instead.
165
+ * An unknown code is shown with its underscores as spaces, never dropped.
166
+ */
167
+ const ROLE_APP_LABELS: Readonly<Record<string, string>> = Object.freeze({
168
+ admin: "Super Admin",
169
+ organization: "Organization",
170
+ property_management_agency: "Property Management",
171
+ security_agency: "Security",
172
+ cleaning_services: "Cleaning",
173
+ mechanical_electrical_services: "Mechanical & Electrical",
174
+ pest_control_services: "Pest Control",
175
+ pool_maintenance_services: "Pool Maintenance",
176
+ landscaping_services: "Landscape",
177
+ resident: "Resident",
178
+ });
179
+
180
+ export function roleAppLabel(type: unknown): string {
181
+ const code = String(type ?? "").trim();
182
+ if (!code) return "";
183
+ return ROLE_APP_LABELS[code] ?? code.replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
184
+ }