custom_ui/frontend/src/components/clientSubPages/ContactInformationForm.vue

364 lines
7.5 KiB
Vue

<template>
<div class="form-section">
<div class="section-header">
<h3>Contact Information</h3>
</div>
<div class="form-grid">
<div
v-for="(contact, index) in localFormData.contacts"
:key="index"
class="contact-item"
>
<div class="contact-header">
<h4>Contact {{ index + 1 }}</h4>
<Button
v-if="localFormData.contacts.length > 1"
@click="removeContact(index)"
size="small"
severity="danger"
label="Delete"
class="remove-btn"
/>
</div>
<div class="form-rows">
<div class="form-row">
<div class="form-field">
<label :for="`first-name-${index}`">
First Name <span class="required">*</span>
</label>
<InputText
:id="`first-name-${index}`"
v-model="contact.firstName"
:disabled="isSubmitting"
placeholder="Enter first name"
class="w-full"
/>
</div>
<div class="form-field">
<label :for="`last-name-${index}`">
Last Name <span class="required">*</span>
</label>
<InputText
:id="`last-name-${index}`"
v-model="contact.lastName"
:disabled="isSubmitting"
placeholder="Enter last name"
class="w-full"
/>
</div>
<div class="form-field">
<label :for="`contact-role-${index}`">Role</label>
<Select
:id="`contact-role-${index}`"
v-model="contact.contactRole"
:options="roleOptions"
optionLabel="label"
optionValue="value"
:disabled="isSubmitting"
placeholder="Select a role"
class="w-full"
/>
</div>
</div>
<div class="form-row">
<div class="form-field">
<label :for="`email-${index}`">Email</label>
<InputText
:id="`email-${index}`"
v-model="contact.email"
:disabled="isSubmitting"
type="email"
placeholder="email@example.com"
class="w-full"
/>
</div>
<div class="form-field">
<label :for="`phone-number-${index}`">Phone</label>
<InputText
:id="`phone-number-${index}`"
v-model="contact.phoneNumber"
:disabled="isSubmitting"
placeholder="(555) 123-4567"
class="w-full"
@input="formatPhone(index, $event)"
@keydown="handlePhoneKeydown($event, index)"
/>
</div>
<div class="form-field">
<v-checkbox
v-model="contact.isPrimary"
label="Primary Contact"
:disabled="isSubmitting"
@change="setPrimary(index)"
/>
</div>
</div>
</div>
</div>
<div class="form-field full-width">
<Button label="Add another contact" @click="addContact" :disabled="isSubmitting" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted } from "vue";
import InputText from "primevue/inputtext";
import Select from "primevue/select";
import Button from "primevue/button";
const props = defineProps({
formData: {
type: Object,
required: true,
},
isSubmitting: {
type: Boolean,
default: false,
},
isEditMode: {
type: Boolean,
default: false,
},
isNewClientLocked: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:formData"]);
const localFormData = computed({
get: () => {
if (!props.formData.contacts || props.formData.contacts.length === 0) {
props.formData.contacts = [
{
firstName: "",
lastName: "",
phoneNumber: "",
email: "",
contactRole: "",
isPrimary: true,
},
];
}
return props.formData;
},
set: (value) => emit("update:formData", value),
});
const roleOptions = ref([
{ label: "Owner", value: "Owner" },
{ label: "Property Manager", value: "Property Manager" },
{ label: "Tenant", value: "Tenant" },
{ label: "Builder", value: "Builder" },
{ label: "Neighbor", value: "Neighbor" },
{ label: "Family Member", value: "Family Member" },
{ label: "Realtor", value: "Realtor" },
{ label: "Other", value: "Other" },
]);
// Ensure at least one contact
onMounted(() => {
if (!localFormData.value.contacts || localFormData.value.contacts.length === 0) {
localFormData.value.contacts = [
{
firstName: "",
lastName: "",
phoneNumber: "",
email: "",
contactRole: "",
isPrimary: true,
},
];
}
});
const addContact = () => {
localFormData.value.contacts.push({
firstName: "",
lastName: "",
phoneNumber: "",
email: "",
contactRole: "",
isPrimary: false,
});
};
const removeContact = (index) => {
if (localFormData.value.contacts.length > 1) {
const wasPrimary = localFormData.value.contacts[index].isPrimary;
localFormData.value.contacts.splice(index, 1);
if (wasPrimary && localFormData.value.contacts.length > 0) {
localFormData.value.contacts[0].isPrimary = true;
}
}
};
const setPrimary = (index) => {
localFormData.value.contacts.forEach((contact, i) => {
contact.isPrimary = i === index;
});
};
const formatPhoneNumber = (value) => {
const digits = value.replace(/\D/g, "").slice(0, 10);
if (digits.length <= 3) return digits;
if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
};
const formatPhone = (index, event) => {
const value = event.target.value;
const formatted = formatPhoneNumber(value);
localFormData.value.contacts[index].phoneNumber = formatted;
};
const handlePhoneKeydown = (event, index) => {
const allowedKeys = [
"Backspace",
"Delete",
"Tab",
"Escape",
"Enter",
"ArrowLeft",
"ArrowRight",
"ArrowUp",
"ArrowDown",
"Home",
"End",
];
if (allowedKeys.includes(event.key)) {
return;
}
// Allow Ctrl+A, Ctrl+C, Ctrl+V, etc.
if (event.ctrlKey || event.metaKey) {
return;
}
// Check if it's a digit
if (!/\d/.test(event.key)) {
event.preventDefault();
return;
}
// Check current digit count
const currentDigits = localFormData.value.contacts[index].phoneNumber.replace(
/\D/g,
"",
).length;
if (currentDigits >= 10) {
event.preventDefault();
}
};
defineExpose({});
</script>
<style scoped>
.form-section {
background: var(--surface-card);
border-radius: 8px;
padding: 1.5rem;
border: 1px solid var(--surface-border);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.section-header h3 {
margin: 0;
color: var(--text-color);
font-size: 1.25rem;
font-weight: 600;
}
.contact-item {
border: 1px solid var(--surface-border);
border-radius: 6px;
padding: 1rem;
margin-bottom: 1rem;
background: var(--surface-section);
}
.contact-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.contact-header h4 {
margin: 0;
color: var(--text-color);
font-size: 1.1rem;
font-weight: 600;
}
.remove-btn {
margin-left: auto;
}
.contact-item .form-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
.form-rows {
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.form-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-field.full-width {
grid-column: 1 / -1;
}
.form-field label {
font-weight: 500;
color: var(--text-color-secondary);
font-size: 0.9rem;
}
.required {
color: var(--red-500);
}
.w-full {
width: 100% !important;
}
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
}
</style>