Revert "remove duplicate item csv"
This reverts commit 15ee0459b59b207795e1a24b1ca04327be09431d.
This commit is contained in:
parent
15ee0459b5
commit
8464fcef87
20
csv/Item Price.csv
Normal file
20
csv/Item Price.csv
Normal file
@ -0,0 +1,20 @@
|
||||
"Data Import Template"
|
||||
"Table:","Item Price"
|
||||
""
|
||||
""
|
||||
"Notes:"
|
||||
"Please do not change the template headings."
|
||||
"First data column must be blank."
|
||||
"If you are uploading new records, leave the ""name"" (ID) column blank."
|
||||
"If you are uploading new records, ""Naming Series"" becomes mandatory, if present."
|
||||
"Only mandatory fields are necessary for new records. You can delete non-mandatory columns if you wish."
|
||||
"For updating, you can update only selective columns."
|
||||
"You can only upload upto 5000 records in one go. (may be less in some cases)"
|
||||
""
|
||||
"DocType:","Item Price","","","","","","","","","","","","","","","","","","","","",""
|
||||
"Column Labels:","ID","Item Code","UOM","Price List","Rate","Created On","Created By","Packing Unit","Item Name","Brand","Item Description","Customer","Supplier","Batch No","Buying","Selling","Currency","Valid From","Lead Time in days","Valid Upto","Note","Reference"
|
||||
"Column Name:","name","item_code","uom","price_list","price_list_rate","creation","owner","packing_unit","item_name","brand","item_description","customer","supplier","batch_no","buying","selling","currency","valid_from","lead_time_days","valid_upto","note","reference"
|
||||
"Mandatory:","Yes","Yes","Yes","Yes","Yes","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No"
|
||||
"Type:","Data","Link","Link","Link","Currency","Datetime","Link","Int","Data","Link","Text","Link","Link","Link","Check","Check","Link","Date","Int","Date","Text","Data"
|
||||
"Info:","","Valid Item","Valid UOM","Valid Price List","","mm-dd-yyyy","Valid User","Integer","","Valid Brand","","Valid Customer","Valid Supplier","Valid Batch","0 or 1","0 or 1","Valid Currency","mm-dd-yyyy","Integer","mm-dd-yyyy","",""
|
||||
"Start entering data below this line"
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
20
csv/Item.csv
Normal file
20
csv/Item.csv
Normal file
@ -0,0 +1,20 @@
|
||||
"Data Import Template"
|
||||
"Table:","Item"
|
||||
""
|
||||
""
|
||||
"Notes:"
|
||||
"Please do not change the template headings."
|
||||
"First data column must be blank."
|
||||
"If you are uploading new records, leave the ""name"" (ID) column blank."
|
||||
"If you are uploading new records, ""Naming Series"" becomes mandatory, if present."
|
||||
"Only mandatory fields are necessary for new records. You can delete non-mandatory columns if you wish."
|
||||
"For updating, you can update only selective columns."
|
||||
"You can only upload upto 5000 records in one go. (may be less in some cases)"
|
||||
""
|
||||
"DocType:","Item","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","~","UOM Conversion Detail","uoms","","","","~","Item Barcode","barcodes","","","","","~","Item Reorder","reorder_levels","","","","","","","~","Item Variant Attribute","attributes","","","","","","","","","","~","Item Default","item_defaults","","","","","","","","","","","","","","~","Item Supplier","supplier_items","","","","~","Item Customer Detail","customer_items","","","","","~","Item Tax","taxes","","","","","",""
|
||||
"Column Labels:","ID","Item Code","Item Group","Default Unit of Measure","Created On","Created By","Item Name","Markup Percentage","Disabled","Allow Alternative Item","Maintain Stock","Has Variants","Opening Stock","Valuation Rate","Standard Selling Rate","Is Fixed Asset","Auto Create Assets on Purchase","Create Grouped Asset","Asset Category","Asset Naming Series","Over Delivery/Receipt Allowance (%)","Over Billing Allowance (%)","Description","Brand","Shelf Life In Days","End of Life","Default Material Request Type","Valuation Method","Warranty Period (in days)","Weight Per Unit","Weight UOM","Allow Negative Stock","Has Batch No","Automatically Create New Batch","Batch Number Series","Has Expiry Date","Retain Sample","Max Sample Quantity","Has Serial No","Serial Number Series","Variant Of","Variant Based On","Enable Deferred Expense","No of Months (Expense)","Enable Deferred Revenue","No of Months (Revenue)","Default Purchase Unit of Measure","Minimum Order Qty","Safety Stock","Allow Purchase","Lead Time in days","Last Purchase Rate","Is Customer Provided Item","Customer","Delivered by Supplier (Drop Ship)","Country of Origin","Customs Tariff Number","Default Sales Unit of Measure","Grant Commission","Allow Sales","Max Discount (%)","Inspection Required before Purchase","Quality Inspection Template","Inspection Required before Delivery","Include Item In Manufacturing","Supply Raw Materials for Purchase","Default BOM","Default Item Manufacturer","Default Manufacturer Part No","","ID","Created On","Created By","UOM","Conversion Factor","","ID","Barcode","Created On","Created By","Barcode Type","UOM","","ID","Request for","Material Request Type","Created On","Created By","Check in (group)","Re-order Level","Re-order Qty","","ID","Attribute","Created On","Created By","Variant Of","Attribute Value","Numeric Values","Disabled","From Range","Increment","To Range","","ID","Company","Created On","Created By","Default Warehouse","Default Price List","Default Discount Account","Default Buying Cost Center","Default Supplier","Default Expense Account","Default Provisional Account","Default Selling Cost Center","Default Income Account","Deferred Expense Account","Deferred Revenue Account","","ID","Supplier","Created On","Created By","Supplier Part Number","","ID","Ref Code","Created On","Created By","Customer Name","Customer Group","","ID","Item Tax Template","Created On","Created By","Tax Category","Valid From","Minimum Net Rate","Maximum Net Rate"
|
||||
"Column Name:","name","item_code","item_group","stock_uom","creation","owner","item_name","custom_markup_percentage","disabled","allow_alternative_item","is_stock_item","has_variants","opening_stock","valuation_rate","standard_rate","is_fixed_asset","auto_create_assets","is_grouped_asset","asset_category","asset_naming_series","over_delivery_receipt_allowance","over_billing_allowance","description","brand","shelf_life_in_days","end_of_life","default_material_request_type","valuation_method","warranty_period","weight_per_unit","weight_uom","allow_negative_stock","has_batch_no","create_new_batch","batch_number_series","has_expiry_date","retain_sample","sample_quantity","has_serial_no","serial_no_series","variant_of","variant_based_on","enable_deferred_expense","no_of_months_exp","enable_deferred_revenue","no_of_months","purchase_uom","min_order_qty","safety_stock","is_purchase_item","lead_time_days","last_purchase_rate","is_customer_provided_item","customer","delivered_by_supplier","country_of_origin","customs_tariff_number","sales_uom","grant_commission","is_sales_item","max_discount","inspection_required_before_purchase","quality_inspection_template","inspection_required_before_delivery","include_item_in_manufacturing","is_sub_contracted_item","default_bom","default_item_manufacturer","default_manufacturer_part_no","~","name","creation","owner","uom","conversion_factor","~","name","barcode","creation","owner","barcode_type","uom","~","name","warehouse","material_request_type","creation","owner","warehouse_group","warehouse_reorder_level","warehouse_reorder_qty","~","name","attribute","creation","owner","variant_of","attribute_value","numeric_values","disabled","from_range","increment","to_range","~","name","company","creation","owner","default_warehouse","default_price_list","default_discount_account","buying_cost_center","default_supplier","expense_account","default_provisional_account","selling_cost_center","income_account","deferred_expense_account","deferred_revenue_account","~","name","supplier","creation","owner","supplier_part_no","~","name","ref_code","creation","owner","customer_name","customer_group","~","name","item_tax_template","creation","owner","tax_category","valid_from","minimum_net_rate","maximum_net_rate"
|
||||
"Mandatory:","Yes","Yes","Yes","Yes","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","No","","Yes","No","No","No","No","","Yes","Yes","No","No","No","No","","Yes","Yes","Yes","No","No","No","No","No","","Yes","Yes","No","No","No","No","No","No","No","No","No","","Yes","Yes","No","No","No","No","No","No","No","No","No","No","No","No","No","","Yes","Yes","No","No","No","","Yes","Yes","No","No","No","No","","Yes","Yes","No","No","No","No","No","No"
|
||||
"Type:","Data","Data","Link","Link","Datetime","Link","Data","Percent","Check","Check","Check","Check","Float","Currency","Currency","Check","Check","Check","Link","Select","Float","Float","Text Editor","Link","Int","Date","Select","Select","Data","Float","Link","Check","Check","Check","Data","Check","Check","Int","Check","Data","Link","Select","Check","Int","Check","Int","Link","Float","Float","Check","Int","Float","Check","Link","Check","Link","Link","Link","Check","Check","Float","Check","Link","Check","Check","Check","Link","Link","Data","","Data","Datetime","Link","Link","Float","","Data","Data","Datetime","Link","Select","Link","","Data","Link","Select","Datetime","Link","Link","Float","Float","","Data","Link","Datetime","Link","Link","Data","Check","Check","Float","Float","Float","","Data","Link","Datetime","Link","Link","Link","Link","Link","Link","Link","Link","Link","Link","Link","Link","","Data","Link","Datetime","Link","Data","","Data","Data","Datetime","Link","Link","Link","","Data","Link","Datetime","Link","Link","Date","Float","Float"
|
||||
"Info:","","","Valid Item Group","Valid UOM","mm-dd-yyyy","Valid User","","","0 or 1","0 or 1","0 or 1","0 or 1","","","","0 or 1","0 or 1","0 or 1","Valid Asset Category","","","","","Valid Brand","Integer","mm-dd-yyyy","One of: Purchase, Material Transfer, Material Issue, Manufacture, Customer Provided","One of: FIFO, Moving Average, LIFO","","","Valid UOM","0 or 1","0 or 1","0 or 1","","0 or 1","0 or 1","Integer","0 or 1","","Valid Item","One of: Item Attribute, Manufacturer","0 or 1","Integer","0 or 1","Integer","Valid UOM","","","0 or 1","Integer","","0 or 1","Valid Customer","0 or 1","Valid Country","Valid Customs Tariff Number","Valid UOM","0 or 1","0 or 1","","0 or 1","Valid Quality Inspection Template","0 or 1","0 or 1","0 or 1","Valid BOM","Valid Manufacturer","","","","mm-dd-yyyy","Valid User","Valid UOM","","","","","mm-dd-yyyy","Valid User","One of: EAN, UPC-A, CODE-39, EAN-12, EAN-8, GS1, GTIN, ISBN, ISBN-10, ISBN-13, ISSN, JAN, PZN, UPC","Valid UOM","","","Valid Warehouse","One of: Purchase, Transfer, Material Issue, Manufacture","mm-dd-yyyy","Valid User","Valid Warehouse","","","","","Valid Item Attribute","mm-dd-yyyy","Valid User","Valid Item","","0 or 1","0 or 1","","","","","","Valid Company","mm-dd-yyyy","Valid User","Valid Warehouse","Valid Price List","Valid Account","Valid Cost Center","Valid Supplier","Valid Account","Valid Account","Valid Cost Center","Valid Account","Valid Account","Valid Account","","","Valid Supplier","mm-dd-yyyy","Valid User","","","","","mm-dd-yyyy","Valid User","Valid Customer","Valid Customer Group","","","Valid Item Tax Template","mm-dd-yyyy","Valid User","Valid Tax Category","mm-dd-yyyy","",""
|
||||
"Start entering data below this line"
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
28
csv/Project Template.csv
Normal file
28
csv/Project Template.csv
Normal file
@ -0,0 +1,28 @@
|
||||
"Data Import Template"
|
||||
"Table:","Project Template"
|
||||
""
|
||||
""
|
||||
"Notes:"
|
||||
"Please do not change the template headings."
|
||||
"First data column must be blank."
|
||||
"If you are uploading new records, leave the ""name"" (ID) column blank."
|
||||
"If you are uploading new records, ""Naming Series"" becomes mandatory, if present."
|
||||
"Only mandatory fields are necessary for new records. You can delete non-mandatory columns if you wish."
|
||||
"For updating, you can update only selective columns."
|
||||
"You can only upload upto 5000 records in one go. (may be less in some cases)"
|
||||
""
|
||||
"DocType:","Project Template","","","","","","","","","~","Project Template Task","tasks","","",""
|
||||
"Column Labels:","ID","% Complete Method","Created On","Created By","Project Type","Company","Calendar Color","Bid Meeting Note Form","Item Groups","","ID","Task","Created On","Created By","Subject"
|
||||
"Column Name:","name","custom__complete_method","creation","owner","project_type","company","calendar_color","bid_meeting_note_form","item_groups","~","name","task","creation","owner","subject"
|
||||
"Mandatory:","Yes","Yes","No","No","No","No","No","No","No","","Yes","Yes","No","No","No"
|
||||
"Type:","Data","Select","Datetime","Link","Link","Link","Color","Link","Data","","Data","Link","Datetime","Link","Read Only"
|
||||
"Info:","","One of: Manual, Task Completion, Task Progress, Task Weight","mm-dd-yyyy","Valid User","Valid Project Type","Valid Company","","Valid Bid Meeting Note Form","","","","Valid Task","mm-dd-yyyy","Valid User",""
|
||||
"Start entering data below this line"
|
||||
"","""SNW Install""","Task Weight","2026-02-18 15:40:37.222666","Administrator","External","Sprinklers Northwest","#c1dec5","SNW Install Bid Meeting Notes","SNW-I, SNW-S, SNW-LS","","""2sjplgsa2i""","TASK-2025-00001","2026-02-18 15:40:37.223178","Administrator","Send customer 3-5 day window for start date"
|
||||
"","","","","","","","","","","","""2sjvr9sh0v""","TASK-2025-00002","2026-02-18 15:40:37.231036","Administrator","811/Locate call in"
|
||||
"","","","","","","","","","","","""2sj7867nq0""","TASK-2025-00003","2026-02-18 15:40:37.231817","Administrator","Permit(s) call in and pay"
|
||||
"","","","","","","","","","","","""2sjh2l2fln""","TASK-2025-00004","2026-02-18 15:40:37.239484","Administrator","Primary Job"
|
||||
"","","","","","","","","","","","""2sjhhtfd2a""","TASK-2025-00005","2026-02-18 15:40:37.239944","Administrator","Hydroseeding"
|
||||
"","","","","","","","","","","","""2sji3le63f""","TASK-2025-00006","2026-02-18 15:40:37.240339","Administrator","Curbing"
|
||||
"","","","","","","","","","","","""2sjira5j6l""","TASK-2025-00007","2026-02-18 15:40:37.240741","Administrator","15-Day QA"
|
||||
"","","","","","","","","","","","""2sjjgdktbj""","TASK-2025-00008","2026-02-18 15:40:37.241134","Administrator","Permit Close-out"
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
562
frontend/src/components/pages/Client.vue
Normal file
562
frontend/src/components/pages/Client.vue
Normal file
@ -0,0 +1,562 @@
|
||||
<template>
|
||||
<div class="client-page">
|
||||
<!-- New Client Form -->
|
||||
<div v-if="isNew">
|
||||
<ClientInformationForm
|
||||
:formData="client"
|
||||
:is-submitting="isSubmitting"
|
||||
@update:formData="handleClientUpdate"
|
||||
@newClientToggle="handleNewClientToggle"
|
||||
@customerSelected="handleCustomerSelected"
|
||||
/>
|
||||
<ContactInformationForm
|
||||
:formData="client"
|
||||
:is-submitting="isSubmitting"
|
||||
:existing-contacts="existingContacts.map(contact => `${contact.firstName || ''} ${contact.lastName || ''}`.trim() || contact.email || 'Unknown Contact')"
|
||||
@update:formData="handleClientUpdate"
|
||||
/>
|
||||
<AddressInformationForm
|
||||
:formData="client"
|
||||
:is-submitting="isSubmitting"
|
||||
:existing-addresses="existingAddresses.map(addr => DataUtils.calculateFullAddress(addr))"
|
||||
@update:formData="handleClientUpdate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Existing Client View -->
|
||||
<div v-else>
|
||||
<!-- Client Header -->
|
||||
<GeneralClientInfo
|
||||
v-if="client.customerName"
|
||||
:client-data="client"
|
||||
/>
|
||||
<AdditionalInfoBar :address="client.addresses[selectedAddressIdx]" v-if="client.customerName" />
|
||||
|
||||
<!-- Address Selector (only shows if multiple addresses) -->
|
||||
<AddressSelector
|
||||
v-if="!isNew && client.addresses && client.addresses.length > 1"
|
||||
:addresses="client.addresses"
|
||||
:selected-address-idx="selectedAddressIdx"
|
||||
:contacts="client.contacts"
|
||||
@update:selected-address-idx="handleAddressChange"
|
||||
/>
|
||||
|
||||
<!-- Main Content Tabs -->
|
||||
<Tabs value="0" class="overview-tabs">
|
||||
<TabList>
|
||||
<Tab value="0">Overview</Tab>
|
||||
<Tab value="1">Projects</Tab>
|
||||
<Tab value="2">Financials</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<!-- Overview Tab -->
|
||||
<TabPanel value="0">
|
||||
<Overview
|
||||
:selected-address="selectedAddressData"
|
||||
:all-contacts="client.contacts"
|
||||
:edit-mode="editMode"
|
||||
:is-new="isNew"
|
||||
:full-address="fullAddress"
|
||||
:client="client"
|
||||
@edit-mode-enabled="enableEditMode"
|
||||
@update:address-contacts="handleAddressContactsUpdate"
|
||||
@update:primary-contact="handlePrimaryContactUpdate"
|
||||
@update:client="handleClientUpdate"
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<!-- Projects Tab -->
|
||||
<TabPanel value="1">
|
||||
<div class="coming-soon-section">
|
||||
<i class="pi pi-wrench"></i>
|
||||
<h3>Projects</h3>
|
||||
<p>Section coming soon</p>
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
||||
<!-- Financials Tab -->
|
||||
<TabPanel value="2">
|
||||
<div class="coming-soon-section">
|
||||
<i class="pi pi-dollar"></i>
|
||||
<h3>Financials</h3>
|
||||
<p>Section coming soon</p>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<!-- Existing Addresses/Contacts Modal -->
|
||||
<Dialog
|
||||
:visible="showExistingModal"
|
||||
@update:visible="showExistingModal = $event"
|
||||
header="Existing Addresses and Contacts Found"
|
||||
:modal="true"
|
||||
class="existing-modal"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<p>The following addresses and/or contacts already exist in the system:</p>
|
||||
<div v-if="existingAddresses && existingAddresses.length > 0" class="existing-section">
|
||||
<h4>Existing Addresses:</h4>
|
||||
<ul>
|
||||
<li v-for="addr in existingAddresses" :key="addr">
|
||||
{{ addr.addressLine1 }} {{ addr.addressLine2 }}, {{ addr.city }}, {{ addr.state }} {{ addr.pincode }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="existingContacts && existingContacts.length > 0" class="existing-section">
|
||||
<h4>Existing Contacts:</h4>
|
||||
<ul>
|
||||
<li v-for="contact in existingContacts" :key="contact">
|
||||
{{ contact.firstName }} {{ contact.lastName }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>Would you like to link these existing addresses/contacts with this new client, or cancel the creation?</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="Cancel" severity="secondary" @click="cancelExisting" />
|
||||
<Button label="Continue and Link" @click="continueWithExisting" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- Form Actions (for edit mode or new client) -->
|
||||
<div class="form-actions" v-if="editMode || isNew">
|
||||
<Button
|
||||
@click="handleCancel"
|
||||
label="Cancel"
|
||||
severity="secondary"
|
||||
:disabled="isSubmitting"
|
||||
/>
|
||||
<Button
|
||||
@click="handleSubmit"
|
||||
:label="isNew ? 'Create Client' : 'Save Changes'"
|
||||
:loading="isSubmitting"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import Tabs from "primevue/tabs";
|
||||
import TabList from "primevue/tablist";
|
||||
import Tab from "primevue/tab";
|
||||
import TabPanels from "primevue/tabpanels";
|
||||
import TabPanel from "primevue/tabpanel";
|
||||
import Button from "primevue/button";
|
||||
import Dialog from "primevue/dialog";
|
||||
import Api from "../../api";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useLoadingStore } from "../../stores/loading";
|
||||
import { useNotificationStore } from "../../stores/notifications-primevue";
|
||||
import { useCompanyStore } from "../../stores/company";
|
||||
import DataUtils from "../../utils";
|
||||
import AddressSelector from "../clientView/AddressSelector.vue";
|
||||
import GeneralClientInfo from "../clientView/GeneralClientInfo.vue";
|
||||
import AdditionalInfoBar from "../clientView/AdditionalInfoBar.vue";
|
||||
import Overview from "../clientView/Overview.vue";
|
||||
import ClientInformationForm from "../clientSubPages/ClientInformationForm.vue";
|
||||
import AddressInformationForm from "../clientSubPages/AddressInformationForm.vue";
|
||||
import ContactInformationForm from "../clientSubPages/ContactInformationForm.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loadingStore = useLoadingStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
const address = (route.query.address || '').trim();
|
||||
const clientName = route.query.client || null;
|
||||
const isNew = computed(() => route.query.new === "true" || false);
|
||||
|
||||
const clientNames = ref([]);
|
||||
const client = ref({});
|
||||
const geocode = ref({});
|
||||
|
||||
const selectedAddress = ref(address);
|
||||
|
||||
const selectedAddressObject = computed(() =>
|
||||
client.value.addresses?.find(
|
||||
(addr) => DataUtils.calculateFullAddress(addr) === selectedAddress.value,
|
||||
),
|
||||
);
|
||||
const addresses = computed(() => {
|
||||
if (client.value && client.value.addresses) {
|
||||
return client.value.addresses.map((addr) => DataUtils.calculateFullAddress(addr).trim());
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const nextVisitDate = ref(null); // Placeholder, update as needed
|
||||
|
||||
// Tab and edit state
|
||||
const editMode = ref(false);
|
||||
const isSubmitting = ref(false);
|
||||
const showExistingModal = ref(false);
|
||||
const existingAddresses = ref([]);
|
||||
const existingContacts = ref([]);
|
||||
|
||||
const selectedAddressIdx = computed({
|
||||
get: () => addresses.value.indexOf(selectedAddress.value),
|
||||
set: (idx) => {
|
||||
if (idx >= 0 && idx < addresses.value.length) {
|
||||
selectedAddress.value = addresses.value[idx];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Find the address data object that matches the selected address string
|
||||
const selectedAddressData = computed(() => {
|
||||
if (!client.value?.addresses || !selectedAddress.value) {
|
||||
return null;
|
||||
}
|
||||
return client.value.addresses.find(
|
||||
(addr) => DataUtils.calculateFullAddress(addr).trim() === selectedAddress.value.trim()
|
||||
);
|
||||
});
|
||||
|
||||
// Calculate full address for display
|
||||
const fullAddress = computed(() => {
|
||||
if (!selectedAddressData.value) return "N/A";
|
||||
return DataUtils.calculateFullAddress(selectedAddressData.value);
|
||||
});
|
||||
|
||||
// const getClientNames = async (type) => {
|
||||
// loadingStore.setLoading(true);
|
||||
// try {
|
||||
// const names = await Api.getClientNames(type);
|
||||
// clientNames.value = names;
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching client names in Client.vue: ", error.message || error);
|
||||
// } finally {
|
||||
// loadingStore.setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
const getClient = async (name) => {
|
||||
loadingStore.setLoading(true);
|
||||
try {
|
||||
const clientData = await Api.getClient(name);
|
||||
client.value = clientData || {};
|
||||
// Set initial selected address if provided in route or use first address
|
||||
if (address && client.value.addresses) {
|
||||
const fullAddresses = client.value.addresses.map((addr) =>
|
||||
DataUtils.calculateFullAddress(addr).trim(),
|
||||
);
|
||||
const trimmedAddress = address.trim();
|
||||
if (fullAddresses.includes(trimmedAddress)) {
|
||||
selectedAddress.value = trimmedAddress;
|
||||
} else if (fullAddresses.length > 0) {
|
||||
selectedAddress.value = fullAddresses[0];
|
||||
}
|
||||
} else if (client.value.addresses && client.value.addresses.length > 0) {
|
||||
selectedAddress.value = DataUtils.calculateFullAddress(client.value.addresses[0]);
|
||||
}
|
||||
if (
|
||||
selectedAddressObject.value?.customLongitude &&
|
||||
selectedAddressObject.value?.customLatitude
|
||||
) {
|
||||
geocode.value = {
|
||||
longitude: selectedAddressObject.value.customLongitude || selectedAddressObject.value.longitude,
|
||||
latitude: selectedAddressObject.value.customLatitude || selectedAddressObject.value.latitude,
|
||||
};
|
||||
} else if (selectedAddress.value) {
|
||||
// geocode.value = await Api.getGeocode(selectedAddress.value);
|
||||
}
|
||||
|
||||
// Check if client is associated with current company
|
||||
if (companyStore.currentCompany && client.value.companies) {
|
||||
const clientHasCompany = client.value.companies.some(company => company.company === companyStore.currentCompany);
|
||||
if (!clientHasCompany) {
|
||||
notificationStore.addWarning(
|
||||
`The selected company is not linked to this client.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching client data in Client.vue: ", error.message || error);
|
||||
} finally {
|
||||
loadingStore.setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
if (clientName) {
|
||||
await getClient(clientName);
|
||||
console.log("Displaying existing client data");
|
||||
}
|
||||
console.debug(
|
||||
"DEBUG: Client.vue mounted with clientName:",
|
||||
clientName,
|
||||
"isNew:",
|
||||
isNew.value,
|
||||
"address:",
|
||||
address,
|
||||
"addresses:",
|
||||
addresses.value,
|
||||
"selectedAddress:",
|
||||
selectedAddress.value,
|
||||
"Does selected address match an address in addresses?:",
|
||||
selectedAddress.value && addresses.value.includes(selectedAddress.value),
|
||||
"geocode:",
|
||||
geocode.value,
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
async (newQuery, oldQuery) => {
|
||||
const clientName = newQuery.client || null;
|
||||
const isNewClient = newQuery.new === "true" || false;
|
||||
const address = newQuery.address || null;
|
||||
|
||||
// Clear client data if switching to new client mode
|
||||
if (isNewClient) {
|
||||
client.value = {};
|
||||
selectedAddress.value = null;
|
||||
geocode.value = {};
|
||||
console.log("Switched to new client mode - cleared client data");
|
||||
} else if (clientName && clientName !== oldQuery.client) {
|
||||
// Load client data if switching to existing client
|
||||
await getClient(clientName);
|
||||
console.log("Route query changed - displaying existing client data");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => companyStore.currentCompany,
|
||||
(newCompany) => {
|
||||
console.log("############# Company changed to:", newCompany);
|
||||
if (!newCompany || !client.value.customerName) return;
|
||||
|
||||
// Check if client is associated with the company
|
||||
let clientHasCompany = false;
|
||||
if (client.value.companies) {
|
||||
clientHasCompany = client.value.companies.some(company => company.company === newCompany);
|
||||
}
|
||||
|
||||
// Check if selected address is associated with the company
|
||||
let addressHasCompany = false;
|
||||
if (selectedAddressData.value?.companies) {
|
||||
addressHasCompany = selectedAddressData.value.companies.some(company => company.company === newCompany);
|
||||
}
|
||||
|
||||
// Show warnings for missing associations
|
||||
if (!clientHasCompany) {
|
||||
notificationStore.addWarning(
|
||||
`The selected company is not linked to this client.`,
|
||||
);
|
||||
} else if (!addressHasCompany) {
|
||||
notificationStore.addWarning(
|
||||
`The selected company is not linked to this address.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Handle address change
|
||||
const handleAddressChange = (newIdx) => {
|
||||
selectedAddressIdx.value = newIdx;
|
||||
// TODO: Update route query with new address
|
||||
};
|
||||
|
||||
// Enable edit mode
|
||||
const enableEditMode = () => {
|
||||
editMode.value = true;
|
||||
};
|
||||
|
||||
// Handle cancel edit or new
|
||||
const handleCancel = () => {
|
||||
if (isNew.value) {
|
||||
// For new client, clear the form data
|
||||
client.value = {};
|
||||
} else {
|
||||
editMode.value = false;
|
||||
// Restore original data if editing
|
||||
}
|
||||
};
|
||||
|
||||
// Handle save edit or create new
|
||||
const handleSubmit = async () => {
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
if (isNew.value) {
|
||||
const clientExists = await Api.checkCustomerExists(client.value.customerName);
|
||||
if (clientExists.exactMatch) {
|
||||
notificationStore.addError("A client with this name already exists. Please choose a different name.");
|
||||
return;
|
||||
}
|
||||
const addressesExist = await Api.checkAddressesExist(client.value.addresses);
|
||||
const contactsExist = await Api.checkContactsExist(client.value.contacts);
|
||||
console.log("Address existence check:", addressesExist);
|
||||
console.log("Contact existence check:", contactsExist);
|
||||
if (addressesExist.length > 0 || contactsExist.length > 0) {
|
||||
// existingAddresses.value = Array.isArray(addressesExist) ? addressesExist : [];
|
||||
// existingContacts.value = Array.isArray(contactsExist) ? contactsExist : [];
|
||||
showExistingModal.value = true;
|
||||
return;
|
||||
}
|
||||
const createdClient = await Api.createClient(client.value);
|
||||
console.log("Created client:", createdClient);
|
||||
notificationStore.addSuccess("Client created successfully!");
|
||||
const strippedName = createdClient.name.split("-#-")[0].trim();
|
||||
// Navigate to the created client
|
||||
router.push('/client?client=' + encodeURIComponent(strippedName));
|
||||
} else {
|
||||
// TODO: Implement save logic
|
||||
notificationStore.addSuccess("Changes saved successfully!");
|
||||
editMode.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting:", error);
|
||||
notificationStore.addError(isNew.value ? "Failed to create client" : "Failed to save changes");
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle address contacts update
|
||||
const handleAddressContactsUpdate = (contactNames) => {
|
||||
console.log("Address contacts updated:", contactNames);
|
||||
// TODO: Store this for saving
|
||||
};
|
||||
|
||||
// Handle primary contact update
|
||||
const handlePrimaryContactUpdate = (contactName) => {
|
||||
console.log("Primary contact updated:", contactName);
|
||||
// TODO: Store this for saving
|
||||
};
|
||||
|
||||
// Handle client update from forms
|
||||
const handleClientUpdate = (newClientData) => {
|
||||
client.value = { ...client.value, ...newClientData };
|
||||
};
|
||||
|
||||
const cancelExisting = () => {
|
||||
showExistingModal.value = false;
|
||||
// TODO: Highlight existing addresses/contacts with red outline
|
||||
};
|
||||
|
||||
const continueWithExisting = async () => {
|
||||
showExistingModal.value = false;
|
||||
try {
|
||||
const createdClient = await Api.createClient(client.value);
|
||||
console.log("Created client:", createdClient);
|
||||
notificationStore.addSuccess("Client created successfully!");
|
||||
const strippedName = createdClient.name.split("-#-")[0].trim();
|
||||
// Navigate to the created client
|
||||
router.push('/client?client=' + encodeURIComponent(strippedName));
|
||||
} catch (error) {
|
||||
console.error("Error creating client:", error);
|
||||
notificationStore.addError("Failed to create client");
|
||||
}
|
||||
};
|
||||
|
||||
const handleNewClientToggle = (isNewClient) => {
|
||||
// Handle toggle if needed
|
||||
};
|
||||
|
||||
const handleCustomerSelected = (clientData) => {
|
||||
// Handle customer selected from search
|
||||
client.value = { ...client.value, ...clientData };
|
||||
};
|
||||
</script>
|
||||
<style lang="css">
|
||||
.tab-info-alert {
|
||||
background-color: #a95e46;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.overview-tabs {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.coming-soon-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
padding: 4rem 2rem;
|
||||
text-align: center;
|
||||
background: var(--surface-card);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.coming-soon-section i {
|
||||
font-size: 4rem;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.coming-soon-section h3 {
|
||||
margin: 0;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.coming-soon-section p {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.client-page {
|
||||
padding-bottom: 5rem; /* Add padding to prevent content from being hidden behind fixed buttons */
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem;
|
||||
background: var(--surface-card);
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--surface-border);
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.existing-modal {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.existing-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.existing-section h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--text-color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.existing-section ul {
|
||||
margin: 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.existing-section li {
|
||||
margin-bottom: 0.25rem;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user