@7365admin1/layer-common 1.8.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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.editorconfig +12 -0
- package/.github/workflows/main.yml +17 -0
- package/.github/workflows/publish.yml +39 -0
- package/.nuxtrc +1 -0
- package/.playground/app.vue +41 -0
- package/.playground/eslint.config.mjs +6 -0
- package/.playground/nuxt.config.ts +22 -0
- package/.playground/pages/feedback.vue +30 -0
- package/CHANGELOG.md +263 -0
- package/README.md +73 -0
- package/app.vue +3 -0
- package/components/AccessCardAddForm.vue +363 -0
- package/components/AccessManagement.vue +420 -0
- package/components/Avatar/Main.vue +68 -0
- package/components/BillingMain.vue +66 -0
- package/components/BtnUploadFile.vue +139 -0
- package/components/BuildingForm.vue +303 -0
- package/components/BuildingManagement/buildings.vue +335 -0
- package/components/BuildingManagement/units.vue +350 -0
- package/components/BuildingUnitFormAdd.vue +441 -0
- package/components/BuildingUnitFormEdit.vue +429 -0
- package/components/CameraForm.vue +264 -0
- package/components/CameraMain.vue +352 -0
- package/components/Card/DeleteConfirmation.vue +51 -0
- package/components/Card/MemberInfoSummary.vue +44 -0
- package/components/Card/Toggle.vue +25 -0
- package/components/Chat/Bubbles.vue +53 -0
- package/components/Chat/Information.vue +416 -0
- package/components/Chat/ListCard.vue +62 -0
- package/components/Chat/Message.vue +158 -0
- package/components/Chat/Navigation.vue +150 -0
- package/components/ConfirmDialog.vue +66 -0
- package/components/Container/Standard.vue +33 -0
- package/components/DashboardPlaceholder.vue +1524 -0
- package/components/Dialog/DeleteConfirmation.vue +51 -0
- package/components/Dialog/ReplaceAutofillPrompt.vue +49 -0
- package/components/Dialog/UpdateMoreAction.vue +103 -0
- package/components/DocumentForm.vue +187 -0
- package/components/DocumentManagement.vue +376 -0
- package/components/Editor.vue +95 -0
- package/components/EntryPassMain.vue +518 -0
- package/components/Feedback/Form.vue +173 -0
- package/components/FeedbackDetail.vue +599 -0
- package/components/FeedbackMain.vue +588 -0
- package/components/FormDialog.vue +65 -0
- package/components/ImageCarousel.vue +138 -0
- package/components/Input/Date.vue +177 -0
- package/components/Input/DateTimePicker.vue +131 -0
- package/components/Input/File.vue +236 -0
- package/components/Input/FileV2.vue +234 -0
- package/components/Input/InputPhoneNumberV2.vue +164 -0
- package/components/Input/ListGroupSelection.vue +96 -0
- package/components/Input/NRICNumber.vue +53 -0
- package/components/Input/NewDate.vue +123 -0
- package/components/Input/Number.vue +124 -0
- package/components/Input/Password.vue +22 -0
- package/components/Input/PhoneNumber.vue +188 -0
- package/components/Input/VehicleNumber.vue +49 -0
- package/components/InputLabel.vue +22 -0
- package/components/InvitationForm.vue +359 -0
- package/components/InvitationMain.vue +310 -0
- package/components/Layout/Header.vue +129 -0
- package/components/Layout/NavigationDrawer.vue +44 -0
- package/components/ListItem.vue +35 -0
- package/components/ListView.vue +87 -0
- package/components/LocalPagination.vue +31 -0
- package/components/MemberMain.vue +459 -0
- package/components/NFC/NFCPatrolReportMain.vue +591 -0
- package/components/NFC/NFCPatrolRouteForm.vue +596 -0
- package/components/NFC/NFCPatrolRouteMain.vue +539 -0
- package/components/NFC/NFCTagForm.vue +236 -0
- package/components/NFC/NFCTagMain.vue +337 -0
- package/components/NFC/PatrolSettings.vue +130 -0
- package/components/NavigationItem.vue +83 -0
- package/components/NumberSettingField.vue +107 -0
- package/components/OnlineFormConfigurationForm.vue +290 -0
- package/components/OnlineFormsConfiguration.vue +429 -0
- package/components/PeopleForm.vue +452 -0
- package/components/PlaceholderComponent.vue +34 -0
- package/components/RolePermissionFormCreate.vue +161 -0
- package/components/RolePermissionFormPreviewUpdate.vue +183 -0
- package/components/RolePermissionMain.vue +361 -0
- package/components/SearchVehicleNumberUser.vue +91 -0
- package/components/ServiceProviderFormCreate.vue +154 -0
- package/components/ServiceProviderMain.vue +547 -0
- package/components/SignaturePad.vue +73 -0
- package/components/Snackbar.vue +23 -0
- package/components/SpecificAttr.vue +53 -0
- package/components/SupplyManagement.vue +292 -0
- package/components/SwitchContext.vue +108 -0
- package/components/TableList.vue +150 -0
- package/components/TableListSecondary.vue +164 -0
- package/components/TableMain.vue +142 -0
- package/components/TableWithButton.vue +94 -0
- package/components/VehicleUpdateMoreAction.vue +84 -0
- package/components/VideoPlayer.vue +125 -0
- package/components/VisitorForm.vue +659 -0
- package/components/VisitorFormSelection.vue +53 -0
- package/components/VisitorManagement.vue +490 -0
- package/components/WorkOrder/Create.vue +284 -0
- package/components/WorkOrder/Detail.vue +71 -0
- package/components/WorkOrder/ListView.vue +96 -0
- package/components/WorkOrder/Main.vue +489 -0
- package/components/Workorder.vue +1 -0
- package/composables/useAddress.ts +107 -0
- package/composables/useBuilding.ts +250 -0
- package/composables/useBuildingUnit.ts +116 -0
- package/composables/useCard.ts +46 -0
- package/composables/useCommonPermission.ts +207 -0
- package/composables/useCustomer.ts +113 -0
- package/composables/useCustomerSite.ts +56 -0
- package/composables/useDashboard.ts +31 -0
- package/composables/useDashboardData.ts +425 -0
- package/composables/useDocument.ts +57 -0
- package/composables/useFacility.ts +246 -0
- package/composables/useFeedback.ts +119 -0
- package/composables/useFile.ts +55 -0
- package/composables/useInvoice.ts +18 -0
- package/composables/useLocal.ts +131 -0
- package/composables/useLocalAuth.ts +137 -0
- package/composables/useLocalSetup.ts +13 -0
- package/composables/useMember.ts +111 -0
- package/composables/useNFCPatrolRoute.ts +77 -0
- package/composables/useNFCPatrolSettings.ts +19 -0
- package/composables/useNFCPatrolTag.ts +53 -0
- package/composables/useOnlineForm.ts +67 -0
- package/composables/useOrg.ts +129 -0
- package/composables/usePDFDownload.ts +25 -0
- package/composables/usePaymentMethod.ts +101 -0
- package/composables/usePeople.ts +81 -0
- package/composables/usePermission.ts +54 -0
- package/composables/usePhoneCountries.ts +561 -0
- package/composables/usePrice.ts +15 -0
- package/composables/usePromoCode.ts +36 -0
- package/composables/useRecapPermission.ts +26 -0
- package/composables/useRole.ts +104 -0
- package/composables/useSecurityUtils.ts +18 -0
- package/composables/useServiceProvider.ts +224 -0
- package/composables/useSite.ts +109 -0
- package/composables/useSiteEntryPassSettings.ts +46 -0
- package/composables/useSiteSettings.ts +123 -0
- package/composables/useSubscription.ts +150 -0
- package/composables/useUser.ts +132 -0
- package/composables/useUtils.ts +445 -0
- package/composables/useVerification.ts +34 -0
- package/composables/useVisitor.ts +120 -0
- package/composables/useWorkOrder.ts +85 -0
- package/error.vue +41 -0
- package/layouts/plain.vue +7 -0
- package/middleware/01.auth.ts +20 -0
- package/middleware/02.org.ts +21 -0
- package/middleware/03.customer.ts +13 -0
- package/middleware/member.ts +4 -0
- package/nuxt.config.ts +54 -0
- package/package.json +39 -0
- package/pages/index.vue +3 -0
- package/pages/payment-method-linked.vue +31 -0
- package/pages/require-customer.vue +56 -0
- package/pages/require-organization-membership.vue +47 -0
- package/pages/unauthorized.vue +29 -0
- package/plugins/API.ts +21 -0
- package/plugins/iconify.client.ts +5 -0
- package/plugins/secure-member.client.ts +86 -0
- package/plugins/vuetify.ts +62 -0
- package/public/bg-camera.jpg +0 -0
- package/public/bg-city.jpg +0 -0
- package/public/bg-condo.jpg +0 -0
- package/public/images/icons/delete-icon.png +0 -0
- package/public/sprite.svg +1 -0
- package/tsconfig.json +3 -0
- package/types/address.d.ts +13 -0
- package/types/building.d.ts +27 -0
- package/types/camera.d.ts +31 -0
- package/types/card.d.ts +22 -0
- package/types/customer.d.ts +27 -0
- package/types/document.d.ts +6 -0
- package/types/feedback.d.ts +68 -0
- package/types/local.d.ts +74 -0
- package/types/member.d.ts +21 -0
- package/types/online-form.d.ts +15 -0
- package/types/org.d.ts +13 -0
- package/types/people.d.ts +24 -0
- package/types/permission.d.ts +25 -0
- package/types/phone-number.d.ts +10 -0
- package/types/price.d.ts +17 -0
- package/types/promo-code.d.ts +19 -0
- package/types/role.d.ts +11 -0
- package/types/select.d.ts +4 -0
- package/types/service-provider.d.ts +15 -0
- package/types/site.d.ts +20 -0
- package/types/subscription.d.ts +23 -0
- package/types/user.d.ts +19 -0
- package/types/verification.d.ts +20 -0
- package/types/visitor.d.ts +42 -0
- package/types/work-order.d.ts +42 -0
- package/utils/phoneMasks.ts +1703 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
export default function useUtils() {
|
|
2
|
+
const requiredRule = (v: string) => !!v || "Required";
|
|
3
|
+
|
|
4
|
+
const emailRule = (v: string): true | string => {
|
|
5
|
+
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
6
|
+
return regex.test(v) || "Please enter a valid email address";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const validateDate = (value: string): boolean | string => {
|
|
10
|
+
const dateRegex = /^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/;
|
|
11
|
+
|
|
12
|
+
if (!dateRegex.test(value)) return "Invalid date format (MM/DD/YYYY)";
|
|
13
|
+
|
|
14
|
+
return true;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function requireListRule(value: string[]) {
|
|
18
|
+
return value.length ? "" : "Required";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const minOneMonthAdvance = (value: string): boolean | string => {
|
|
22
|
+
if (!/^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/.test(value)) {
|
|
23
|
+
return "Invalid date format (MM/DD/YYYY)";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const [month, day, year] = value.split("/").map(Number);
|
|
27
|
+
const selectedDate = new Date(year, month - 1, day);
|
|
28
|
+
|
|
29
|
+
const currentDate = new Date();
|
|
30
|
+
const minDate = new Date();
|
|
31
|
+
minDate.setMonth(currentDate.getMonth() + 1); // 1 month in advance
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
selectedDate >= minDate || "Date must be at least 1 month in advance"
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const passwordRule = (v: string) =>
|
|
39
|
+
v.length >= 8 || "Password must be at least 8 characters long";
|
|
40
|
+
|
|
41
|
+
const confirmPasswordRule =
|
|
42
|
+
(newPassword: Ref<string>, confirmPassword: Ref<string>) => () =>
|
|
43
|
+
confirmPassword.value === newPassword.value || "Passwords must match";
|
|
44
|
+
|
|
45
|
+
const validateEmail = (email: Ref<string>, emailErrors: Ref<string[]>) => {
|
|
46
|
+
emailErrors.value = [];
|
|
47
|
+
|
|
48
|
+
if (!email.value) {
|
|
49
|
+
emailErrors.value.push("Please input your email");
|
|
50
|
+
} else {
|
|
51
|
+
const result = emailRule(email.value);
|
|
52
|
+
if (result !== true) {
|
|
53
|
+
emailErrors.value.push(result);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const validateWord = (value: string): boolean | string => {
|
|
59
|
+
if (value.trim().length < 3) return "Must be at least 3 characters";
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const validatePassword = (
|
|
64
|
+
password: Ref<string>,
|
|
65
|
+
passwordErrors: Ref<string[]>
|
|
66
|
+
) => {
|
|
67
|
+
passwordErrors.value = [];
|
|
68
|
+
|
|
69
|
+
if (!password.value) {
|
|
70
|
+
passwordErrors.value.push("Please input your password");
|
|
71
|
+
} else if (!passwordRule(password.value)) {
|
|
72
|
+
passwordErrors.value.push("Password must be at least 8 characters long");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function back() {
|
|
77
|
+
useRouter().back();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function debounce<T extends (...args: any[]) => void>(
|
|
81
|
+
func: T,
|
|
82
|
+
wait: number
|
|
83
|
+
): (...args: Parameters<T>) => void {
|
|
84
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
85
|
+
|
|
86
|
+
return function (...args: Parameters<T>) {
|
|
87
|
+
clearTimeout(timeoutId);
|
|
88
|
+
timeoutId = setTimeout(() => func(...args), wait);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function insertBetween(arr: any, elementToInsert: any) {
|
|
93
|
+
return arr.flatMap((item: any, index: number, array: any) =>
|
|
94
|
+
index < array.length - 1 ? [item, elementToInsert] : [item]
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getNameInitials(name: string) {
|
|
99
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
100
|
+
return ""; // Default fallback for invalid input
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const words = name.trim().split(/\s+/); // Split by spaces
|
|
104
|
+
|
|
105
|
+
if (words.length === 1) {
|
|
106
|
+
return words[0].slice(0, 2).toUpperCase(); // Take first two letters of the single word
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return words
|
|
110
|
+
.map((word) => word[0]?.toUpperCase()) // Take the first letter of each word and capitalize
|
|
111
|
+
.join("")
|
|
112
|
+
.slice(0, 2); // Limit to 2 characters
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getDimensions(
|
|
116
|
+
file: File
|
|
117
|
+
): Promise<{ width: number; height: number }> {
|
|
118
|
+
const img = new Image();
|
|
119
|
+
img.src = URL.createObjectURL(file);
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
img.onload = () => {
|
|
122
|
+
const width = img.width;
|
|
123
|
+
const height = img.height;
|
|
124
|
+
resolve({ width, height });
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
img.onerror = () => {
|
|
128
|
+
reject(new Error("Failed to read image dimensions"));
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function getCountries() {
|
|
134
|
+
try {
|
|
135
|
+
const countries = await useNuxtApp().$api<Array<Record<string, any>>>(
|
|
136
|
+
"https://restcountries.com/v3.1/all?fields=name,idd",
|
|
137
|
+
{ method: "GET" }
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const uniqueCountries = new Map();
|
|
141
|
+
|
|
142
|
+
countries.forEach((country) => {
|
|
143
|
+
let suffixes = country.idd?.suffixes?.[0] || "";
|
|
144
|
+
let code = `${country.idd?.root || ""}${suffixes}`;
|
|
145
|
+
let name = country.name?.common || "";
|
|
146
|
+
|
|
147
|
+
if (name && code && !uniqueCountries.has(code)) {
|
|
148
|
+
uniqueCountries.set(code, { index: `${name}-${code}`, name, code });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return Array.from(uniqueCountries.values()).sort((a, b) =>
|
|
153
|
+
a.name.localeCompare(b.name)
|
|
154
|
+
);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(error);
|
|
157
|
+
throw new Error("Failed to fetch countries.");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const formatter = new Intl.NumberFormat("en-US", {
|
|
162
|
+
minimumFractionDigits: 2,
|
|
163
|
+
maximumFractionDigits: 2,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
function formatNumber(
|
|
167
|
+
amount: number,
|
|
168
|
+
options: {
|
|
169
|
+
currency?: string;
|
|
170
|
+
locale?: string;
|
|
171
|
+
useSymbol?: boolean;
|
|
172
|
+
decimalPlaces?: number;
|
|
173
|
+
} = {}
|
|
174
|
+
): string {
|
|
175
|
+
const {
|
|
176
|
+
currency,
|
|
177
|
+
locale = "en-US",
|
|
178
|
+
useSymbol = false,
|
|
179
|
+
decimalPlaces = 2,
|
|
180
|
+
} = options;
|
|
181
|
+
|
|
182
|
+
return new Intl.NumberFormat(locale, {
|
|
183
|
+
style: useSymbol && currency ? "currency" : "decimal",
|
|
184
|
+
currency: currency || "USD", // Default currency (ignored if `useSymbol` is false)
|
|
185
|
+
minimumFractionDigits: decimalPlaces,
|
|
186
|
+
maximumFractionDigits: decimalPlaces,
|
|
187
|
+
}).format(amount);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 🔹 Examples:
|
|
191
|
+
// console.log(formatNumber(1234.56)); // "1,234.56" (comma separator, 2 decimals)
|
|
192
|
+
// console.log(formatNumber(1234.56, { decimalPlaces: 0 })); // "1,234" (no decimals)
|
|
193
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "USD" })); // "$1,234.56"
|
|
194
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "SGD", decimalPlaces: 0 })); // "S$1,234"
|
|
195
|
+
// console.log(formatNumber(1234.56, { useSymbol: false, decimalPlaces: 0 })); // "1,234"
|
|
196
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE" })); // "1.234,56 €"
|
|
197
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE", decimalPlaces: 0 })); // "1.234 €"
|
|
198
|
+
|
|
199
|
+
function convertPermissionsToArray(permissions: TPermissions) {
|
|
200
|
+
return Object.entries(permissions).flatMap(([resource, actions]) =>
|
|
201
|
+
Object.keys(actions).map((action) => `${resource}:${action}`)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function redirect(link: string, page?: string) {
|
|
206
|
+
const href = page ? `${link}/${page}` : link;
|
|
207
|
+
|
|
208
|
+
window.location.href = href;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function computeTieredCost(seats: number, tiers: TPromoTier[]) {
|
|
212
|
+
let totalCost = 0;
|
|
213
|
+
|
|
214
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
215
|
+
let { min, max, price } = tiers[i];
|
|
216
|
+
|
|
217
|
+
if (max === 0) max = Infinity; // Handle unlimited tier
|
|
218
|
+
|
|
219
|
+
if (seats >= min) {
|
|
220
|
+
let seatsInTier = Math.min(seats, max) - min + 1;
|
|
221
|
+
totalCost += seatsInTier * price;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (seats <= max) break; // Stop when all seats are counted
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return totalCost;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const getColorStatus = (status: string) => {
|
|
231
|
+
switch (status.toLowerCase()) {
|
|
232
|
+
case "in-progress":
|
|
233
|
+
case "in progress":
|
|
234
|
+
return "warning";
|
|
235
|
+
case "to-do":
|
|
236
|
+
case "to do":
|
|
237
|
+
return "#052439";
|
|
238
|
+
case "complete":
|
|
239
|
+
case "active":
|
|
240
|
+
return "success";
|
|
241
|
+
|
|
242
|
+
case "suspended":
|
|
243
|
+
return "error";
|
|
244
|
+
|
|
245
|
+
default:
|
|
246
|
+
return "grey";
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
function getOrigin() {
|
|
251
|
+
if (process.client) {
|
|
252
|
+
return window.location.origin;
|
|
253
|
+
}
|
|
254
|
+
return "";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const search = useState("search", () => "");
|
|
258
|
+
|
|
259
|
+
//returns Oct 16, 2025, 03:45 PM // N/A format
|
|
260
|
+
function formatDate(dateString: string) {
|
|
261
|
+
if (!dateString) return "N/A";
|
|
262
|
+
|
|
263
|
+
const date = new Date(dateString);
|
|
264
|
+
return date.toLocaleDateString("en-US", {
|
|
265
|
+
year: "numeric",
|
|
266
|
+
month: "short",
|
|
267
|
+
day: "numeric",
|
|
268
|
+
hour: "2-digit",
|
|
269
|
+
minute: "2-digit",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// returns 16/10/2025, 15:45 format
|
|
274
|
+
function UTCToLocalTIme(UTCDateTime: string) {
|
|
275
|
+
if (!UTCDateTime) return "";
|
|
276
|
+
const local = new Date(UTCDateTime);
|
|
277
|
+
|
|
278
|
+
const formatted = local.toLocaleString("en-GB", {
|
|
279
|
+
day: "2-digit",
|
|
280
|
+
month: "2-digit",
|
|
281
|
+
year: "numeric",
|
|
282
|
+
hour: "2-digit",
|
|
283
|
+
minute: "2-digit",
|
|
284
|
+
hour12: false, // 24-hour format
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return formatted;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// dd/mm/yyyy in local time
|
|
291
|
+
function formatDateDDMMYYYYLocal(dateString: string) {
|
|
292
|
+
if (!dateString) return "-";
|
|
293
|
+
|
|
294
|
+
const date = new Date(dateString);
|
|
295
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
296
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
297
|
+
const year = date.getFullYear();
|
|
298
|
+
|
|
299
|
+
return `${day}/${month}/${year}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function formatNature(value: string): string {
|
|
303
|
+
if (!value) return "";
|
|
304
|
+
return value
|
|
305
|
+
.split("_")
|
|
306
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
307
|
+
.join(" ");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function replaceMatch(str: string, match: string, replace: string) {
|
|
311
|
+
return str.replace(new RegExp(match, "g"), replace);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function setRouteParams(...args: Array<Record<string, string>>) {
|
|
315
|
+
const params: Record<string, string> = {};
|
|
316
|
+
|
|
317
|
+
args.forEach((arg) => {
|
|
318
|
+
Object.entries(arg).forEach(([key, value]) => {
|
|
319
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
320
|
+
params[key] = value;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return params;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function toOrdinal(n: number): string {
|
|
329
|
+
const s = ["th", "st", "nd", "rd"];
|
|
330
|
+
const v = n % 100;
|
|
331
|
+
return n + (s[(v - 20) % 10] || s[v] || s[0]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function formatDateISO8601(date: Date): string {
|
|
335
|
+
const pad = (n: number) => n.toString().padStart(2, '0');
|
|
336
|
+
const year = date.getFullYear();
|
|
337
|
+
const month = pad(date.getMonth() + 1);
|
|
338
|
+
const day = pad(date.getDate());
|
|
339
|
+
const hours = pad(date.getHours());
|
|
340
|
+
const minutes = pad(date.getMinutes());
|
|
341
|
+
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function isValidBaseURL(baseURL: string | null) {
|
|
345
|
+
if (!baseURL) return true;
|
|
346
|
+
|
|
347
|
+
// Check if the baseURL starts with http:// or https://
|
|
348
|
+
if (!baseURL.startsWith("http://") && !baseURL.startsWith("https://")) {
|
|
349
|
+
return 'Base URL should start with "https://" or "http://"';
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const url = new URL(baseURL);
|
|
354
|
+
|
|
355
|
+
// Reject if there's any path beyond root
|
|
356
|
+
if (url.pathname !== "/" && url.pathname !== "") {
|
|
357
|
+
return "Invalid Base URL";
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Also reject if there’s a query or hash
|
|
361
|
+
if (url.search || url.hash) {
|
|
362
|
+
return "Invalid Base URL";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return ["http:", "https:"].includes(url.protocol)
|
|
366
|
+
? true
|
|
367
|
+
: "Invalid Base URL";
|
|
368
|
+
} catch (e: any) {
|
|
369
|
+
return "Invalid Base URL";
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function formatCamelCaseToWords(key: string){
|
|
374
|
+
if(!key) return "";
|
|
375
|
+
return key
|
|
376
|
+
.replace(/([A-Z])/g, ' $1')
|
|
377
|
+
.replace(/^./, str => str.toUpperCase());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
function calculateRemainingTime(
|
|
382
|
+
item: Date | string | number | null | undefined
|
|
383
|
+
) {
|
|
384
|
+
if (!item) return -1;
|
|
385
|
+
const _date = new Date(item);
|
|
386
|
+
if (isNaN(_date.getTime())) return -1;
|
|
387
|
+
const creationTime = _date.getTime();
|
|
388
|
+
const currentTime = Date.now();
|
|
389
|
+
const differenceInMillis = currentTime - creationTime;
|
|
390
|
+
const differenceInSeconds = Math.floor(differenceInMillis / 1000);
|
|
391
|
+
const desiredDurationInSeconds = 24 * 60 * 60;
|
|
392
|
+
const remainingTimeInSeconds = desiredDurationInSeconds - differenceInSeconds;
|
|
393
|
+
console.log(remainingTimeInSeconds);
|
|
394
|
+
return remainingTimeInSeconds;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const formatTime = (seconds: number) => {
|
|
398
|
+
if (seconds <= 0 || isNaN(seconds)) return "00h 00m";
|
|
399
|
+
const hours = Math.floor(seconds / 3600);
|
|
400
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
401
|
+
return `${String(hours).padStart(2, "0")}h ${String(minutes).padStart(
|
|
402
|
+
2,
|
|
403
|
+
"0"
|
|
404
|
+
)}m`;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
requiredRule,
|
|
409
|
+
emailRule,
|
|
410
|
+
passwordRule,
|
|
411
|
+
confirmPasswordRule,
|
|
412
|
+
validateEmail,
|
|
413
|
+
validatePassword,
|
|
414
|
+
back,
|
|
415
|
+
debounce,
|
|
416
|
+
insertBetween,
|
|
417
|
+
getNameInitials,
|
|
418
|
+
getDimensions,
|
|
419
|
+
validateWord,
|
|
420
|
+
getCountries,
|
|
421
|
+
formatter,
|
|
422
|
+
formatNumber,
|
|
423
|
+
validateDate,
|
|
424
|
+
minOneMonthAdvance,
|
|
425
|
+
requireListRule,
|
|
426
|
+
convertPermissionsToArray,
|
|
427
|
+
redirect,
|
|
428
|
+
computeTieredCost,
|
|
429
|
+
getColorStatus,
|
|
430
|
+
getOrigin,
|
|
431
|
+
search,
|
|
432
|
+
formatDate,
|
|
433
|
+
UTCToLocalTIme,
|
|
434
|
+
formatNature,
|
|
435
|
+
replaceMatch,
|
|
436
|
+
setRouteParams,
|
|
437
|
+
toOrdinal,
|
|
438
|
+
formatDateISO8601,
|
|
439
|
+
isValidBaseURL,
|
|
440
|
+
formatCamelCaseToWords,
|
|
441
|
+
calculateRemainingTime,
|
|
442
|
+
formatTime,
|
|
443
|
+
formatDateDDMMYYYYLocal
|
|
444
|
+
};
|
|
445
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default function useVerification() {
|
|
2
|
+
function getVerifications({
|
|
3
|
+
status = "pending",
|
|
4
|
+
type = "user-invite",
|
|
5
|
+
search = "",
|
|
6
|
+
page = 1,
|
|
7
|
+
email = "",
|
|
8
|
+
app = ""
|
|
9
|
+
} = {}): Promise<{
|
|
10
|
+
items: TMiniVerification[];
|
|
11
|
+
pages: number;
|
|
12
|
+
pageRange: string;
|
|
13
|
+
}> {
|
|
14
|
+
return useNuxtApp().$api("/api/verifications", {
|
|
15
|
+
method: "GET",
|
|
16
|
+
query: { status, search, page, type, email, app },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function cancelUserInvitation(id: string) {
|
|
21
|
+
console.log("Calling API to cancel:", id);
|
|
22
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
23
|
+
`/api/verifications/${id}/cancel`,
|
|
24
|
+
{
|
|
25
|
+
method: "PUT",
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
getVerifications,
|
|
32
|
+
cancelUserInvitation,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export default function () {
|
|
2
|
+
type TVisitorSelection = {
|
|
3
|
+
label: string;
|
|
4
|
+
value: TVisitorType;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const visitorSelection: TVisitorSelection[] =[
|
|
8
|
+
// { label: "Guest", value: "guest" },
|
|
9
|
+
{ label: "Contractor", value: "contractor" },
|
|
10
|
+
{ label: "Walk-In", value: "walk-in" },
|
|
11
|
+
{ label: "Delivery", value: "delivery" },
|
|
12
|
+
{ label: "Pick-Up", value: "pick-up" },
|
|
13
|
+
{ label: "Drop-Off", value: "drop-off" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const typeFieldMap: Record<TVisitorType, (keyof TVisitorPayload)[]> = {
|
|
17
|
+
guest: ['name', 'nric', 'contact', 'company', 'block', 'level', 'unit' , 'unitName', 'remarks'],
|
|
18
|
+
contractor: ['contractorType', 'name', 'nric', 'company', 'contact', 'plateNumber', 'block', 'level', 'unit', 'unitName', 'remarks'],
|
|
19
|
+
'walk-in': ['name', 'company', 'nric', 'contact', 'block', 'level', 'unit' , 'unitName', 'remarks'],
|
|
20
|
+
delivery: ['attachments', 'name', 'deliveryType', 'company', 'nric', 'contact', 'plateNumber', 'block', 'level', 'unit' , 'unitName', 'remarks'],
|
|
21
|
+
'pick-up': ['plateNumber', 'block', 'remarks'],
|
|
22
|
+
'drop-off': ['plateNumber', 'block', 'remarks'],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const contractorTypes = [
|
|
26
|
+
{ title: "Estate Contractor", value: "estate-contractor" },
|
|
27
|
+
{ title: "Home Contractor", value: "home-contractor" },
|
|
28
|
+
{ title: "Property Agent", value: "property-agent" },
|
|
29
|
+
{ title: "House Mover", value: "house-mover" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
type GetVisitorsParams = {
|
|
34
|
+
page?: number
|
|
35
|
+
limit?: number
|
|
36
|
+
sort?: "asc" | "desc"
|
|
37
|
+
search?: string
|
|
38
|
+
org?: string
|
|
39
|
+
site?: string
|
|
40
|
+
dateTo?: string
|
|
41
|
+
dateFrom?: string
|
|
42
|
+
type?: string
|
|
43
|
+
status?: string
|
|
44
|
+
checkedOut?: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function getVisitors({
|
|
48
|
+
page = 1,
|
|
49
|
+
limit = 10,
|
|
50
|
+
sort = "asc",
|
|
51
|
+
search = "",
|
|
52
|
+
org = "",
|
|
53
|
+
site = "",
|
|
54
|
+
dateTo = "",
|
|
55
|
+
dateFrom = "",
|
|
56
|
+
type = "",
|
|
57
|
+
status = "registered",
|
|
58
|
+
// status = "registered"
|
|
59
|
+
checkedOut
|
|
60
|
+
}: GetVisitorsParams = {}) {
|
|
61
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
62
|
+
"/api/visitor-transactions",
|
|
63
|
+
{
|
|
64
|
+
method: "GET",
|
|
65
|
+
query: {
|
|
66
|
+
page,
|
|
67
|
+
limit,
|
|
68
|
+
sort,
|
|
69
|
+
search,
|
|
70
|
+
org,
|
|
71
|
+
site,
|
|
72
|
+
dateTo,
|
|
73
|
+
dateFrom,
|
|
74
|
+
type,
|
|
75
|
+
status,
|
|
76
|
+
checkedOut
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function createVisitor(payload: Partial<TVisitorPayload>) {
|
|
83
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
84
|
+
"/api/visitor-transactions",
|
|
85
|
+
{
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: payload,
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function updateVisitor(_id: string, payload: Partial<TVisitorPayload>) {
|
|
93
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
94
|
+
`/api/visitor-transactions/id/${_id}`,
|
|
95
|
+
{
|
|
96
|
+
method: "PUT",
|
|
97
|
+
body: payload,
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function deleteVisitor(_id: string) {
|
|
103
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
104
|
+
`/api/visitor-transactions/id/${_id}`,
|
|
105
|
+
{
|
|
106
|
+
method: "DELETE",
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
typeFieldMap,
|
|
113
|
+
visitorSelection,
|
|
114
|
+
contractorTypes,
|
|
115
|
+
createVisitor,
|
|
116
|
+
getVisitors,
|
|
117
|
+
updateVisitor,
|
|
118
|
+
deleteVisitor,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export default function useWorkOrder() {
|
|
2
|
+
const workOrders = useState<Array<TWorkOrder>>("workOrders", () => []);
|
|
3
|
+
const page = useState("page", () => 1);
|
|
4
|
+
const pages = useState("pages", () => 0);
|
|
5
|
+
const pageRange = useState("pageRange", () => "-- - -- of --");
|
|
6
|
+
const workOrder = useState<TWorkOrder>("workOrder", () => ({
|
|
7
|
+
_id: "",
|
|
8
|
+
category: "",
|
|
9
|
+
categoryInfo: "",
|
|
10
|
+
subject: "",
|
|
11
|
+
description: "",
|
|
12
|
+
createdBy: "",
|
|
13
|
+
service: "",
|
|
14
|
+
provider: "",
|
|
15
|
+
organization: "",
|
|
16
|
+
site: "",
|
|
17
|
+
createdByName: "",
|
|
18
|
+
assignee: "",
|
|
19
|
+
location: "",
|
|
20
|
+
attachments: [],
|
|
21
|
+
feedback: "",
|
|
22
|
+
status: "",
|
|
23
|
+
createdAt: "",
|
|
24
|
+
updatedAt: "",
|
|
25
|
+
deletedAt: "",
|
|
26
|
+
highPriority: false,
|
|
27
|
+
block: "",
|
|
28
|
+
level: "",
|
|
29
|
+
unit: "",
|
|
30
|
+
serviceProvider: "",
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
async function getWorkOrders({
|
|
34
|
+
page = 1,
|
|
35
|
+
site = "",
|
|
36
|
+
status = "to-do",
|
|
37
|
+
search = "",
|
|
38
|
+
limit = 10,
|
|
39
|
+
category = "",
|
|
40
|
+
} = {}) {
|
|
41
|
+
try {
|
|
42
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
43
|
+
`/api/work-orders/site/${site}/status/${status}`,
|
|
44
|
+
{
|
|
45
|
+
method: "GET",
|
|
46
|
+
query: { page, search, limit, category },
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getWorkOrderById(id: string) {
|
|
55
|
+
return useNuxtApp().$api<TWorkOrder>(`/api/work-orders/${id}`, {
|
|
56
|
+
method: "GET",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createWorkOrder(payload: TWorkOrderCreate) {
|
|
61
|
+
return useNuxtApp().$api<Record<string, any>>("/api/work-orders", {
|
|
62
|
+
method: "POST",
|
|
63
|
+
body: payload,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function updateWorkOrder(id: string, payload: object) {
|
|
68
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/work-orders/${id}`, {
|
|
69
|
+
method: "PUT",
|
|
70
|
+
body: payload,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
workOrders,
|
|
76
|
+
workOrder,
|
|
77
|
+
page,
|
|
78
|
+
pages,
|
|
79
|
+
pageRange,
|
|
80
|
+
createWorkOrder,
|
|
81
|
+
getWorkOrders,
|
|
82
|
+
getWorkOrderById,
|
|
83
|
+
updateWorkOrder,
|
|
84
|
+
};
|
|
85
|
+
}
|