get create client working

This commit is contained in:
Casey 2025-11-25 06:22:44 -06:00
parent 4a3576168a
commit cb33d0c3b3
11 changed files with 1179 additions and 336 deletions

View file

@ -1,172 +1,113 @@
<template>
<div class="overview-container">
<!-- Client Basic Info Card -->
<div class="info-card">
<div class="card-header">
<h3>Client Information</h3>
<Button
v-if="!isNew && !editMode"
@click="toggleEditMode"
icon="pi pi-pencil"
label="Edit"
size="small"
severity="secondary"
/>
</div>
<div class="info-grid">
<div class="info-item">
<label>Customer Name:</label>
<AutoComplete
v-if="isNew || editMode"
v-model="formData.customerName"
:suggestions="customerSuggestions"
@complete="searchCustomers"
@item-select="onCustomerSelect"
placeholder="Type or select customer name"
class="w-full"
:disabled="isSubmitting"
/>
<span v-else>{{ clientData?.customerName || "N/A" }}</span>
</div>
<div class="info-item">
<label>Customer Type:</label>
<Select
v-if="isNew || editMode"
v-model="formData.customerType"
:options="customerTypeOptions"
placeholder="Select customer type"
class="w-full"
:disabled="isSubmitting"
/>
<span v-else>{{ clientData?.customerType || "N/A" }}</span>
</div>
<div class="info-item" v-if="!isNew && !editMode">
<label>Customer Group:</label>
<span>{{ clientData?.customerGroup || "N/A" }}</span>
</div>
<div class="info-item" v-if="!isNew && !editMode">
<label>Territory:</label>
<span>{{ clientData?.territory || "N/A" }}</span>
</div>
</div>
</div>
<!-- Form Mode (new=true or edit mode) -->
<template v-if="isNew || editMode">
<ClientInformationForm
ref="clientInfoRef"
v-model:form-data="formData"
:is-submitting="isSubmitting"
:is-edit-mode="editMode"
@new-client-toggle="handleNewClientToggle"
@customer-selected="handleCustomerSelected"
/>
<!-- Address Info Card -->
<div class="info-card">
<h3>Address Information</h3>
<div class="info-grid">
<div class="info-item" v-if="isNew || editMode">
<label>Address Line 1 *:</label>
<InputText
v-model="formData.addressLine1"
placeholder="Street address"
class="w-full"
:disabled="isSubmitting"
required
/>
</div>
<div class="info-item" v-if="isNew || editMode">
<label>Address Line 2:</label>
<InputText
v-model="formData.addressLine2"
placeholder="Apt, suite, unit, etc."
class="w-full"
:disabled="isSubmitting"
/>
</div>
<div class="info-item" v-if="isNew || editMode">
<label>Zip Code *:</label>
<InputText
v-model="formData.zipcode"
placeholder="12345"
@input="handleZipcodeInput"
maxlength="5"
class="w-full"
:disabled="isSubmitting"
required
/>
</div>
<div class="info-item" v-if="isNew || editMode">
<label>City *:</label>
<InputText
v-model="formData.city"
placeholder="City"
class="w-full"
:disabled="isSubmitting || zipcodeLookupDisabled"
required
/>
</div>
<div class="info-item" v-if="isNew || editMode">
<label>State *:</label>
<InputText
v-model="formData.state"
placeholder="State"
class="w-full"
:disabled="isSubmitting || zipcodeLookupDisabled"
required
/>
</div>
<!-- Read-only mode for existing clients -->
<div
class="info-item full-width"
v-if="!isNew && !editMode && selectedAddressData"
>
<label>Full Address:</label>
<span>{{ fullAddress }}</span>
</div>
<div class="info-item" v-if="!isNew && !editMode && selectedAddressData">
<label>City:</label>
<span>{{ selectedAddressData.city || "N/A" }}</span>
</div>
<div class="info-item" v-if="!isNew && !editMode && selectedAddressData">
<label>State:</label>
<span>{{ selectedAddressData.state || "N/A" }}</span>
</div>
<div class="info-item" v-if="!isNew && !editMode && selectedAddressData">
<label>Zip Code:</label>
<span>{{ selectedAddressData.pincode || "N/A" }}</span>
</div>
</div>
</div>
<ContactInformationForm
ref="contactInfoRef"
v-model:form-data="formData"
:is-submitting="isSubmitting"
:is-edit-mode="editMode"
:is-new-client-locked="isNewClientMode"
:available-contacts="availableContacts"
@new-contact-toggle="handleNewContactToggle"
/>
<!-- Contact Info Card (only for new/edit mode) -->
<div class="info-card" v-if="isNew || editMode">
<h3>Contact Information</h3>
<div class="info-grid">
<div class="info-item">
<label>Contact Name *:</label>
<AutoComplete
v-model="formData.contactName"
:suggestions="contactSuggestions"
@complete="searchContacts"
@item-select="onContactSelect"
placeholder="Type or select contact name"
class="w-full"
:disabled="isSubmitting || !formData.customerName"
required
<AddressInformationForm
v-model:form-data="formData"
:is-submitting="isSubmitting"
:is-edit-mode="editMode"
/>
</template>
<!-- Display Mode (existing client view) -->
<template v-else>
<!-- Client Basic Info Card -->
<div class="info-card">
<div class="card-header">
<h3>Client Information</h3>
<Button
@click="toggleEditMode"
icon="pi pi-pencil"
label="Edit"
size="small"
severity="secondary"
/>
</div>
<div class="info-item">
<label>Phone Number:</label>
<InputText
v-model="formData.phoneNumber"
placeholder="(555) 123-4567"
class="w-full"
:disabled="isSubmitting"
/>
</div>
<div class="info-item">
<label>Email:</label>
<InputText
v-model="formData.email"
placeholder="email@example.com"
type="email"
class="w-full"
:disabled="isSubmitting"
/>
<div class="info-grid">
<div class="info-item">
<label>Customer Name:</label>
<span>{{ clientData?.customerName || "N/A" }}</span>
</div>
<div class="info-item">
<label>Customer Type:</label>
<span>{{ clientData?.customerType || "N/A" }}</span>
</div>
<div class="info-item">
<label>Customer Group:</label>
<span>{{ clientData?.customerGroup || "N/A" }}</span>
</div>
<div class="info-item">
<label>Territory:</label>
<span>{{ clientData?.territory || "N/A" }}</span>
</div>
</div>
</div>
</div>
<!-- Address Info Card -->
<div class="info-card" v-if="selectedAddressData">
<h3>Address Information</h3>
<div class="info-grid">
<div class="info-item full-width">
<label>Address Title:</label>
<span>{{ selectedAddressData.addressTitle || "N/A" }}</span>
</div>
<div class="info-item full-width">
<label>Full Address:</label>
<span>{{ fullAddress }}</span>
</div>
<div class="info-item">
<label>City:</label>
<span>{{ selectedAddressData.city || "N/A" }}</span>
</div>
<div class="info-item">
<label>State:</label>
<span>{{ selectedAddressData.state || "N/A" }}</span>
</div>
<div class="info-item">
<label>Zip Code:</label>
<span>{{ selectedAddressData.pincode || "N/A" }}</span>
</div>
</div>
</div>
<!-- Contact Info Card -->
<div class="info-card" v-if="selectedAddressData">
<h3>Contact Information</h3>
<div class="info-grid">
<div class="info-item">
<label>Contact Name:</label>
<span>{{ contactFullName }}</span>
</div>
<div class="info-item">
<label>Phone:</label>
<span>{{ selectedAddressData.phone || "N/A" }}</span>
</div>
<div class="info-item">
<label>Email:</label>
<span>{{ selectedAddressData.emailId || "N/A" }}</span>
</div>
</div>
</div>
</template>
<!-- Status Cards (only for existing clients) -->
<div class="status-cards" v-if="!isNew && !editMode && selectedAddressData">
@ -204,13 +145,13 @@
<div class="form-actions" v-if="isNew || editMode">
<Button
@click="handleCancel"
label="Cancel"
:label="editMode ? 'Cancel' : 'Clear'"
severity="secondary"
:disabled="isSubmitting"
/>
<Button
@click="handleSave"
:label="isNew ? 'Create Client' : 'Save Changes'"
:label="isNew ? 'Create' : 'Update'"
:loading="isSubmitting"
:disabled="!isFormValid"
/>
@ -262,11 +203,11 @@
import { computed, ref, watch, onMounted } from "vue";
import Badge from "primevue/badge";
import Button from "primevue/button";
import InputText from "primevue/inputtext";
import AutoComplete from "primevue/autocomplete";
import Select from "primevue/select";
import Dialog from "primevue/dialog";
import LeafletMap from "../common/LeafletMap.vue";
import ClientInformationForm from "./ClientInformationForm.vue";
import ContactInformationForm from "./ContactInformationForm.vue";
import AddressInformationForm from "./AddressInformationForm.vue";
import DataUtils from "../../utils";
import Api from "../../api";
import { useRouter } from "vue-router";
@ -290,46 +231,42 @@ const props = defineProps({
const router = useRouter();
const notificationStore = useNotificationStore();
// Refs for child components
const clientInfoRef = ref(null);
const contactInfoRef = ref(null);
// Form state
const editMode = ref(false);
const showEditConfirmDialog = ref(false);
const isSubmitting = ref(false);
const zipcodeLookupDisabled = ref(true);
const isNewClientMode = ref(false);
const availableContacts = ref([]);
// Form data
const formData = ref({
customerName: "",
customerType: "",
addressTitle: "",
addressLine1: "",
addressLine2: "",
zipcode: "",
pincode: "",
city: "",
state: "",
contactName: "",
firstName: "",
lastName: "",
phoneNumber: "",
email: "",
});
// Autocomplete data
const customerSuggestions = ref([]);
const contactSuggestions = ref([]);
const selectedCustomerData = ref(null);
// Options
const customerTypeOptions = ref(["Company", "Individual"]);
// Initialize form data when component mounts
onMounted(() => {
if (props.isNew) {
// Initialize empty form for new client
resetForm();
console.log("Mounted in new client mode - initialized empty form");
} else if (props.clientData && Object.keys(props.clientData).length > 0) {
// Populate form with existing client data
populateFormFromClientData();
console.log("Mounted with existing client data - populated form");
} else {
// Default to empty form if no client data
resetForm();
console.log("Mounted with no client data - initialized empty form");
}
@ -340,32 +277,27 @@ watch(
() => props.clientData,
(newData) => {
if (props.isNew) {
// Always keep form empty for new clients, regardless of clientData
resetForm();
} else if (newData && Object.keys(newData).length > 0) {
populateFormFromClientData();
} else {
// No client data, reset form
resetForm();
}
},
{ deep: true },
);
// Watch for isNew prop changes to reset form when switching to new client mode
// Watch for isNew prop changes
watch(
() => props.isNew,
(isNewValue) => {
if (isNewValue) {
// Reset form when switching to new client mode
resetForm();
editMode.value = false;
console.log("Switched to new client mode - reset form data");
} else if (props.clientData && Object.keys(props.clientData).length > 0) {
// Populate form when switching back to existing client
populateFormFromClientData();
} else {
// No client data, reset form
resetForm();
}
},
@ -386,15 +318,11 @@ const selectedAddressData = computed(() => {
// Get coordinates from the selected address
const latitude = computed(() => {
if (!selectedAddressData.value) return null;
// Check custom fields first, then fallback to regular fields
return selectedAddressData.value.customLatitude || selectedAddressData.value.latitude || null;
});
const longitude = computed(() => {
if (!selectedAddressData.value) return null;
// Check custom fields first, then fallback to regular fields
return (
selectedAddressData.value.customLongitude || selectedAddressData.value.longitude || null
);
@ -406,16 +334,36 @@ const fullAddress = computed(() => {
return DataUtils.calculateFullAddress(selectedAddressData.value);
});
// Calculate contact full name
const contactFullName = computed(() => {
if (!selectedAddressData.value) return "N/A";
const firstName = selectedAddressData.value.customContactFirstName || "";
const lastName = selectedAddressData.value.customContactLastName || "";
return `${firstName} ${lastName}`.trim() || "N/A";
});
// Form validation
const isFormValid = computed(() => {
const hasCustomerName = formData.value.customerName?.trim();
const hasCustomerType = formData.value.customerType?.trim();
const hasAddressTitle = formData.value.addressTitle?.trim();
const hasAddressLine1 = formData.value.addressLine1?.trim();
const hasPincode = formData.value.pincode?.trim();
const hasCity = formData.value.city?.trim();
const hasState = formData.value.state?.trim();
const hasFirstName = formData.value.firstName?.trim();
const hasLastName = formData.value.lastName?.trim();
return (
formData.value.customerName &&
formData.value.customerType &&
formData.value.addressLine1 &&
formData.value.zipcode &&
formData.value.city &&
formData.value.state &&
formData.value.contactName
hasCustomerName &&
hasCustomerType &&
hasAddressTitle &&
hasAddressLine1 &&
hasPincode &&
hasCity &&
hasState &&
hasFirstName &&
hasLastName
);
});
@ -425,7 +373,7 @@ const getStatusSeverity = (status) => {
case "Not Started":
return "secondary";
case "In Progress":
return "warn"; // Use 'warn' instead of 'warning' for PrimeVue Badge
return "warn";
case "Completed":
return "success";
default:
@ -438,18 +386,20 @@ const resetForm = () => {
formData.value = {
customerName: "",
customerType: "",
addressTitle: "",
addressLine1: "",
addressLine2: "",
zipcode: "",
pincode: "",
city: "",
state: "",
contactName: "",
firstName: "",
lastName: "",
phoneNumber: "",
email: "",
};
selectedCustomerData.value = null;
zipcodeLookupDisabled.value = false; // Allow manual entry for new clients
editMode.value = false; // Ensure edit mode is off
availableContacts.value = [];
isNewClientMode.value = false;
editMode.value = false;
console.log("Form reset - all fields cleared");
};
@ -459,15 +409,46 @@ const populateFormFromClientData = () => {
formData.value = {
customerName: props.clientData.customerName || "",
customerType: props.clientData.customerType || "",
addressTitle: selectedAddressData.value.addressTitle || "",
addressLine1: selectedAddressData.value.addressLine1 || "",
addressLine2: selectedAddressData.value.addressLine2 || "",
zipcode: selectedAddressData.value.pincode || "",
pincode: selectedAddressData.value.pincode || "",
city: selectedAddressData.value.city || "",
state: selectedAddressData.value.state || "",
contactName: selectedAddressData.value.customContactName || "",
firstName: selectedAddressData.value.customContactFirstName || "",
lastName: selectedAddressData.value.customContactLastName || "",
phoneNumber: selectedAddressData.value.phone || "",
email: selectedAddressData.value.emailId || "",
};
// Populate available contacts if any
if (selectedAddressData.value.contacts && selectedAddressData.value.contacts.length > 0) {
availableContacts.value = selectedAddressData.value.contacts;
}
};
// Event handlers
const handleNewClientToggle = (isNewClient) => {
isNewClientMode.value = isNewClient;
if (isNewClient) {
// Reset form when toggling to new client
resetForm();
}
};
const handleCustomerSelected = (clientData) => {
// When a customer is selected, populate available contacts from the first address
if (clientData.addresses && clientData.addresses.length > 0) {
availableContacts.value = clientData.addresses[0].contacts || [];
} else {
availableContacts.value = [];
}
};
const handleNewContactToggle = (isNewContact) => {
if (!isNewContact && availableContacts.value.length === 0) {
notificationStore.addWarning("No contacts available for this customer.");
}
};
// Edit mode methods
@ -478,95 +459,7 @@ const toggleEditMode = () => {
const confirmEdit = () => {
showEditConfirmDialog.value = false;
editMode.value = true;
populateFormFromClientData(); // Refresh form with current data
};
// Zipcode handling
const handleZipcodeInput = async (event) => {
const input = event.target.value;
// Only allow digits
const digitsOnly = input.replace(/\D/g, "");
// Limit to 5 digits
if (digitsOnly.length > 5) {
return;
}
formData.value.zipcode = digitsOnly;
// Fetch city/state when 5 digits entered
if (digitsOnly.length === 5) {
try {
const places = await Api.getCityStateByZip(digitsOnly);
if (places && places.length > 0) {
// Auto-populate city and state
formData.value.city = places[0]["place name"];
formData.value.state = places[0]["state abbreviation"];
zipcodeLookupDisabled.value = true;
notificationStore.addSuccess(
`Found: ${places[0]["place name"]}, ${places[0]["state abbreviation"]}`,
);
}
} catch (error) {
// Enable manual entry if lookup fails
zipcodeLookupDisabled.value = false;
notificationStore.addWarning(
"Could not find city/state for this zip code. Please enter manually.",
);
}
} else {
// Reset city/state if zipcode is incomplete
if (zipcodeLookupDisabled.value) {
formData.value.city = "";
formData.value.state = "";
}
}
};
// Customer search
const searchCustomers = async (event) => {
try {
const customers = await Api.getCustomerNames("all");
customerSuggestions.value = customers.filter((name) =>
name.toLowerCase().includes(event.query.toLowerCase()),
);
} catch (error) {
console.error("Error searching customers:", error);
customerSuggestions.value = [];
}
};
const onCustomerSelect = (event) => {
// Store selected customer for contact lookup
selectedCustomerData.value = event.value;
// Reset contact data when customer changes
formData.value.contactName = "";
formData.value.phoneNumber = "";
formData.value.email = "";
};
// Contact search
const searchContacts = async (event) => {
if (!selectedCustomerData.value) {
contactSuggestions.value = [];
return;
}
try {
// TODO: Implement contact search API method
// For now, just allow typing
contactSuggestions.value = [event.query];
} catch (error) {
console.error("Error searching contacts:", error);
contactSuggestions.value = [];
}
};
const onContactSelect = (event) => {
// TODO: Auto-populate phone and email from selected contact
// For now, just set the name
formData.value.contactName = event.value;
populateFormFromClientData();
};
// Save/Cancel actions
@ -579,33 +472,37 @@ const handleSave = async () => {
isSubmitting.value = true;
try {
// Prepare client data for upsert
const clientData = {
customerName: formData.value.customerName,
customerType: formData.value.customerType,
addressTitle: formData.value.addressTitle,
addressLine1: formData.value.addressLine1,
addressLine2: formData.value.addressLine2,
pincode: formData.value.pincode,
city: formData.value.city,
state: formData.value.state,
firstName: formData.value.firstName,
lastName: formData.value.lastName,
phoneNumber: formData.value.phoneNumber,
email: formData.value.email,
};
console.log("Upserting client with data:", clientData);
// Call the upsert API
const result = await Api.createClient(clientData);
// Calculate full address for redirect
const fullAddressParts = [formData.value.addressLine1];
if (formData.value.addressLine2?.trim()) {
fullAddressParts.push(formData.value.addressLine2);
}
fullAddressParts.push(`${formData.value.city}, ${formData.value.state}`);
fullAddressParts.push(formData.value.pincode);
const fullAddress = fullAddressParts.join(" ");
if (props.isNew) {
// Create new client
const clientData = {
customerName: formData.value.customerName,
customerType: formData.value.customerType,
addressLine1: formData.value.addressLine1,
addressLine2: formData.value.addressLine2,
zipcode: formData.value.zipcode,
city: formData.value.city,
state: formData.value.state,
contactName: formData.value.contactName,
phoneNumber: formData.value.phoneNumber,
email: formData.value.email,
};
// TODO: Implement API call to create client
console.log("Would create client with data:", clientData);
// For now, just show success and redirect
const fullAddress = DataUtils.calculateFullAddress({
addressLine1: formData.value.addressLine1,
addressLine2: formData.value.addressLine2,
city: formData.value.city,
state: formData.value.state,
pincode: formData.value.zipcode,
});
notificationStore.addSuccess(
`Client ${formData.value.customerName} created successfully!`,
);
@ -619,12 +516,11 @@ const handleSave = async () => {
},
});
} else {
// Update existing client (edit mode)
// TODO: Implement API call to update client
console.log("Would update client with data:", formData.value);
notificationStore.addSuccess("Client updated successfully!");
editMode.value = false;
// Reload the client data
// Note: Parent component should handle reloading
}
} catch (error) {
console.error("Error saving client:", error);
@ -636,8 +532,8 @@ const handleSave = async () => {
const handleCancel = () => {
if (props.isNew) {
// Go back for new client
router.back();
// Clear form for new client
resetForm();
} else {
// Exit edit mode and restore original data
editMode.value = false;