@@ -204,13 +145,13 @@
@@ -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;
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 87d7c11..1424341 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -7,6 +7,7 @@ import { globalSettings } from "./globalSettings";
import { createPinia } from "pinia";
// Vuetify
+import "@primeuix/themes/aura";
import "vuetify/styles";
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
diff --git a/frontend/src/style.css b/frontend/src/style.css
index 45e8578..7bfb5af 100644
--- a/frontend/src/style.css
+++ b/frontend/src/style.css
@@ -55,3 +55,19 @@
justify-content: flex-end;
gap: 5px;
}
+
+/* Fix ToggleSwitch z-index so slider is visible but input receives clicks */
+.p-toggleswitch {
+ position: relative;
+}
+
+.p-toggleswitch-slider {
+ position: relative;
+ z-index: 0;
+ pointer-events: none;
+}
+
+.p-toggleswitch-input {
+ position: absolute;
+ z-index: 1;
+}