@7365admin1/layer-common 1.9.0 → 1.10.1
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/CHANGELOG.md +12 -0
- package/components/AcceptDialog.vue +44 -0
- package/components/AccessCardAddForm.vue +101 -13
- package/components/AccessManagement.vue +130 -47
- package/components/AddSupplyForm.vue +165 -0
- package/components/AreaChecklistHistoryLogs.vue +235 -0
- package/components/AreaChecklistHistoryMain.vue +176 -0
- package/components/AreaFormDialog.vue +266 -0
- package/components/AreaMain.vue +841 -0
- package/components/AttendanceCheckInOutDialog.vue +416 -0
- package/components/AttendanceDetailsDialog.vue +184 -0
- package/components/AttendanceMain.vue +155 -0
- package/components/AttendanceMapSearchDialog.vue +393 -0
- package/components/AttendanceSettingsDialog.vue +398 -0
- package/components/BuildingManagement/buildings.vue +5 -5
- package/components/BuildingManagement/units.vue +5 -5
- package/components/ChecklistItemRow.vue +54 -0
- package/components/CheckoutItemMain.vue +705 -0
- package/components/CleaningScheduleMain.vue +271 -0
- package/components/DocumentManagement.vue +8 -9
- package/components/EntryPass/QrTemplatePreview.vue +104 -0
- package/components/EntryPassMain.vue +252 -200
- package/components/HygieneUpdateMoreAction.vue +238 -0
- package/components/IncidentReport/Authorities.vue +226 -0
- package/components/IncidentReport/IncidentInformation.vue +258 -0
- package/components/IncidentReport/affectedEntities.vue +167 -0
- package/components/InvitationMain.vue +19 -17
- package/components/ManageChecklistMain.vue +384 -0
- package/components/MemberMain.vue +48 -20
- package/components/MyAttendanceMain.vue +224 -0
- package/components/OnlineFormsConfiguration.vue +9 -2
- package/components/PasswordConfirmation.vue +95 -0
- package/components/PhotoUpload.vue +410 -0
- package/components/RolePermissionMain.vue +17 -15
- package/components/ScheduleAreaMain.vue +313 -0
- package/components/ScheduleTaskAreaFormDialog.vue +144 -0
- package/components/ScheduleTaskAreaUpdateMoreAction.vue +109 -0
- package/components/ScheduleTaskForm.vue +471 -0
- package/components/ScheduleTaskMain.vue +345 -0
- package/components/ScheduleTastTicketMain.vue +182 -0
- package/components/ServiceProviderMain.vue +27 -7
- package/components/StockCard.vue +191 -0
- package/components/SupplyManagementMain.vue +557 -0
- package/components/TableHygiene.vue +617 -0
- package/components/UnitMain.vue +451 -0
- package/components/VisitorManagement.vue +28 -15
- package/composables/useAccessManagement.ts +90 -0
- package/composables/useAreaPermission.ts +51 -0
- package/composables/useAreas.ts +99 -0
- package/composables/useAttendance.ts +89 -0
- package/composables/useAttendancePermission.ts +68 -0
- package/composables/useBuilding.ts +2 -2
- package/composables/useBuildingUnit.ts +2 -2
- package/composables/useCard.ts +2 -0
- package/composables/useCheckout.ts +61 -0
- package/composables/useCheckoutPermission.ts +80 -0
- package/composables/useCleaningPermission.ts +229 -0
- package/composables/useCleaningSchedulePermission.ts +58 -0
- package/composables/useCleaningSchedules.ts +233 -0
- package/composables/useCountry.ts +8 -0
- package/composables/useDOBEntries.ts +13 -0
- package/composables/useDashboardData.ts +2 -2
- package/composables/useDocument.ts +3 -2
- package/composables/useFeedback.ts +1 -1
- package/composables/useFile.ts +4 -6
- package/composables/useLocation.ts +78 -0
- package/composables/useOnlineForm.ts +16 -9
- package/composables/usePeople.ts +87 -72
- package/composables/useQR.ts +29 -0
- package/composables/useRole.ts +3 -2
- package/composables/useScheduleTask.ts +89 -0
- package/composables/useScheduleTaskArea.ts +85 -0
- package/composables/useScheduleTaskPermission.ts +68 -0
- package/composables/useSiteEntryPassSettings.ts +4 -15
- package/composables/useStock.ts +45 -0
- package/composables/useSupply.ts +63 -0
- package/composables/useSupplyPermission.ts +92 -0
- package/composables/useUnitPermission.ts +51 -0
- package/composables/useUnits.ts +82 -0
- package/composables/useWebUsb.ts +389 -0
- package/composables/useWorkOrder.ts +1 -1
- package/nuxt.config.ts +3 -0
- package/package.json +4 -1
- package/types/area.d.ts +22 -0
- package/types/attendance.d.ts +38 -0
- package/types/checkout-item.d.ts +27 -0
- package/types/cleaner-schedule.d.ts +54 -0
- package/types/location.d.ts +42 -0
- package/types/schedule-task.d.ts +18 -0
- package/types/stock.d.ts +16 -0
- package/types/supply.d.ts +11 -0
- package/types/verification.d.ts +1 -1
- package/utils/acm-crypto.ts +30 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters align="center" justify="center">
|
|
3
|
+
<v-col cols="12" lg="12">
|
|
4
|
+
<TableMain
|
|
5
|
+
title="Attendance Records"
|
|
6
|
+
:items="items"
|
|
7
|
+
:headers="headers"
|
|
8
|
+
:loading="loading"
|
|
9
|
+
:show-header="true"
|
|
10
|
+
v-model:page="page"
|
|
11
|
+
:pages="pages"
|
|
12
|
+
:pageRange="pageRange"
|
|
13
|
+
no-data-text="No attendance records found."
|
|
14
|
+
@refresh="getAttendancesRefresh"
|
|
15
|
+
@row-click="handleRowClick"
|
|
16
|
+
>
|
|
17
|
+
<template #actions>
|
|
18
|
+
<v-btn
|
|
19
|
+
v-if="canManageAttendanceSettings"
|
|
20
|
+
variant="flat"
|
|
21
|
+
color="black"
|
|
22
|
+
class="text-none"
|
|
23
|
+
@click="dialogShowSettings = true"
|
|
24
|
+
>
|
|
25
|
+
<v-icon class="mr-2">mdi-cog</v-icon>
|
|
26
|
+
Settings
|
|
27
|
+
</v-btn>
|
|
28
|
+
</template>
|
|
29
|
+
<template #item.checkInTimestamp="{ item }">
|
|
30
|
+
<span>{{ formatDate(item.checkInTimestamp) }}</span>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template #item.checkOutTimestamp="{ item }">
|
|
34
|
+
<span>{{
|
|
35
|
+
item.checkOutTimestamp ? formatDate(item.checkOutTimestamp) : "N/A"
|
|
36
|
+
}}</span>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<template #item.totalHours="{ item }">
|
|
40
|
+
<span>{{ calculateTotalHours(item) }}</span>
|
|
41
|
+
</template>
|
|
42
|
+
</TableMain>
|
|
43
|
+
</v-col>
|
|
44
|
+
</v-row>
|
|
45
|
+
|
|
46
|
+
<AttendanceDetailsDialog
|
|
47
|
+
v-model="dialogShowMoreActions"
|
|
48
|
+
:attendance-id="selectedAttendanceId"
|
|
49
|
+
@close="dialogShowMoreActions = false"
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<AttendanceSettingsDialog
|
|
53
|
+
v-model="dialogShowSettings"
|
|
54
|
+
:site="props.site"
|
|
55
|
+
@saved="onSettingsSaved"
|
|
56
|
+
@close="dialogShowSettings = false"
|
|
57
|
+
/>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<script setup lang="ts">
|
|
61
|
+
import useAttendance from "../composables/useAttendance";
|
|
62
|
+
import { useAttendancePermission } from "../composables/useAttendancePermission";
|
|
63
|
+
|
|
64
|
+
const props = defineProps({
|
|
65
|
+
orgId: { type: String, default: "" },
|
|
66
|
+
site: { type: String, default: "" },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const {
|
|
70
|
+
canViewAllAttendance,
|
|
71
|
+
canViewAttendanceDetails,
|
|
72
|
+
canManageAttendanceSettings,
|
|
73
|
+
} = useAttendancePermission();
|
|
74
|
+
|
|
75
|
+
const submitting = ref(false);
|
|
76
|
+
const page = ref(1);
|
|
77
|
+
const pages = ref(0);
|
|
78
|
+
const pageRange = ref("-- - -- of --");
|
|
79
|
+
const searchInput = ref("");
|
|
80
|
+
|
|
81
|
+
const items = ref<Array<Record<string, any>>>([]);
|
|
82
|
+
|
|
83
|
+
const headers = [
|
|
84
|
+
{ title: "Cleaner", value: "userName" },
|
|
85
|
+
{ title: "Check In", value: "checkInTimestamp" },
|
|
86
|
+
{ title: "Check Out", value: "checkOutTimestamp" },
|
|
87
|
+
{ title: "Total Hours", value: "totalHours" },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const { getAttendances } = useAttendance();
|
|
91
|
+
const { formatDate } = useUtils();
|
|
92
|
+
|
|
93
|
+
function calculateTotalHours(attendance: any): string {
|
|
94
|
+
const checkInTime =
|
|
95
|
+
attendance.checkInTimestamp || attendance.checkIn?.timestamp;
|
|
96
|
+
const checkOutTime =
|
|
97
|
+
attendance.checkOutTimestamp || attendance.checkOut?.timestamp;
|
|
98
|
+
|
|
99
|
+
if (!checkInTime) return "N/A";
|
|
100
|
+
if (!checkOutTime) return "In Progress";
|
|
101
|
+
|
|
102
|
+
const checkIn = new Date(checkInTime);
|
|
103
|
+
const checkOut = new Date(checkOutTime);
|
|
104
|
+
const diffMs = checkOut.getTime() - checkIn.getTime();
|
|
105
|
+
const diffHours = diffMs / (1000 * 60 * 60);
|
|
106
|
+
|
|
107
|
+
return `${diffHours.toFixed(2)} hrs`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const {
|
|
111
|
+
data: getAttendancesReq,
|
|
112
|
+
refresh: getAttendancesRefresh,
|
|
113
|
+
pending: loading,
|
|
114
|
+
} = await useLazyAsyncData(
|
|
115
|
+
"get-my-attendances",
|
|
116
|
+
() =>
|
|
117
|
+
getAttendances({
|
|
118
|
+
page: page.value,
|
|
119
|
+
search: searchInput.value,
|
|
120
|
+
site: props.site,
|
|
121
|
+
}),
|
|
122
|
+
{
|
|
123
|
+
watch: [page, searchInput, () => props.site],
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
watchEffect(() => {
|
|
128
|
+
if (getAttendancesReq.value) {
|
|
129
|
+
items.value = getAttendancesReq.value.items.map((item: any) => ({
|
|
130
|
+
...item,
|
|
131
|
+
totalHours: calculateTotalHours(item),
|
|
132
|
+
}));
|
|
133
|
+
pages.value = getAttendancesReq.value.pages;
|
|
134
|
+
pageRange.value = getAttendancesReq.value.pageRange;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const dialogShowMoreActions = ref(false);
|
|
139
|
+
const dialogShowSettings = ref(false);
|
|
140
|
+
const selectedAttendanceId = ref("");
|
|
141
|
+
|
|
142
|
+
async function handleRowClick(data: any) {
|
|
143
|
+
const id = (data?.item as any)?._id;
|
|
144
|
+
if (!id) return;
|
|
145
|
+
|
|
146
|
+
selectedAttendanceId.value = id;
|
|
147
|
+
dialogShowMoreActions.value = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const onSettingsSaved = async (payload: any) => {
|
|
151
|
+
console.log("Settings saved:", payload);
|
|
152
|
+
// TODO: Implement API call to save settings
|
|
153
|
+
await getAttendancesRefresh();
|
|
154
|
+
};
|
|
155
|
+
</script>
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog v-model="showDialog" max-width="500" persistent>
|
|
3
|
+
<v-card>
|
|
4
|
+
<v-toolbar>
|
|
5
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
6
|
+
<span class="font-weight-bold text-h6">Search Location</span>
|
|
7
|
+
</v-row>
|
|
8
|
+
</v-toolbar>
|
|
9
|
+
|
|
10
|
+
<v-card-text class="pa-6">
|
|
11
|
+
<v-form ref="formRef" @submit.prevent="handleSearch">
|
|
12
|
+
<v-row dense>
|
|
13
|
+
<v-col cols="12" md="6">
|
|
14
|
+
<InputLabel title="Country (Optional)" />
|
|
15
|
+
<v-autocomplete
|
|
16
|
+
v-model="formData.country"
|
|
17
|
+
:items="countryList"
|
|
18
|
+
:loading="loadingCountries"
|
|
19
|
+
density="comfortable"
|
|
20
|
+
placeholder="Select country"
|
|
21
|
+
clearable
|
|
22
|
+
hide-details="auto"
|
|
23
|
+
@update:model-value="handleCountryChange"
|
|
24
|
+
/>
|
|
25
|
+
</v-col>
|
|
26
|
+
|
|
27
|
+
<v-col cols="12" md="6">
|
|
28
|
+
<InputLabel title="Postal Code" required />
|
|
29
|
+
<v-text-field
|
|
30
|
+
v-model="formData.postalCode"
|
|
31
|
+
clearable
|
|
32
|
+
:loading="postalLoading"
|
|
33
|
+
density="comfortable"
|
|
34
|
+
placeholder="Enter postal code"
|
|
35
|
+
:rules="postalCodeRules"
|
|
36
|
+
hide-details="auto"
|
|
37
|
+
@update:model-value="handlePostalCodeInput"
|
|
38
|
+
@click:clear="handlePostalCodeClear"
|
|
39
|
+
/>
|
|
40
|
+
</v-col>
|
|
41
|
+
|
|
42
|
+
<v-col cols="12" md="6">
|
|
43
|
+
<InputLabel title="City (Optional)" />
|
|
44
|
+
<v-text-field
|
|
45
|
+
v-model="formData.city"
|
|
46
|
+
:loading="postalLoading"
|
|
47
|
+
density="comfortable"
|
|
48
|
+
placeholder="Enter city"
|
|
49
|
+
hide-details="auto"
|
|
50
|
+
@update:model-value="handleCityChange"
|
|
51
|
+
/>
|
|
52
|
+
</v-col>
|
|
53
|
+
|
|
54
|
+
<v-col cols="12" md="6">
|
|
55
|
+
<InputLabel title="Address (Optional)" />
|
|
56
|
+
<v-text-field
|
|
57
|
+
v-model="formData.address"
|
|
58
|
+
:loading="postalLoading"
|
|
59
|
+
density="comfortable"
|
|
60
|
+
placeholder="Enter address"
|
|
61
|
+
hide-details="auto"
|
|
62
|
+
@update:model-value="handleAddressChange"
|
|
63
|
+
/>
|
|
64
|
+
</v-col>
|
|
65
|
+
</v-row>
|
|
66
|
+
</v-form>
|
|
67
|
+
</v-card-text>
|
|
68
|
+
|
|
69
|
+
<v-toolbar class="pa-0" density="compact">
|
|
70
|
+
<v-row no-gutters>
|
|
71
|
+
<v-col cols="6" class="pa-0">
|
|
72
|
+
<v-btn
|
|
73
|
+
block
|
|
74
|
+
variant="text"
|
|
75
|
+
class="text-none"
|
|
76
|
+
size="large"
|
|
77
|
+
height="56"
|
|
78
|
+
:disabled="searching"
|
|
79
|
+
@click="handleClose"
|
|
80
|
+
>
|
|
81
|
+
Cancel
|
|
82
|
+
</v-btn>
|
|
83
|
+
</v-col>
|
|
84
|
+
<v-col cols="6" class="pa-0">
|
|
85
|
+
<v-btn
|
|
86
|
+
block
|
|
87
|
+
variant="flat"
|
|
88
|
+
color="black"
|
|
89
|
+
class="text-none font-weight-bold rounded-0"
|
|
90
|
+
height="56"
|
|
91
|
+
size="large"
|
|
92
|
+
:loading="searching"
|
|
93
|
+
:disabled="!formData.postalCode"
|
|
94
|
+
@click="handleSearch"
|
|
95
|
+
>
|
|
96
|
+
<v-icon class="mr-2">mdi-magnify</v-icon>
|
|
97
|
+
Search
|
|
98
|
+
</v-btn>
|
|
99
|
+
</v-col>
|
|
100
|
+
</v-row>
|
|
101
|
+
</v-toolbar>
|
|
102
|
+
</v-card>
|
|
103
|
+
</v-dialog>
|
|
104
|
+
|
|
105
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
106
|
+
</template>
|
|
107
|
+
That is
|
|
108
|
+
|
|
109
|
+
<script setup lang="ts">
|
|
110
|
+
const showDialog = defineModel({ type: Boolean, default: false });
|
|
111
|
+
|
|
112
|
+
const props = defineProps<{
|
|
113
|
+
initialCountry?: string;
|
|
114
|
+
initialPostalCode?: string;
|
|
115
|
+
initialCity?: string;
|
|
116
|
+
initialAddress?: string;
|
|
117
|
+
}>();
|
|
118
|
+
|
|
119
|
+
const emit = defineEmits<{
|
|
120
|
+
locationSelected: [location: TLocationData];
|
|
121
|
+
}>();
|
|
122
|
+
|
|
123
|
+
const { getCountries, getAddressByPostalCode, searchLocationByAddress } =
|
|
124
|
+
useLocation();
|
|
125
|
+
|
|
126
|
+
const formRef = ref<any>(null);
|
|
127
|
+
const searching = ref(false);
|
|
128
|
+
const loadingCountries = ref(false);
|
|
129
|
+
const postalLoading = ref(false);
|
|
130
|
+
|
|
131
|
+
const formData = ref<TLocationFormData>({
|
|
132
|
+
country: props.initialCountry || "",
|
|
133
|
+
postalCode: props.initialPostalCode || "",
|
|
134
|
+
city: props.initialCity || "",
|
|
135
|
+
address: props.initialAddress || "",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const manuallyEdited = ref<TLocationFieldState>({
|
|
139
|
+
country: false,
|
|
140
|
+
city: false,
|
|
141
|
+
address: false,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const autoFilled = ref<TLocationFieldState>({
|
|
145
|
+
country: !!props.initialCountry,
|
|
146
|
+
city: !!props.initialCity,
|
|
147
|
+
address: !!props.initialAddress,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const message = ref("");
|
|
151
|
+
const messageSnackbar = ref(false);
|
|
152
|
+
const messageColor = ref("");
|
|
153
|
+
|
|
154
|
+
const countryList = ref<string[]>([]);
|
|
155
|
+
|
|
156
|
+
let postalCodeTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
157
|
+
|
|
158
|
+
const postalCodeRules = [(v: string) => !!v || "Postal code is required"];
|
|
159
|
+
|
|
160
|
+
onMounted(async () => {
|
|
161
|
+
await loadCountriesList();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
onUnmounted(() => {
|
|
165
|
+
if (postalCodeTimeout) {
|
|
166
|
+
clearTimeout(postalCodeTimeout);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const loadCountriesList = async () => {
|
|
171
|
+
try {
|
|
172
|
+
loadingCountries.value = true;
|
|
173
|
+
countryList.value = await getCountries();
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.error("Failed to load countries:", err);
|
|
176
|
+
showMessage("Failed to load countries list", "error");
|
|
177
|
+
} finally {
|
|
178
|
+
loadingCountries.value = false;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const handleCountryChange = () => {
|
|
183
|
+
manuallyEdited.value.country = true;
|
|
184
|
+
autoFilled.value.country = false;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleCityChange = () => {
|
|
188
|
+
manuallyEdited.value.city = true;
|
|
189
|
+
autoFilled.value.city = false;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleAddressChange = () => {
|
|
193
|
+
manuallyEdited.value.address = true;
|
|
194
|
+
autoFilled.value.address = false;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const handlePostalCodeInput = () => {
|
|
198
|
+
postalLoading.value = true;
|
|
199
|
+
|
|
200
|
+
if (postalCodeTimeout) {
|
|
201
|
+
clearTimeout(postalCodeTimeout);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
postalCodeTimeout = setTimeout(() => {
|
|
205
|
+
fetchAddressFromPostal();
|
|
206
|
+
}, 800);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const handlePostalCodeClear = () => {
|
|
210
|
+
if (postalCodeTimeout) {
|
|
211
|
+
clearTimeout(postalCodeTimeout);
|
|
212
|
+
postalCodeTimeout = null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
postalLoading.value = false;
|
|
216
|
+
formData.value.postalCode = "";
|
|
217
|
+
|
|
218
|
+
if (autoFilled.value.country) {
|
|
219
|
+
formData.value.country = "";
|
|
220
|
+
autoFilled.value.country = false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (autoFilled.value.city) {
|
|
224
|
+
formData.value.city = "";
|
|
225
|
+
autoFilled.value.city = false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (autoFilled.value.address) {
|
|
229
|
+
formData.value.address = "";
|
|
230
|
+
autoFilled.value.address = false;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const fetchAddressFromPostal = async () => {
|
|
235
|
+
if (!formData.value.postalCode) {
|
|
236
|
+
postalLoading.value = false;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
postalLoading.value = true;
|
|
242
|
+
|
|
243
|
+
const result = await getAddressByPostalCode(
|
|
244
|
+
formData.value.postalCode,
|
|
245
|
+
formData.value.country,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (result) {
|
|
249
|
+
const addressData = result.address || {};
|
|
250
|
+
const displayParts =
|
|
251
|
+
result.display_name?.split(",").map((s: string) => s.trim()) || [];
|
|
252
|
+
|
|
253
|
+
const countryName = displayParts[displayParts.length - 1] || "";
|
|
254
|
+
const derivedCity =
|
|
255
|
+
addressData.city ||
|
|
256
|
+
addressData.town ||
|
|
257
|
+
addressData.village ||
|
|
258
|
+
addressData.state ||
|
|
259
|
+
displayParts[displayParts.length - 2] ||
|
|
260
|
+
"";
|
|
261
|
+
|
|
262
|
+
if (!manuallyEdited.value.country && countryName) {
|
|
263
|
+
formData.value.country = countryName;
|
|
264
|
+
autoFilled.value.country = true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!manuallyEdited.value.city && derivedCity) {
|
|
268
|
+
formData.value.city = derivedCity;
|
|
269
|
+
autoFilled.value.city = true;
|
|
270
|
+
}
|
|
271
|
+
if (!manuallyEdited.value.address && result.display_name) {
|
|
272
|
+
formData.value.address = result.display_name;
|
|
273
|
+
autoFilled.value.address = true;
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
if (autoFilled.value.country && !manuallyEdited.value.country) {
|
|
277
|
+
formData.value.country = "";
|
|
278
|
+
autoFilled.value.country = false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (autoFilled.value.city && !manuallyEdited.value.city) {
|
|
282
|
+
formData.value.city = "";
|
|
283
|
+
autoFilled.value.city = false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (autoFilled.value.address && !manuallyEdited.value.address) {
|
|
287
|
+
formData.value.address = "";
|
|
288
|
+
autoFilled.value.address = false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.error("Error fetching address from postal code:", err);
|
|
293
|
+
} finally {
|
|
294
|
+
postalLoading.value = false;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const handleSearch = async () => {
|
|
299
|
+
const { valid } = await formRef.value.validate();
|
|
300
|
+
|
|
301
|
+
if (!valid) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
searching.value = true;
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const item = await searchLocationByAddress({
|
|
309
|
+
postalCode: formData.value.postalCode,
|
|
310
|
+
city: manuallyEdited.value.city ? formData.value.city : undefined,
|
|
311
|
+
address: manuallyEdited.value.address
|
|
312
|
+
? formData.value.address
|
|
313
|
+
: undefined,
|
|
314
|
+
country: formData.value.country,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
if (item) {
|
|
318
|
+
const location: TLocationData = {
|
|
319
|
+
latitude: parseFloat(item.lat),
|
|
320
|
+
longitude: parseFloat(item.lon),
|
|
321
|
+
address: formData.value.address || item.display_name || "",
|
|
322
|
+
city:
|
|
323
|
+
formData.value.city ||
|
|
324
|
+
item.address?.city ||
|
|
325
|
+
item.address?.town ||
|
|
326
|
+
item.address?.village ||
|
|
327
|
+
"",
|
|
328
|
+
country: formData.value.country || item.address?.country || "",
|
|
329
|
+
postalCode: formData.value.postalCode || "",
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
emit("locationSelected", location);
|
|
333
|
+
showMessage("Location found successfully", "success");
|
|
334
|
+
handleClose();
|
|
335
|
+
} else {
|
|
336
|
+
showMessage(
|
|
337
|
+
"Location not found. Please try different search criteria.",
|
|
338
|
+
"error",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
console.error("Error searching location:", err);
|
|
343
|
+
showMessage("Failed to search location. Please try again.", "error");
|
|
344
|
+
} finally {
|
|
345
|
+
searching.value = false;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const handleClose = () => {
|
|
350
|
+
resetForm();
|
|
351
|
+
showDialog.value = false;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const resetForm = () => {
|
|
355
|
+
formData.value = {
|
|
356
|
+
country: props.initialCountry || "",
|
|
357
|
+
postalCode: props.initialPostalCode || "",
|
|
358
|
+
city: props.initialCity || "",
|
|
359
|
+
address: props.initialAddress || "",
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
manuallyEdited.value = {
|
|
363
|
+
country: false,
|
|
364
|
+
city: false,
|
|
365
|
+
address: false,
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
autoFilled.value = {
|
|
369
|
+
country: !!props.initialCountry,
|
|
370
|
+
city: !!props.initialCity,
|
|
371
|
+
address: !!props.initialAddress,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
if (postalCodeTimeout) {
|
|
375
|
+
clearTimeout(postalCodeTimeout);
|
|
376
|
+
postalCodeTimeout = null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
postalLoading.value = false;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const showMessage = (msg: string, color: string = "error") => {
|
|
383
|
+
message.value = msg;
|
|
384
|
+
messageColor.value = color;
|
|
385
|
+
messageSnackbar.value = true;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
watch(showDialog, (newVal) => {
|
|
389
|
+
if (newVal) {
|
|
390
|
+
resetForm();
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
</script>
|