@iservice365/layer-common 1.1.0 → 1.2.0

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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/CameraForm.vue +264 -0
  3. package/components/CameraMain.vue +352 -0
  4. package/components/Card/DeleteConfirmation.vue +51 -0
  5. package/components/Card/MemberInfoSummary.vue +44 -0
  6. package/components/Chat/Information.vue +28 -11
  7. package/components/Dialog/DeleteConfirmation.vue +51 -0
  8. package/components/Dialog/UpdateMoreAction.vue +99 -0
  9. package/components/Feedback/Form.vue +17 -3
  10. package/components/FeedbackDetail.vue +0 -11
  11. package/components/FeedbackMain.vue +21 -11
  12. package/components/Input/DateTimePicker.vue +5 -1
  13. package/components/Input/File.vue +1 -1
  14. package/components/Input/FileV2.vue +111 -63
  15. package/components/Input/InputPhoneNumberV2.vue +115 -0
  16. package/components/Input/NRICNumber.vue +41 -0
  17. package/components/Input/PhoneNumber.vue +1 -0
  18. package/components/Input/VehicleNumber.vue +41 -0
  19. package/components/NumberSettingField.vue +107 -0
  20. package/components/PeopleForm.vue +420 -0
  21. package/components/TableMain.vue +2 -1
  22. package/components/VehicleUpdateMoreAction.vue +84 -0
  23. package/components/VisitorForm.vue +712 -0
  24. package/components/VisitorFormSelection.vue +53 -0
  25. package/components/VisitorManagement.vue +568 -0
  26. package/components/WorkOrder/Create.vue +70 -46
  27. package/components/WorkOrder/Main.vue +11 -10
  28. package/composables/useBuilding.ts +250 -0
  29. package/composables/useBuildingUnit.ts +116 -0
  30. package/composables/useFeedback.ts +3 -3
  31. package/composables/useFile.ts +7 -9
  32. package/composables/useLocal.ts +67 -0
  33. package/composables/usePeople.ts +48 -0
  34. package/composables/useSecurityUtils.ts +18 -0
  35. package/composables/useSiteSettings.ts +111 -0
  36. package/composables/useUtils.ts +30 -1
  37. package/composables/useVisitor.ts +79 -0
  38. package/package.json +1 -1
  39. package/plugins/vuetify.ts +6 -1
  40. package/types/building.d.ts +19 -0
  41. package/types/camera.d.ts +31 -0
  42. package/types/people.d.ts +22 -0
  43. package/types/select.d.ts +4 -0
  44. package/types/site.d.ts +10 -7
  45. package/types/visitor.d.ts +42 -0
  46. package/utils/phoneMasks.ts +1703 -0
@@ -1,6 +1,5 @@
1
1
  export default function useFile() {
2
- const baseUrl =
3
- "https://iservice365-dev.sgp1.cdn.digitaloceanspaces.com/default";
2
+ const baseUrl = useRuntimeConfig().public.API_DO_STORAGE_ENDPOINT;
4
3
 
5
4
  function addFile(file: File | null) {
6
5
  if (!file) {
@@ -10,13 +9,10 @@ export default function useFile() {
10
9
  const formData = new FormData();
11
10
  formData.append("file", file);
12
11
 
13
- return $fetch<Record<string, any>>(
14
- "https://storage-api-dev-new-wv7cx.ondigitalocean.app/api/files/upload/v2",
15
- {
16
- method: "POST",
17
- body: formData,
18
- }
19
- );
12
+ return $fetch<Record<string, any>>("/api/files", {
13
+ method: "POST",
14
+ body: formData,
15
+ });
20
16
  }
21
17
 
22
18
  function getFileUrl(id: string) {
@@ -41,8 +37,10 @@ export default function useFile() {
41
37
  }
42
38
  );
43
39
  }
40
+
44
41
 
45
42
  return {
43
+ baseUrl,
46
44
  addFile,
47
45
  deleteFile,
48
46
  urlToFile,
@@ -49,6 +49,72 @@ export default function useLocal() {
49
49
 
50
50
  const landingPage = useCookie("landing-page", cookieConfig);
51
51
 
52
+ const subjects = [
53
+ {
54
+ title: "Facilities",
55
+ value: "Facilities",
56
+ subtitle:
57
+ "Shared amenities like gym, pool, BBQ pits, and function rooms.",
58
+ },
59
+ {
60
+ title: "Building Facade",
61
+ value: "Building Facade",
62
+ subtitle:
63
+ "Exterior walls, paint, cladding, signage, and overall appearance.",
64
+ },
65
+ {
66
+ title: "Security",
67
+ value: "Security",
68
+ subtitle:
69
+ "Guard services, access control, patrols, and visitor management.",
70
+ },
71
+ {
72
+ title: "Cleaning",
73
+ value: "Cleaning",
74
+ subtitle:
75
+ "Cleanliness of lobbies, corridors, lifts, bins, and common areas.",
76
+ },
77
+ {
78
+ title: "Landscape",
79
+ value: "Landscape",
80
+ subtitle: "Condition of plants, lawns, trees, and garden maintenance.",
81
+ },
82
+ {
83
+ title: "Pest Control",
84
+ value: "Pest Control",
85
+ subtitle:
86
+ "Effectiveness of measures against insects, rodents, and pests.",
87
+ },
88
+ {
89
+ title: "Water Features",
90
+ value: "Water Features",
91
+ subtitle:
92
+ "Operation and cleanliness of fountains, ponds, and pools (non-swimming).",
93
+ },
94
+ {
95
+ title: "Car Park",
96
+ value: "Car Park",
97
+ subtitle:
98
+ "Parking availability, markings, lighting, and barrier systems.",
99
+ },
100
+ {
101
+ title: "Lift",
102
+ value: "Lift",
103
+ subtitle:
104
+ "Lift reliability, speed, cleanliness, ventilation, and downtime.",
105
+ },
106
+ {
107
+ title: "Security Systems",
108
+ value: "Security Systems",
109
+ subtitle: "CCTV, intercoms, access cards, and gate/door sensors.",
110
+ },
111
+ {
112
+ title: "Others",
113
+ value: "Others",
114
+ subtitle: "Any issues or feedback not covered by the categories above.",
115
+ },
116
+ ];
117
+
52
118
  return {
53
119
  cookieConfig,
54
120
  getUserFromCookie,
@@ -57,5 +123,6 @@ export default function useLocal() {
57
123
  headerSearch,
58
124
  natureOfBusiness,
59
125
  landingPage,
126
+ subjects,
60
127
  };
61
128
  }
@@ -0,0 +1,48 @@
1
+ export default function(){
2
+
3
+
4
+ async function getAll({
5
+ page = 1,
6
+ limit = 10,
7
+ sort = "asc",
8
+ search = "",
9
+ org = "",
10
+ site = "",
11
+ dateTo = "",
12
+ dateFrom = "",
13
+ type="",
14
+ displayNoCheckOut=false
15
+ } = {}) {
16
+ return await useNuxtApp().$api<Record<string, any>>("/api/people", {
17
+ method: "GET",
18
+ query: { page, limit, sort, search, org, site, dateTo, dateFrom, type }
19
+ });
20
+ }
21
+
22
+ async function create(payload: Partial<TPeoplePayload>){
23
+ return await useNuxtApp().$api<Record<string, any>>("/api/people", {
24
+ method: "POST",
25
+ body: payload,
26
+ });
27
+ }
28
+
29
+ async function updateById(_id: string, payload: Partial<TPeoplePayload>){
30
+ return await useNuxtApp().$api<Record<string, any>>(`/api/people/id/${_id}`, {
31
+ method: "PUT",
32
+ body: payload,
33
+ });
34
+ }
35
+
36
+ async function deleteById(_id: string){
37
+ return await useNuxtApp().$api<Record<string, any>>(`/api/people/id/${_id}`, {
38
+ method: "DELETE",
39
+ });
40
+ }
41
+
42
+ return {
43
+ create,
44
+ getAll,
45
+ updateById,
46
+ deleteById
47
+ }
48
+ }
@@ -0,0 +1,18 @@
1
+ export default function(){
2
+
3
+ const formatLocation = ({block, level, unit}: {block: number, level: string, unit: string | TBuildingUnit}) => {
4
+ const parts = [];
5
+
6
+ if (block) parts.push(`Block ${block}`);
7
+ if (level) parts.push(level);
8
+ if (unit) parts.push(unit);
9
+
10
+ return parts.join(" / ");
11
+ };
12
+
13
+
14
+ return {
15
+ formatLocation
16
+
17
+ }
18
+ }
@@ -0,0 +1,111 @@
1
+ export default function () {
2
+ async function getSiteById(siteId: string) {
3
+ return await useNuxtApp().$api<Record<string, any>>(
4
+ `/api/sites/${siteId}`,
5
+ {
6
+ method: "GET",
7
+ }
8
+ );
9
+ }
10
+
11
+ async function getSiteLevels(siteId: string, query: { block: number }) {
12
+ return await useNuxtApp().$api<Record<string, any>>(
13
+ `/api/buildings/site/${siteId}`,
14
+ {
15
+ method: "GET",
16
+ query: query,
17
+ }
18
+ );
19
+ }
20
+
21
+ async function getSiteUnits(siteId: string, block: number, level: string) {
22
+ return await useNuxtApp().$api<Record<string, any>>(
23
+ `/api/building-units/site/${siteId}/block/${block}/level/${level}`,
24
+ {
25
+ method: "GET",
26
+ }
27
+ );
28
+ }
29
+
30
+ async function updateSite(siteId: string, payload: { block: number }) {
31
+ return await useNuxtApp().$api<Record<string, any>>(
32
+ `/api/sites/${siteId}/block`,
33
+ {
34
+ method: "PUT",
35
+ body: payload,
36
+ }
37
+ );
38
+ }
39
+
40
+ async function addCamera(camera: TSiteCamera) {
41
+ return await useNuxtApp().$api<Record<string, any>>(`/api/site-cameras`, {
42
+ method: "POST",
43
+ body: camera,
44
+ });
45
+ }
46
+
47
+ async function updateSiteCamera(
48
+ id: string,
49
+ value: Partial<
50
+ Pick<
51
+ TSiteCamera,
52
+ | "host"
53
+ | "category"
54
+ | "guardPost"
55
+ | "password"
56
+ | "username"
57
+ | "direction"
58
+ >
59
+ >
60
+ ) {
61
+ return await useNuxtApp().$api<Record<string, any>>(
62
+ `/api/site-cameras/id/${id}`,
63
+ {
64
+ method: "PATCH",
65
+ body: value,
66
+ }
67
+ );
68
+ }
69
+
70
+ async function deleteSiteCameraById(id: string) {
71
+ return await useNuxtApp().$api<Record<string, any>>(
72
+ `/api/site-cameras/id/${id}`,
73
+ {
74
+ method: "DELETE",
75
+ }
76
+ );
77
+ }
78
+
79
+ async function getAllSiteCameras(payload: {
80
+ site: string;
81
+ type?: string;
82
+ page?: number;
83
+ }) {
84
+ return await useNuxtApp().$api<Record<string, any>>(`/api/site-cameras`, {
85
+ method: "GET",
86
+ query: payload,
87
+ });
88
+ }
89
+
90
+ async function setSiteGuardPosts(siteId: string, value: number) {
91
+ return await useNuxtApp().$api<Record<string, any>>(
92
+ `/api/sites/guard-post/id/${siteId}`,
93
+ {
94
+ method: "PATCH",
95
+ body: { guardPost: value },
96
+ }
97
+ );
98
+ }
99
+
100
+ return {
101
+ getSiteById,
102
+ getSiteLevels,
103
+ getSiteUnits,
104
+ updateSite,
105
+ addCamera,
106
+ getAllSiteCameras,
107
+ setSiteGuardPosts,
108
+ updateSiteCamera,
109
+ deleteSiteCameraById,
110
+ };
111
+ }
@@ -365,6 +365,33 @@ export default function useUtils() {
365
365
  .replace(/^./, str => str.toUpperCase());
366
366
  }
367
367
 
368
+
369
+ function calculateRemainingTime(
370
+ item: Date | string | number | null | undefined
371
+ ) {
372
+ if (!item) return -1;
373
+ const _date = new Date(item);
374
+ if (isNaN(_date.getTime())) return -1;
375
+ const creationTime = _date.getTime();
376
+ const currentTime = Date.now();
377
+ const differenceInMillis = currentTime - creationTime;
378
+ const differenceInSeconds = Math.floor(differenceInMillis / 1000);
379
+ const desiredDurationInSeconds = 24 * 60 * 60;
380
+ const remainingTimeInSeconds = desiredDurationInSeconds - differenceInSeconds;
381
+ console.log(remainingTimeInSeconds);
382
+ return remainingTimeInSeconds;
383
+ }
384
+
385
+ const formatTime = (seconds: number) => {
386
+ if (seconds <= 0 || isNaN(seconds)) return "00h 00m";
387
+ const hours = Math.floor(seconds / 3600);
388
+ const minutes = Math.floor((seconds % 3600) / 60);
389
+ return `${String(hours).padStart(2, "0")}h ${String(minutes).padStart(
390
+ 2,
391
+ "0"
392
+ )}m`;
393
+ };
394
+
368
395
  return {
369
396
  requiredRule,
370
397
  emailRule,
@@ -398,6 +425,8 @@ export default function useUtils() {
398
425
  toOrdinal,
399
426
  formatDateISO8601,
400
427
  isValidBaseURL,
401
- formatCamelCaseToWords
428
+ formatCamelCaseToWords,
429
+ calculateRemainingTime,
430
+ formatTime
402
431
  };
403
432
  }
@@ -0,0 +1,79 @@
1
+ export default function(){
2
+
3
+ type TVisitorSelection = {
4
+ label: string;
5
+ value: TVisitorType
6
+ }
7
+
8
+ const visitorSelection: TVisitorSelection[] =[
9
+ { label: "Contractor", value: "contractor" },
10
+ { label: "Delivery", value: "delivery" },
11
+ { label: "Walk-In", value: "walk-in"},
12
+ { label: "Pick-Up", value: "pick-up"},
13
+ { label: "Drop-Off", value: "drop-off"}
14
+ ];
15
+
16
+ const typeFieldMap: Record<TVisitorType, (keyof TVisitorPayload)[]> = {
17
+ contractor: ['contractorType', 'name', 'nric', 'company', 'contact', 'plateNumber', 'block', 'level', 'unit', 'unitName', 'remarks'],
18
+ delivery: ['attachments', 'name', 'deliveryType', 'company', 'nric', 'contact', 'plateNumber', 'block', 'level', 'unit' , 'unitName', 'remarks'],
19
+ 'walk-in': ['name', 'company', 'nric', 'contact', 'block', 'level', 'unit' , 'unitName', 'remarks'],
20
+ 'pick-up': ['plateNumber', 'block', 'remarks'],
21
+ 'drop-off': ['plateNumber', 'block', 'remarks'],
22
+ }
23
+
24
+ const contractorTypes = [
25
+ { title: "Estate Contractor", value: "estate-contractor" },
26
+ { title: "Home Contractor", value: "home-contractor" },
27
+ { title: "Property Agent", value: "property-agent" },
28
+ { title: "House Mover", value: "house-mover" },
29
+ ]
30
+
31
+
32
+ async function getVisitors({
33
+ page = 1,
34
+ limit = 10,
35
+ sort = "asc",
36
+ search = "",
37
+ org = "",
38
+ site = "",
39
+ dateTo = "",
40
+ dateFrom = "",
41
+ type="",
42
+ displayNoCheckOut=false
43
+ } = {}) {
44
+ return await useNuxtApp().$api<Record<string, any>>("/api/visitor-transactions", {
45
+ method: "GET",
46
+ query: { page, limit, sort, search, org, site, dateTo, dateFrom, ...(type.length > 0 && {type}), ...(displayNoCheckOut === true && {checkedOut: false}) },
47
+ });
48
+ }
49
+
50
+ async function createVisitor(payload: Partial<TVisitorPayload>){
51
+ return await useNuxtApp().$api<Record<string, any>>("/api/visitor-transactions", {
52
+ method: "POST",
53
+ body: payload,
54
+ });
55
+ }
56
+
57
+ async function updateVisitor(_id: string, payload: Partial<TVisitorPayload>){
58
+ return await useNuxtApp().$api<Record<string, any>>(`/api/visitor-transactions/id/${_id}`, {
59
+ method: "PUT",
60
+ body: payload,
61
+ });
62
+ }
63
+
64
+ async function deleteVisitor(_id: string){
65
+ return await useNuxtApp().$api<Record<string, any>>(`/api/visitor-transactions/id/${_id}`, {
66
+ method: "DELETE",
67
+ });
68
+ }
69
+
70
+ return {
71
+ typeFieldMap,
72
+ visitorSelection,
73
+ contractorTypes,
74
+ createVisitor,
75
+ getVisitors,
76
+ updateVisitor,
77
+ deleteVisitor
78
+ }
79
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@iservice365/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.1.0",
5
+ "version": "1.2.0",
6
6
  "main": "./nuxt.config.ts",
7
7
  "scripts": {
8
8
  "dev": "nuxi dev .playground",
@@ -1,6 +1,8 @@
1
1
  // import this after install `@mdi/font` package
2
2
  import "@mdi/font/css/materialdesignicons.css";
3
3
  import { VFileUpload, VFileUploadItem } from 'vuetify/labs/VFileUpload'
4
+ import { VMaskInput } from 'vuetify/labs/VMaskInput'
5
+
4
6
 
5
7
  import "vuetify/styles";
6
8
  import { createVuetify } from "vuetify";
@@ -16,10 +18,12 @@ export default defineNuxtPlugin((app) => {
16
18
  VAutocomplete: {
17
19
  variant: "outlined",
18
20
  density: "comfortable",
21
+ menuProps: { attach: document.body, zIndex: 3000, }
19
22
  },
20
23
  VSelect: {
21
24
  variant: "outlined",
22
25
  density: "comfortable",
26
+ menuProps: { attach: document.body, zIndex: 3000}
23
27
  },
24
28
  VTextarea: {
25
29
  variant: "outlined",
@@ -49,7 +53,8 @@ export default defineNuxtPlugin((app) => {
49
53
  },
50
54
  components: {
51
55
  VFileUpload,
52
- VFileUploadItem
56
+ VFileUploadItem,
57
+ VMaskInput
53
58
  }
54
59
  });
55
60
 
@@ -0,0 +1,19 @@
1
+ declare type TBuilding = {
2
+ _id?: string;
3
+ site: string;
4
+ name: string;
5
+ block: number | null;
6
+ levels: string[];
7
+ };
8
+
9
+ declare type TBuildingUnit = {
10
+ _id?: string;
11
+ site: string;
12
+ name?: string;
13
+ building: string;
14
+ buildingName?: string;
15
+ block: number | null;
16
+ level: string | null;
17
+ category: string;
18
+ status: string;
19
+ };
@@ -0,0 +1,31 @@
1
+ declare type TCamera = {
2
+ site: string;
3
+ cameras: {
4
+ host: string;
5
+ username: string;
6
+ password: string;
7
+ type: TCameraTypes;
8
+ }[];
9
+ };
10
+
11
+ declare type TCameraConfig = {
12
+ host: string;
13
+ username: string;
14
+ password: string;
15
+ type: TCameraTypes;
16
+ };
17
+
18
+ declare type TCameraTypes = "ip" | "exit" | "entry" | "both";
19
+
20
+ declare type TSiteCamera = {
21
+ _id?: string;
22
+ site: string;
23
+ host: string;
24
+ username: string;
25
+ password: string;
26
+ type: "ip" | "anpr";
27
+ category: "standard" | "resident" | "visitor";
28
+ direction: "entry" | "exit" | "both" | "none";
29
+ guardPost: number | null;
30
+ status: string;
31
+ };
@@ -0,0 +1,22 @@
1
+ declare type TPeople = {
2
+ name?: string;
3
+ block?: number | string;
4
+ level?: string;
5
+ unit?: string | Partial<TBuildingUnit>;
6
+ unitName?: string;
7
+ contact: string;
8
+ plateNumber: string;
9
+ nric?: string;
10
+ contact?: string;
11
+ remarks?: string;
12
+ start: string;
13
+ end: string;
14
+ org: string;
15
+ site: string,
16
+ type?: TPeoplePayload;
17
+ };
18
+
19
+
20
+ declare type TPeoplePayload = Pick<TGuest, "name" | "block" | "level" | "unit" | "unitName" | "contact" | "plateNumber" | "nric" | "contact" | "remarks" | "org" | "site" | "start" | "end" | "type">
21
+
22
+ declare type TPeopleType = "visitor" | "resident" | "tenant"
@@ -0,0 +1,4 @@
1
+ declare type TDefaultOptionObj = {
2
+ title: string;
3
+ value: any;
4
+ }
package/types/site.d.ts CHANGED
@@ -1,17 +1,20 @@
1
+ declare type TSiteCreate = Pick<TSite, "name" | "description" | "orgId">;
2
+
3
+
1
4
  declare type TSite = {
2
5
  _id?: string;
3
6
  name: string;
4
7
  description?: string;
5
8
  orgId: string;
9
+ metadata?: TSiteMetadata;
10
+ status?: string;
11
+ createdAt?: string;
12
+ updatedAt?: string | string;
13
+ deletedAt?: string | string;
6
14
  blocks?: number;
7
- customerOrgId: string;
8
- customerSiteId: string;
15
+ customerOrgId?: string;
16
+ customerSiteId?: string;
9
17
  createdAt?: string;
10
18
  updatedAt?: string;
11
19
  deletedAt?: string;
12
- metadata?: {
13
- block?: number; // Number of blocks in the site
14
- };
15
20
  };
16
-
17
- declare type TSiteCreate = Pick<TSite, "name" | "description" | "orgId">;
@@ -0,0 +1,42 @@
1
+ declare type TVisitor = {
2
+ name?: string;
3
+ type: TVisitorType;
4
+ company?: string | "";
5
+ block?: number | string;
6
+ level?: string;
7
+ unit?: string;
8
+ unitName?: string; // only for display purpose
9
+ contact: string;
10
+ plateNumber: string;
11
+ checkIn?: string;
12
+ checkOut?: string | null;
13
+ contractorType?: string;
14
+ deliveryType?: string;
15
+ nric?: string;
16
+ contact?: string;
17
+ remarks?: string;
18
+ attachments: string[];
19
+ org: string;
20
+ site: string,
21
+ manualCheckout?: boolean;
22
+ };
23
+
24
+ declare type TVisitorType = "contractor" | "delivery" | "walk-in" | "pick-up" | "drop-off";
25
+
26
+ declare type TVisitorPayload = Pick<TVisitor, "name" | "type" | "company" | "block" | "level" | "unit" | "unitName" | "contact" | "plateNumber" | "checkOut" | "contractorType" | "deliveryType" | "nric" | "contact" | "remarks" | "attachments" | "org" | "site"> & {
27
+ members?: TMemberInfo[]
28
+ }
29
+
30
+
31
+
32
+ declare type TDefaultOptionObj = {
33
+ title: string;
34
+ value: any;
35
+ }
36
+
37
+ declare type TMemberInfo = {
38
+ name: string;
39
+ nric: string;
40
+ visitorPass: string;
41
+ contact: string
42
+ }