Compare commits
2 commits
15ee0459b5
...
0868e90916
| Author | SHA1 | Date | |
|---|---|---|---|
| 0868e90916 | |||
| 8464fcef87 |
1 changed files with 562 additions and 0 deletions
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…
Add table
Add a link
Reference in a new issue