@7365admin1/layer-common 3.0.15 → 3.0.17

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.
@@ -27,6 +27,15 @@
27
27
  size="x-small"
28
28
  class="text-capitalize"
29
29
  >{{ submissionStatus }}</v-chip>
30
+ <span v-if="(initialValues as any).block" class="text-caption text-medium-emphasis">
31
+ Block: <strong>{{ (initialValues as any).block }}</strong>
32
+ </span>
33
+ <span v-if="(initialValues as any).level" class="text-caption text-medium-emphasis">
34
+ Level: <strong>{{ (initialValues as any).level }}</strong>
35
+ </span>
36
+ <span v-if="submissionUnit" class="text-caption text-medium-emphasis">
37
+ Unit: <strong>{{ submissionUnit }}</strong>
38
+ </span>
30
39
  <template v-if="requiresPayment">
31
40
  <v-chip
32
41
  v-if="initialManagementValues?.paymentStatus"
@@ -53,7 +62,7 @@
53
62
  class="pa-6"
54
63
  >
55
64
  <v-form v-model="validForm">
56
- <template v-for="(block, i) in definition.blocks" :key="i">
65
+ <template v-for="(block, i) in displayBlocks" :key="i">
57
66
 
58
67
  <!-- ADDRESS BLOCK -->
59
68
  <div
@@ -630,6 +639,47 @@ function initValues(def: FormDefinition) {
630
639
  }
631
640
 
632
641
  values.value = { ...initValues(props.definition), ...props.initialValues };
642
+ // Seed unitNo from the top-level submission unit when not already in fields
643
+ if (props.submissionUnit && !values.value.unitNo) {
644
+ values.value.unitNo = props.submissionUnit;
645
+ }
646
+
647
+ const LOCATION_KEYS = new Set(["block", "level", "floor", "unitNo"]);
648
+
649
+ const displayBlocks = computed<FormBlock[]>(() => {
650
+ const filtered = props.definition.blocks.reduce<FormBlock[]>((acc, b) => {
651
+ if (b.type === "field" && LOCATION_KEYS.has((b as any).field?.key)) return acc;
652
+ if (b.type === "row") {
653
+ const remaining = (b as any).fields.filter((f: any) => !LOCATION_KEYS.has(f.key));
654
+ if (remaining.length === 0) return acc;
655
+ if (remaining.length !== (b as any).fields.length) {
656
+ acc.push({ ...b, fields: remaining } as any);
657
+ return acc;
658
+ }
659
+ }
660
+ acc.push(b);
661
+ return acc;
662
+ }, []);
663
+
664
+ const locationRow = {
665
+ type: "row" as const,
666
+ fields: [
667
+ { key: "block", label: "Block", fieldType: "text", cols: 4 },
668
+ { key: "level", label: "Level", fieldType: "text", cols: 4 },
669
+ { key: "unitNo", label: "Unit No.", fieldType: "text", cols: 4 },
670
+ ],
671
+ };
672
+
673
+ const addressIdx = filtered.findIndex((b) => b.type === "address");
674
+ if (addressIdx >= 0) {
675
+ return [
676
+ ...filtered.slice(0, addressIdx + 1),
677
+ locationRow,
678
+ ...filtered.slice(addressIdx + 1),
679
+ ] as FormBlock[];
680
+ }
681
+ return [locationRow, ...filtered] as FormBlock[];
682
+ });
633
683
 
634
684
  const NAME_LABELS = new Set([
635
685
  "name",
@@ -87,7 +87,7 @@
87
87
  title="Edit Vehicle" prepend-icon="mdi-pencil"
88
88
  @click="handleEditVehicleAction(plateNumberItem as TPlateNumber)" />
89
89
  <v-list-item
90
- v-if="props.app === 'property_management_agency' && (plateNumberItem as TPlateNumber)?.status == 'pending'"
90
+ v-if="canApproveVehicle && (plateNumberItem as TPlateNumber)?.status == 'pending'"
91
91
  title="Approve" prepend-icon="mdi-check-circle" base-color="success"
92
92
  @click="handleApproveVehicle(plateNumberItem as TPlateNumber)" />
93
93
  <v-list-item v-if="(plateNumberItem as TPlateNumber)?.status == 'deleted'" title="Restore"
@@ -164,6 +164,7 @@ const props = defineProps({
164
164
  canUpdateVehicle: { type: Boolean, default: true },
165
165
  canDeleteVehicle: { type: Boolean, default: true },
166
166
  canViewVehicleDetails: { type: Boolean, default: true },
167
+ canApproveVehicle: { type: Boolean, default: false },
167
168
  site: { type: String, required: true },
168
169
  org: { type: String, required: true },
169
170
  app: { type: String, required: true },
@@ -194,6 +195,10 @@ const plateHeaders = [
194
195
  const { formatCamelCaseToWords, formatDate, debounce, maskNRIC } = useUtils();
195
196
  const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, updateVehicle, getSpecificVehicleById } = useVehicle();
196
197
 
198
+ const canApproveVehicle = computed(() => {
199
+ return true;
200
+ })
201
+
197
202
  const items = ref<Array<Record<string, any>>>([]);
198
203
  const page = ref(1);
199
204
  const pages = ref(0);
@@ -390,7 +395,7 @@ function hasVehicleActions(item: TPlateNumber) {
390
395
 
391
396
  return (
392
397
  isActive ||
393
- (props.app === "property_management_agency" && isPending) ||
398
+ (props.canApproveVehicle && isPending) ||
394
399
  isDeleted ||
395
400
  (canUpdateType && isActive) ||
396
401
  (props.canDeleteVehicle && isActive)
@@ -232,6 +232,21 @@ export default function useOrg() {
232
232
  )
233
233
  }
234
234
 
235
+ function updateStatus(
236
+ id: string,
237
+ status: "active" | "suspended",
238
+ ) {
239
+ return useNuxtApp().$api<Record<string, any>>(
240
+ `/api/organizations/${id}/status`,
241
+ {
242
+ method: "PATCH",
243
+ body: {
244
+ status,
245
+ },
246
+ },
247
+ );
248
+ }
249
+
235
250
 
236
251
  return {
237
252
  org,
@@ -252,6 +267,7 @@ export default function useOrg() {
252
267
  updateOrg,
253
268
  getOrgsByEmail,
254
269
  getOrganizationsWithSubscription,
255
- completeOnboarding
270
+ completeOnboarding,
271
+ updateStatus
256
272
  };
257
273
  }
@@ -11,15 +11,35 @@ export default function(){
11
11
  const downloadUrl = window.URL.createObjectURL(res);
12
12
  const a = document.createElement("a");
13
13
  a.href = downloadUrl;
14
- a.download = title; // Specify the file name
14
+ a.download = title.toLowerCase().endsWith(".pdf") ? title : `${title}.pdf`; // Specify the file name
15
15
  document.body.appendChild(a);
16
16
  a.click(); // Trigger the download
17
17
  window.URL.revokeObjectURL(downloadUrl); // Clean up
18
18
  }
19
19
 
20
+ /**
21
+ * Generate a PDF directly from HTML content (no Puppeteer navigation / auth needed).
22
+ * The HTML is POSTed to the server which uses page.setContent() to render it.
23
+ */
24
+ async function downloadPDFFromHtml({ htmlContent, title, orientation }: { htmlContent: string; title: string; orientation: "P" | "L" }) {
25
+ const res = await useNuxtApp().$api<Blob | MediaSource>("/api/site-soa/generate-pdf-html", {
26
+ method: "POST",
27
+ body: { htmlContent, title, orientation },
28
+ });
29
+ if (!res) throw new Error("Error downloading PDF");
30
+ const downloadUrl = window.URL.createObjectURL(res);
31
+ const a = document.createElement("a");
32
+ a.href = downloadUrl;
33
+ a.download = title.toLowerCase().endsWith(".pdf") ? title : `${title}.pdf`;
34
+ document.body.appendChild(a);
35
+ a.click();
36
+ window.URL.revokeObjectURL(downloadUrl);
37
+ }
38
+
20
39
  return {
21
- downloadPDF
40
+ downloadPDF,
41
+ downloadPDFFromHtml,
22
42
  }
23
43
 
24
44
 
25
- }
45
+ }
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.0.15",
5
+ "version": "3.0.17",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {