big updates
This commit is contained in:
parent
34f2c110d6
commit
03a230b8f7
14 changed files with 2417 additions and 242 deletions
311
frontend/src/components/modals/OnSiteMeetingModal.vue
Normal file
311
frontend/src/components/modals/OnSiteMeetingModal.vue
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
<template>
|
||||
<!-- New Meeting Creation Modal -->
|
||||
<Modal
|
||||
v-model:visible="showModal"
|
||||
:options="modalOptions"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<template #title>Schedule New On-Site Meeting</template>
|
||||
<div class="new-meeting-form">
|
||||
<div class="form-group">
|
||||
<label for="meeting-address">Address: <span class="required">*</span></label>
|
||||
<div class="address-input-group">
|
||||
<InputText
|
||||
id="meeting-address"
|
||||
v-model="formData.address"
|
||||
class="address-input"
|
||||
placeholder="Enter meeting address"
|
||||
@input="validateForm"
|
||||
/>
|
||||
<Button
|
||||
label="Search"
|
||||
icon="pi pi-search"
|
||||
size="small"
|
||||
:disabled="!formData.address.trim()"
|
||||
@click="searchAddress"
|
||||
class="search-btn"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="meeting-notes">Notes (Optional):</label>
|
||||
<Textarea
|
||||
id="meeting-notes"
|
||||
v-model="formData.notes"
|
||||
class="w-full"
|
||||
placeholder="Additional notes..."
|
||||
rows="3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<!-- Address Search Results Modal -->
|
||||
<Modal
|
||||
v-model:visible="showAddressSearchModal"
|
||||
:options="searchModalOptions"
|
||||
@confirm="closeAddressSearch"
|
||||
>
|
||||
<template #title>Address Search Results</template>
|
||||
<div class="address-search-results">
|
||||
<div v-if="addressSearchResults.length === 0" class="no-results">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<p>No addresses found matching your search.</p>
|
||||
</div>
|
||||
<div v-else class="results-list">
|
||||
<div
|
||||
v-for="(address, index) in addressSearchResults"
|
||||
:key="index"
|
||||
class="address-result-item"
|
||||
@click="selectAddress(address)"
|
||||
>
|
||||
<i class="pi pi-map-marker"></i>
|
||||
<span>{{ address }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from "vue";
|
||||
import Modal from "../common/Modal.vue";
|
||||
import InputText from "primevue/inputtext";
|
||||
import Textarea from "primevue/textarea";
|
||||
import Button from "primevue/button";
|
||||
import { useNotificationStore } from "../../stores/notifications-primevue";
|
||||
import Api from "../../api";
|
||||
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
initialAddress: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["update:visible", "confirm", "cancel"]);
|
||||
|
||||
// Local state
|
||||
const showModal = computed({
|
||||
get() {
|
||||
return props.visible;
|
||||
},
|
||||
set(value) {
|
||||
emit("update:visible", value);
|
||||
},
|
||||
});
|
||||
|
||||
const showAddressSearchModal = ref(false);
|
||||
const addressSearchResults = ref([]);
|
||||
const isFormValid = ref(false);
|
||||
|
||||
// Form data
|
||||
const formData = ref({
|
||||
address: "",
|
||||
notes: "",
|
||||
});
|
||||
|
||||
// Form validation state
|
||||
|
||||
// Modal options
|
||||
const modalOptions = computed(() => ({
|
||||
maxWidth: "500px",
|
||||
persistent: true,
|
||||
confirmButtonText: "Create",
|
||||
cancelButtonText: "Cancel",
|
||||
confirmButtonColor: "primary",
|
||||
showConfirmButton: true,
|
||||
showCancelButton: true,
|
||||
confirmButtonProps: {
|
||||
disabled: !isFormValid.value,
|
||||
},
|
||||
}));
|
||||
|
||||
const searchModalOptions = computed(() => ({
|
||||
maxWidth: "600px",
|
||||
showCancelButton: false,
|
||||
confirmButtonText: "Close",
|
||||
confirmButtonColor: "primary",
|
||||
}));
|
||||
|
||||
// Methods
|
||||
const validateForm = () => {
|
||||
const hasValidAddress = formData.value.address && formData.value.address.trim().length > 0;
|
||||
isFormValid.value = hasValidAddress;
|
||||
};
|
||||
|
||||
const searchAddress = async () => {
|
||||
const searchTerm = formData.value.address.trim();
|
||||
if (!searchTerm) return;
|
||||
|
||||
try {
|
||||
const results = await Api.searchAddresses(searchTerm);
|
||||
console.info("Address search results:", results);
|
||||
|
||||
// Ensure results is always an array
|
||||
// const safeResults = Array.isArray(results) ? results : [];
|
||||
addressSearchResults.value = results;
|
||||
|
||||
if (results.length === 0) {
|
||||
notificationStore.addWarning("No addresses found matching your search criteria.");
|
||||
} else {
|
||||
showAddressSearchModal.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error searching addresses:", error);
|
||||
addressSearchResults.value = [];
|
||||
notificationStore.addError("Failed to search addresses. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
const selectAddress = (address) => {
|
||||
formData.value.address = address;
|
||||
showAddressSearchModal.value = false;
|
||||
validateForm();
|
||||
};
|
||||
|
||||
const closeAddressSearch = () => {
|
||||
showAddressSearchModal.value = false;
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!isFormValid.value) return;
|
||||
|
||||
emit("confirm", { ...formData.value });
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit("cancel");
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
address: props.initialAddress || "",
|
||||
notes: "",
|
||||
};
|
||||
validateForm();
|
||||
};
|
||||
|
||||
// Watch for prop changes
|
||||
watch(
|
||||
() => props.initialAddress,
|
||||
(newAddress) => {
|
||||
formData.value.address = newAddress || "";
|
||||
validateForm();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(isVisible) => {
|
||||
if (isVisible) {
|
||||
resetForm();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Initial validation
|
||||
validateForm();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.new-meeting-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.address-input-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.address-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.address-search-results {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-results i {
|
||||
font-size: 2em;
|
||||
color: #f39c12;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.results-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.address-result-item {
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.address-result-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #2196f3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.address-result-item i {
|
||||
color: #2196f3;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.address-result-item span {
|
||||
flex: 1;
|
||||
font-size: 0.9em;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue