build out client page, edit functionality, create functionality, data massager

This commit is contained in:
Casey 2025-11-19 22:25:16 -06:00
parent f510645a31
commit 34f2c110d6
15 changed files with 1571 additions and 1681 deletions

View file

@ -1,44 +1,89 @@
<template>
<!-- Client Header -->
<div class="client-header" v-if="client.customerName">
<div class="client-info">
<h2 class="client-name">{{ client.customerName }}</h2>
<div class="address-section" v-if="addresses.length > 0">
<label class="address-label">Address:</label>
<Select
v-if="addresses.length > 1"
v-model="selectedAddress"
:options="addresses"
class="address-dropdown"
placeholder="Select an address"
/>
<span v-else class="single-address">{{ addresses[0] }}</span>
</div>
</div>
</div>
<Tabs value="0">
<TabList>
<Tab value="0">Overview</Tab>
<Tab value="1">Projects <span class=tab-info-alert>1</span></Tab>
<Tab value="1">Projects <span class="tab-info-alert">1</span></Tab>
<Tab value="2">Financials</Tab>
<Tab value="3">History</Tab>
</TabList>
<TabPanels>
<TabPanel value="0">
<div id=overview-tab><h3>Overview</h3></div>
<Overview
:client-data="client"
:selected-address="selectedAddress"
:is-new="isNew"
/>
</TabPanel>
<TabPanel value="1">
<div id=projects-tab><h3>Project Status</h3></div>
<div id="projects-tab"><h3>Project Status</h3></div>
</TabPanel>
<TabPanel value="2">
<div id=financials-tab><h3>Accounting</h3></div>
<div id="financials-tab"><h3>Accounting</h3></div>
</TabPanel>
<TabPanel value="3">
<div id=history-tab><h3>History</h3></div>
<div id="history-tab"><h3>History</h3></div>
</TabPanel>
</TabPanels>
</Tabs>
</template>
<script setup>
import { onMounted, ref } from "vue";
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 Select from "primevue/select";
import Api from "../../api";
import ApiWithToast from "../../api-toast";
import { useRoute } from "vue-router";
import { useLoadingStore } from "../../stores/loading";
import { useNotificationStore } from "../../stores/notifications-primevue";
import DataUtils from "../../utils";
import Overview from "../clientSubPages/Overview.vue";
import ProjectStatus from "../clientSubPages/ProjectStatus.vue";
const route = useRoute();
const loadingStore = useLoadingStore();
const notificationStore = useNotificationStore();
const address = route.query.address || null;
const clientName = route.query.client || null;
const isNew = computed(() => route.query.new === "true" || false);
const clientNames = ref([]);
const client = ref({});
const { clientName } = defineProps({
clientName: { type: String, required: true },
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));
}
return [];
});
const getClientNames = async (type) => {
@ -58,6 +103,30 @@ const getClient = async (name) => {
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),
);
if (fullAddresses.includes(address)) {
selectedAddress.value = address;
} 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,
latitude: selectedAddressObject.value.customLatitude,
};
} else if (selectedAddress.value) {
geocode.value = await Api.getGeocode(selectedAddress.value);
}
} catch (error) {
console.error("Error fetching client data in Client.vue: ", error.message || error);
} finally {
@ -66,18 +135,99 @@ const getClient = async (name) => {
};
onMounted(async () => {
if (clientName === "new") {
// Logic for creating a new client
console.log("Creating a new client");
} else {
// Logic for fetching and displaying existing client data
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");
}
},
);
</script>
<style lang="css">
.client-header {
background: var(--surface-card);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid var(--surface-border);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.client-info {
display: flex;
flex-direction: column;
gap: 1rem;
}
.client-name {
margin: 0;
color: var(--text-color);
font-size: 1.75rem;
font-weight: 600;
}
.address-section {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.address-label {
font-weight: 500;
color: var(--text-color-secondary);
min-width: 70px;
}
.address-dropdown {
min-width: 300px;
flex: 1;
max-width: 500px;
}
.single-address {
color: var(--text-color);
font-size: 0.95rem;
padding: 0.5rem 0;
}
.tab-info-alert {
background-color: a95e46;
background-color: #a95e46;
border-radius: 10px;
color: white;
padding-left: 5px;
@ -85,4 +235,25 @@ onMounted(async () => {
padding-top: 2px;
padding-bottom: 2px;
}
@media (max-width: 768px) {
.client-info {
gap: 0.75rem;
}
.client-name {
font-size: 1.5rem;
}
.address-section {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.address-dropdown {
min-width: 100%;
max-width: 100%;
}
}
</style>