fix client creation

This commit is contained in:
Casey 2025-12-13 08:23:03 -06:00
parent 0d7976b140
commit 0c1bb52f1b
9 changed files with 305 additions and 161 deletions

View file

@ -136,67 +136,67 @@ def get_client(client_name):
@frappe.whitelist() @frappe.whitelist()
def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10): def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10):
"""Get paginated client table data with filtering and sorting support.""" """Get paginated client table data with filtering and sorting support."""
try: # try:
print("DEBUG: Raw client table query received:", { # print("DEBUG: Raw client table query received:", {
"filters": filters, # "filters": filters,
"sortings": sortings, # "sortings": sortings,
"page": page, # "page": page,
"page_size": page_size # "page_size": page_size
}) # })
processed_filters, processed_sortings, is_or, page, page_size = process_query_conditions(filters, sortings, page, page_size) # processed_filters, processed_sortings, is_or, page, page_size = process_query_conditions(filters, sortings, page, page_size)
print("DEBUG: Processed filters:", processed_filters) # print("DEBUG: Processed filters:", processed_filters)
print("DEBUG: Processed sortings:", processed_sortings) # print("DEBUG: Processed sortings:", processed_sortings)
# Handle count with proper OR filter support # # Handle count with proper OR filter support
if is_or: # if is_or:
count = frappe.db.sql(*get_count_or_filters("Address", processed_filters))[0][0] # count = frappe.db.sql(*get_count_or_filters("Address", processed_filters))[0][0]
else: # else:
count = frappe.db.count("Address", filters=processed_filters) # count = frappe.db.count("Address", filters=processed_filters)
print("DEBUG: Count of addresses matching filters:", count) # print("DEBUG: Count of addresses matching filters:", count)
address_names = frappe.db.get_all( # address_names = frappe.db.get_all(
"Address", # "Address",
fields=["name"], # fields=["name"],
filters=processed_filters if not is_or else None, # filters=processed_filters if not is_or else None,
or_filters=processed_filters if is_or else None, # or_filters=processed_filters if is_or else None,
limit=page_size, # limit=page_size,
start=(page - 1) * page_size, # start=(page - 1) * page_size,
order_by=processed_sortings # order_by=processed_sortings
) # )
addresses = [frappe.get_doc("Address", addr["name"]).as_dict() for addr in address_names] # addresses = [frappe.get_doc("Address", addr["name"]).as_dict() for addr in address_names]
tableRows = [] # tableRows = []
for address in addresses: # for address in addresses:
tableRow = {} # tableRow = {}
links = address.links # links = address.links
customer_links = [link for link in links if link.link_doctype == "Customer"] if links else None # customer_links = [link for link in links if link.link_doctype == "Customer"] if links else None
customer_name = address.get("custom_customer_to_bill") # customer_name = address.get("custom_customer_to_bill")
if not customer_name and not customer_links: # if not customer_name and not customer_links:
print("DEBUG: No customer links found and no customer to bill.") # print("DEBUG: No customer links found and no customer to bill.")
customer_name = "N/A" # customer_name = "N/A"
elif not customer_name and customer_links: # elif not customer_name and customer_links:
print("DEBUG: No customer to bill. Customer links found:", customer_links) # print("DEBUG: No customer to bill. Customer links found:", customer_links)
customer_name = customer_links[0].link_name # customer_name = customer_links[0].link_name
tableRow["id"] = address["name"] # tableRow["id"] = address["name"]
tableRow["customer_name"] = customer_name # tableRow["customer_name"] = customer_name
tableRow["address"] = ( # tableRow["address"] = (
f"{address['address_line1']}" # f"{address['address_line1']}"
f"{' ' + address['address_line2'] if address['address_line2'] else ''} " # f"{' ' + address['address_line2'] if address['address_line2'] else ''} "
f"{address['city']}, {address['state']} {address['pincode']}" # f"{address['city']}, {address['state']} {address['pincode']}"
) # )
tableRow["appointment_scheduled_status"] = address.custom_onsite_meeting_scheduled # tableRow["appointment_scheduled_status"] = address.custom_onsite_meeting_scheduled
tableRow["estimate_sent_status"] = address.custom_estimate_sent_status # tableRow["estimate_sent_status"] = address.custom_estimate_sent_status
tableRow["job_status"] = address.custom_job_status # tableRow["job_status"] = address.custom_job_status
tableRow["payment_received_status"] = address.custom_payment_received_status # tableRow["payment_received_status"] = address.custom_payment_received_status
tableRows.append(tableRow) # tableRows.append(tableRow)
tableDataDict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size) # tableDataDict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size)
return build_success_response(tableDataDict) # return build_success_response(tableDataDict)
except frappe.ValidationError as ve: # except frappe.ValidationError as ve:
return build_error_response(str(ve), 400) # return build_error_response(str(ve), 400)
except Exception as e: # except Exception as e:
return build_error_response(str(e), 500) # return build_error_response(str(e), 500)
@frappe.whitelist() @frappe.whitelist()
@ -207,27 +207,37 @@ def upsert_client(data):
# Handle customer creation/update # Handle customer creation/update
print("#####DEBUG: Upsert client data received:", data) print("#####DEBUG: Upsert client data received:", data)
print("#####DEBUG: Checking for existing customer with name:", data.get("customer_name")) print("#####DEBUG: Checking for existing customer with name:", data.get("customer_name"))
customer = frappe.db.exists("Customer", {"customer_name": data.get("customer_name")}) customer = frappe.db.exists("Customer", {"customer_name": data.get("customer_name")})
if not customer: if not customer:
new_lead_data = {"doctype": "Lead"} print("#####DEBUG: No existing customer found. Checking for existing lead")
customer = frappe.db.exists("Lead", {"lead_name": data.get("customer_name")})
else:
print("#####DEBUG: Existing customer found:", customer)
if not customer:
print("#####DEBUG: No existing lead found. Creating new lead.")
is_individual = data.get("customer_type") == "Individual" is_individual = data.get("customer_type") == "Individual"
primary_contact = next((c for c in data.get("contacts", []) if c.get("is_primary")), None) primary_contact = next((c for c in data.get("contacts", []) if c.get("is_primary")), None)
if not primary_contact: if not primary_contact:
return build_error_response("Primary contact information is required to create a new customer.", 400) return build_error_response("Primary contact information is required to create a new customer.", 400)
if is_individual: print("#####DEBUG: Primary contact found:", primary_contact)
# Grab the contact that has is_primary true
new_lead_data["first_name"] = primary_contact.get("first_name") new_lead_data = {
new_lead_data["last_name"] = primary_contact.get("last_name")
else:
new_lead_data["company_name"] = data.get("customer_name")
new_lead_data["email_id"] = primary_contact.get("email")
new_lead_data["phone"] = primary_contact.get("phone_number")
new_client_doc = frappe.get_doc({
"doctype": "Lead", "doctype": "Lead",
"customer_name": data.get("customer_name"), "lead_name": data.get("customer_name"),
"customer_type": data.get("customer_type") "first_name": primary_contact.get("first_name"),
}).insert(ignore_permissions=True) "last_name": primary_contact.get("last_name"),
"email_id": primary_contact.get("email"),
"phone": primary_contact.get("phone_number"),
"customer_type": data.get("customer_type"),
"company": data.get("company")
}
print("#####DEBUG: New lead data prepared:", new_lead_data)
new_client_doc = frappe.get_doc(new_lead_data).insert(ignore_permissions=True)
else: else:
new_client_doc = frappe.get_doc("Customer", data.get("customer_name")) new_client_doc = frappe.get_doc("Customer", data.get("customer_name"))
print(f"#####DEBUG: {new_client_doc.doctype}:", new_client_doc.as_dict()) print(f"#####DEBUG: {new_client_doc.doctype}:", new_client_doc.as_dict())
@ -265,6 +275,7 @@ def upsert_client(data):
contact_data = json.loads(contact_data) contact_data = json.loads(contact_data)
print("#####DEBUG: Processing contact data:", contact_data) print("#####DEBUG: Processing contact data:", contact_data)
contact_exists = frappe.db.exists("Contact", {"email_id": contact_data.get("email"), "phone": contact_data.get("phone_number")}) contact_exists = frappe.db.exists("Contact", {"email_id": contact_data.get("email"), "phone": contact_data.get("phone_number")})
print("Contact exists check:", contact_exists)
if not contact_exists: if not contact_exists:
contact_doc = frappe.get_doc({ contact_doc = frappe.get_doc({
"doctype": "Contact", "doctype": "Contact",
@ -288,7 +299,7 @@ def upsert_client(data):
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
print("Created new contact:", contact_doc.as_dict()) print("Created new contact:", contact_doc.as_dict())
else: else:
contact_doc = frappe.get_doc("Contact", {"email_id": data.get("email")}) contact_doc = frappe.get_doc("Contact", {"email_id": contact_data.get("email"), "phone": contact_data.get("phone_number")})
print("Contact already exists:", contact_doc.as_dict()) print("Contact already exists:", contact_doc.as_dict())
contact_docs.append(contact_doc) contact_docs.append(contact_doc)

View file

@ -1,5 +1,6 @@
import os import os
import subprocess import subprocess
import sys
import frappe import frappe
from .utils import create_module from .utils import create_module
@ -70,6 +71,15 @@ def add_custom_fields():
print("\n🔧 Adding custom fields to Address doctype...") print("\n🔧 Adding custom fields to Address doctype...")
custom_fields = { custom_fields = {
"Lead": [
dict(
fieldname="customer_type",
label="Customer Type",
fieldtype="Select",
options="Individual\nCompany\nPartnership",
insert_after="lead_name"
)
],
"Address": [ "Address": [
dict( dict(
fieldname="full_address", fieldname="full_address",
@ -315,21 +325,29 @@ def update_address_fields():
filled_length = int(bar_length * index // total_addresses) filled_length = int(bar_length * index // total_addresses)
bar = '' * filled_length + '' * (bar_length - filled_length) bar = '' * filled_length + '' * (bar_length - filled_length)
# Print a three-line, refreshing progress block to avoid terminal wrap # Print a three-line, refreshing progress block without adding new lines each loop
progress_line = f"📊 Progress: [{bar}] {progress_percentage:3d}% ({index}/{total_addresses})" progress_line = f"📊 Progress: [{bar}] {progress_percentage:3d}% ({index}/{total_addresses})"
counters_line = f" Fields updated: {total_field_updates} | Addresses updated: {addresses_updated}" counters_line = f" Fields updated: {total_field_updates} | Addresses updated: {addresses_updated}"
detail_line = f" Processing: {name[:40]}..." detail_line = f" Processing: {name[:40]}..."
if index == 1: if index == 1:
# Save cursor position at the start of the progress block # First render: write the three lines
print("\033[s", end='') sys.stdout.write(
f"\r\033[K{progress_line}\n"
f"\033[K{counters_line}\n"
f"\033[K{detail_line}"
)
else: else:
# Restore to the saved cursor position to rewrite the three-line block # Move cursor up 3 lines, then rewrite each line in place
print("\033[u", end='') sys.stdout.write("\033[2F")
sys.stdout.write(f"\r\033[K{progress_line}\n")
sys.stdout.write(f"\033[K{counters_line}\n")
sys.stdout.write(f"\033[K{detail_line}")
print(f"\r\033[K{progress_line}") if index == total_addresses:
print(f"\r\033[K{counters_line}") sys.stdout.write("\n")
print(f"\r\033[K{detail_line}", end='' if index != total_addresses else '\n', flush=True)
sys.stdout.flush()
should_update = False should_update = False
address = frappe.get_doc("Address", name) address = frappe.get_doc("Address", name)

View file

@ -1,7 +1,6 @@
<script setup> <script setup>
import { IconoirProvider } from "@iconoir/vue"; import { IconoirProvider } from "@iconoir/vue";
import SideBar from "./components/SideBar.vue"; import SideBar from "./components/SideBar.vue";
import CompanySelector from "./components/CompanySelector.vue";
import { watchEffect, onMounted, ref } from "vue"; import { watchEffect, onMounted, ref } from "vue";
import { useCompanyStore } from "@/stores/company"; import { useCompanyStore } from "@/stores/company";
import { useThemeStore } from "@/stores/theme"; import { useThemeStore } from "@/stores/theme";
@ -48,7 +47,6 @@ watchEffect(() => {
> >
<div id="snw-ui"> <div id="snw-ui">
<div class="sidebar-column"> <div class="sidebar-column">
<CompanySelector />
<SideBar /> <SideBar />
</div> </div>
<div id="display-content"> <div id="display-content">
@ -84,7 +82,7 @@ watchEffect(() => {
width: 100%; width: 100%;
margin: 10px auto; margin: 10px auto;
height: 90vh; height: 90vh;
background: linear-gradient(145deg, var(--theme-surface) 0%, var(--theme-surface-alt) 60%); background: var(--theme-gradient);
} }
.sidebar-column { .sidebar-column {

View file

@ -16,8 +16,8 @@ const selectedCompany = computed({
const fontSize = computed(() => { const fontSize = computed(() => {
const len = selectedCompany.value.length; const len = selectedCompany.value.length;
if (len > 12) return '0.75rem'; if (len > 12) return '0.71rem';
if (len > 10) return '0.8rem'; if (len > 10) return '0.75rem';
return '0.875rem'; return '0.875rem';
}); });
</script> </script>
@ -41,7 +41,7 @@ const fontSize = computed(() => {
} }
.company-select { .company-select {
width: 170px; width: 100%;
} }
:deep(.p-select) { :deep(.p-select) {

View file

@ -3,6 +3,7 @@ import { ref, nextTick } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useModalStore } from "@/stores/modal"; import { useModalStore } from "@/stores/modal";
import { useNotificationStore } from "@/stores/notifications-primevue" import { useNotificationStore } from "@/stores/notifications-primevue"
import CompanySelector from "./CompanySelector.vue";
import { import {
Home, Home,
Community, Community,
@ -153,6 +154,9 @@ const handleCategoryClick = (category) => {
<template> <template>
<div id="sidebar" class="sidebar" :class="{ collapsed: isCollapsed }"> <div id="sidebar" class="sidebar" :class="{ collapsed: isCollapsed }">
<div class="sidebar-top" :class="{ collapsed: isCollapsed }">
<CompanySelector />
</div>
<!-- Toggle Button --> <!-- Toggle Button -->
<button <button
class="sidebar-toggle" class="sidebar-toggle"
@ -282,7 +286,6 @@ const handleCategoryClick = (category) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 170px; width: 170px;
min-width: 170px;
align-self: flex-start; align-self: flex-start;
gap: 5px; gap: 5px;
background-color: var(--theme-surface-alt); background-color: var(--theme-surface-alt);
@ -294,6 +297,15 @@ const handleCategoryClick = (category) => {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.sidebar-top {
margin-bottom: 6px;
}
.sidebar-top.collapsed {
margin-bottom: 8px;
width: 100%;
}
#sidebar.collapsed { #sidebar.collapsed {
width: 56px; width: 56px;
min-width: 56px; min-width: 56px;

View file

@ -36,6 +36,7 @@ const handleItem = (item) => {
if (typeof item?.command === "function") { if (typeof item?.command === "function") {
item.command(); item.command();
} }
toggle();
}; };
watch( watch(

View file

@ -1,78 +1,87 @@
<template> <template>
<div class="form-section"> <div>
<div class="section-header"> <div class="form-section">
<h3>Client Information</h3> <div class="section-header">
<label class="toggle-container" v-if="!isEditMode"> <h3>Client Information</h3>
<v-switch v-model="isNewClient" color="success" /> <label class="toggle-container" v-if="!isEditMode">
<span class="toggle-label">New Client</span> <v-switch v-model="isNewClient" color="success" />
</label> <span class="toggle-label">New Client</span>
</div> </label>
<div class="form-grid"> </div>
<div class="form-field"> <div class="form-grid">
<label for="customer-name"> Customer Name <span class="required">*</span> </label> <div class="form-field">
<div class="input-with-button"> <label for="customer-name"> Customer Name <span class="required">*</span> </label>
<InputText <div class="input-with-button">
id="customer-name" <InputText
v-model="localFormData.customerName" id="customer-name"
:disabled="isSubmitting || isEditMode" v-model="localFormData.customerName"
placeholder="Enter customer name" :disabled="isSubmitting || isEditMode"
placeholder="Enter customer name"
class="w-full"
/>
<Button
label="Check Client"
size="small"
icon="pi pi-user-check"
class="check-btn"
@click="checkCustomerExists"
:disabled="isSubmitting || !localFormData.customerName.trim()"
>Check</Button>
<Button
v-if="!isNewClient && !isEditMode"
@click="searchCustomers"
:disabled="isSubmitting || !localFormData.customerName.trim()"
size="small"
icon="pi pi-search"
class="search-btn"
></Button>
</div>
</div>
<div class="form-field">
<label for="customer-type"> Customer Type <span class="required">*</span> </label>
<Select
id="customer-type"
v-model="localFormData.customerType"
:options="customerTypeOptions"
:disabled="isSubmitting || (!isNewClient && !isEditMode)"
placeholder="Select customer type"
class="w-full" class="w-full"
/> />
<Button
v-if="!isNewClient && !isEditMode"
@click="searchCustomers"
:disabled="isSubmitting || !localFormData.customerName.trim()"
size="small"
icon="pi pi-search"
class="search-btn"
></Button>
</div> </div>
</div> </div>
<div class="form-field">
<label for="customer-type"> Customer Type <span class="required">*</span> </label>
<Select
id="customer-type"
v-model="localFormData.customerType"
:options="customerTypeOptions"
:disabled="isSubmitting || (!isNewClient && !isEditMode)"
placeholder="Select customer type"
class="w-full"
/>
</div>
</div> </div>
<!-- Customer Search Results Modal -->
<Dialog
:visible="showCustomerSearchModal"
@update:visible="showCustomerSearchModal = $event"
header="Select Customer"
:modal="true"
class="search-dialog"
>
<div class="search-results">
<div v-if="customerSearchResults.length === 0" class="no-results">
<i class="pi pi-info-circle"></i>
<p>No customers found matching your search.</p>
</div>
<div v-else class="results-list">
<div
v-for="(customerName, index) in customerSearchResults"
:key="index"
class="result-item"
@click="selectCustomer(customerName)"
>
<strong>{{ customerName }}</strong>
<i class="pi pi-chevron-right"></i>
</div>
</div>
</div>
<template #footer>
<Button label="Cancel" severity="secondary" @click="showCustomerSearchModal = false" />
</template>
</Dialog>
</div> </div>
</template><script setup>
<!-- Customer Search Results Modal -->
<Dialog
v-model:visible="showCustomerSearchModal"
header="Select Customer"
:modal="true"
class="search-dialog"
>
<div class="search-results">
<div v-if="customerSearchResults.length === 0" class="no-results">
<i class="pi pi-info-circle"></i>
<p>No customers found matching your search.</p>
</div>
<div v-else class="results-list">
<div
v-for="(customerName, index) in customerSearchResults"
:key="index"
class="result-item"
@click="selectCustomer(customerName)"
>
<strong>{{ customerName }}</strong>
<i class="pi pi-chevron-right"></i>
</div>
</div>
</div>
<template #footer>
<Button label="Cancel" severity="secondary" @click="showCustomerSearchModal = false" />
</template>
</Dialog>
</template>
<script setup>
import { ref, watch, computed } from "vue"; import { ref, watch, computed } from "vue";
import InputText from "primevue/inputtext"; import InputText from "primevue/inputtext";
import Select from "primevue/select"; import Select from "primevue/select";
@ -109,6 +118,30 @@ const showCustomerSearchModal = ref(false);
const customerSearchResults = ref([]); const customerSearchResults = ref([]);
const customerTypeOptions = ["Individual", "Partnership", "Company"]; const customerTypeOptions = ["Individual", "Partnership", "Company"];
const mapContactsFromClient = (contacts = []) => {
if (!Array.isArray(contacts) || contacts.length === 0) {
return [
{
firstName: "",
lastName: "",
phoneNumber: "",
email: "",
contactRole: "",
isPrimary: true,
},
];
}
return contacts.map((contact, index) => ({
firstName: contact.firstName || "",
lastName: contact.lastName || "",
phoneNumber: contact.phoneNumber || contact.phone || contact.mobileNo || "",
email: contact.email || contact.emailId || contact.customEmail || "",
contactRole: contact.contactRole || contact.role || "",
isPrimary: contact.isPrimary ?? contact.isPrimaryContact ?? index === 0,
}));
};
// Watch for toggle changes // Watch for toggle changes
watch(isNewClient, (newValue) => { watch(isNewClient, (newValue) => {
emit("newClientToggle", newValue); emit("newClientToggle", newValue);
@ -138,6 +171,45 @@ const searchCustomers = async () => {
} }
}; };
const checkCustomerExists = async () => {
const searchTerm = localFormData.value.customerName.trim();
if (!searchTerm) return;
try {
const client = await Api.getClient(searchTerm);
if (!client) {
notificationStore.addInfo("Customer is not in our system yet.");
return;
}
localFormData.value.customerName = client.customerName || searchTerm;
localFormData.value.customerType = client.customerType || localFormData.value.customerType;
localFormData.value.contacts = mapContactsFromClient(client.contacts);
isNewClient.value = false;
showCustomerSearchModal.value = false;
emit("customerSelected", client);
notificationStore.addSuccess(
`Customer ${localFormData.value.customerName} found and loaded from system.`,
);
} catch (error) {
console.error("Error checking customer:", error);
const message =
typeof error?.message === "string" &&
error.message.toLowerCase().includes("not found")
? "Customer is not in our system yet."
: "Failed to check customer. Please try again.";
if (message.includes("not in our system")) {
notificationStore.addInfo(message);
} else {
notificationStore.addError(message);
}
}
};
const selectCustomer = async (customerName) => { const selectCustomer = async (customerName) => {
try { try {
// Fetch full customer data // Fetch full customer data
@ -145,6 +217,7 @@ const selectCustomer = async (customerName) => {
localFormData.value.customerName = clientData.customerName; localFormData.value.customerName = clientData.customerName;
localFormData.value.customerType = clientData.customerType; localFormData.value.customerType = clientData.customerType;
localFormData.value.contacts = mapContactsFromClient(clientData.contacts);
showCustomerSearchModal.value = false; showCustomerSearchModal.value = false;
// Pass the full client data including contacts // Pass the full client data including contacts
@ -306,6 +379,24 @@ defineExpose({
background: var(--surface-hover); background: var(--surface-hover);
} }
.check-btn {
border: 1px solid var(--primary-color);
color: var(--primary-color);
background: var(--surface-card);
padding: 0.25rem 0.75rem;
min-width: 8rem;
justify-content: center;
}
.check-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.check-btn:hover:not(:disabled) {
background: var(--surface-hover);
}
.search-btn { .search-btn {
background: var(--primary-color); background: var(--primary-color);
border: 1px solid var(--primary-color); border: 1px solid var(--primary-color);
@ -317,6 +408,7 @@ defineExpose({
border-radius: 4px; border-radius: 4px;
transition: background 0.2s; transition: background 0.2s;
color: white; color: white;
min-width: 8rem;
} }
.search-btn:disabled { .search-btn:disabled {

View file

@ -2,8 +2,8 @@ import { defineStore } from "pinia";
export const useCompanyStore = defineStore("company", { export const useCompanyStore = defineStore("company", {
state: () => ({ state: () => ({
companies: ["SprinklersNW", "Nuco Yard Care", "Lowe Fencing", "Veritas Stone", "Daniel's Landscape Supplies"], companies: ["Sprinklers Northwest", "Nuco Yard Care", "Lowe Fencing", "Veritas Stone", "Daniels Landscape Supplies"],
selectedCompany: "SprinklersNW", selectedCompany: "Sprinklers Northwest",
}), }),
getters: { getters: {

View file

@ -1,15 +1,17 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
const themeMap = { const themeMap = {
SprinklersNW: { "Sprinklers Northwest": {
primary: "#0f7ac7", primary: "#75bdf1ff",
primaryStrong: "#0c639f", primaryStrong: "#068ee9ff",
secondary: "#2ca66f", secondary: "#2ca66f",
accent: "#8fd9a8", accent: "#8fd9a8",
primaryGradientStart: "#0c639f", primaryGradientStart: "#70b9e9ff",
primaryGradientEnd: "#1390e0", primaryGradientEnd: "#1390e0",
primaryGradient: "linear-gradient(90deg, #a6d2f0ff 0%, #a1d7f8ff 100%)",
secondaryGradientStart: "#2c9b64", secondaryGradientStart: "#2c9b64",
secondaryGradientEnd: "#38c487", secondaryGradientEnd: "#38c487",
secondaryGradient: "linear-gradient(90deg, #2c9b64 0%, #38c487 100%)",
surface: "#cadcf1ff", surface: "#cadcf1ff",
surfaceAlt: "#dbfdefff", surfaceAlt: "#dbfdefff",
border: "#cfe5f7", border: "#cfe5f7",
@ -26,8 +28,10 @@ const themeMap = {
accent: "#f7de6d", accent: "#f7de6d",
primaryGradientStart: "#2f6425", primaryGradientStart: "#2f6425",
primaryGradientEnd: "#4a9f3a", primaryGradientEnd: "#4a9f3a",
primaryGradient: "linear-gradient(90deg, #2f6425 0%, #4a9f3a 100%)",
secondaryGradientStart: "#d2a106", secondaryGradientStart: "#d2a106",
secondaryGradientEnd: "#f7de6d", secondaryGradientEnd: "#f7de6d",
secondaryGradient: "linear-gradient(90deg, #d2a106 0%, #f7de6d 100%)",
surface: "#f7fbe9", surface: "#f7fbe9",
surfaceAlt: "#f1f4d6", surfaceAlt: "#f1f4d6",
border: "#dfe8b5", border: "#dfe8b5",
@ -44,8 +48,10 @@ const themeMap = {
accent: "#9cc6ff", accent: "#9cc6ff",
primaryGradientStart: "#232d3f", primaryGradientStart: "#232d3f",
primaryGradientEnd: "#375073", primaryGradientEnd: "#375073",
primaryGradient: "linear-gradient(90deg, #232d3f 0%, #375073 100%)",
secondaryGradientStart: "#4f8ee5", secondaryGradientStart: "#4f8ee5",
secondaryGradientEnd: "#5fa4ff", secondaryGradientEnd: "#5fa4ff",
secondaryGradient: "linear-gradient(90deg, #4f8ee5 0%, #5fa4ff 100%)",
surface: "#f5f7fb", surface: "#f5f7fb",
surfaceAlt: "#e7ecf5", surfaceAlt: "#e7ecf5",
border: "#ced6e5", border: "#ced6e5",
@ -62,8 +68,10 @@ const themeMap = {
accent: "#d8d0c5", accent: "#d8d0c5",
primaryGradientStart: "#5e564d", primaryGradientStart: "#5e564d",
primaryGradientEnd: "#8a8073", primaryGradientEnd: "#8a8073",
primaryGradient: "linear-gradient(90deg, #5e564d 0%, #8a8073 100%)",
secondaryGradientStart: "#b2a89c", secondaryGradientStart: "#b2a89c",
secondaryGradientEnd: "#cfc6b8", secondaryGradientEnd: "#cfc6b8",
secondaryGradient: "linear-gradient(90deg, #b2a89c 0%, #cfc6b8 100%)",
surface: "#f7f5f2", surface: "#f7f5f2",
surfaceAlt: "#ebe6df", surfaceAlt: "#ebe6df",
border: "#d8d0c5", border: "#d8d0c5",
@ -73,15 +81,17 @@ const themeMap = {
textDark: "#231c16", textDark: "#231c16",
textLight: "#ffffff", textLight: "#ffffff",
}, },
"Daniel's Landscape Supplies": { "Daniels Landscape Supplies": {
primary: "#2f6b2f", primary: "#2f6b2f",
primaryStrong: "#245224", primaryStrong: "#245224",
secondary: "#f28c28", secondary: "#f28c28",
accent: "#ffc174", accent: "#ffc174",
primaryGradientStart: "#245224", primaryGradientStart: "#245224",
primaryGradientEnd: "#3a8a3a", primaryGradientEnd: "#3a8a3a",
primaryGradient: "linear-gradient(90deg, #245224 0%, #3a8a3a 100%)",
secondaryGradientStart: "#f28c28", secondaryGradientStart: "#f28c28",
secondaryGradientEnd: "#ffc174", secondaryGradientEnd: "#ffc174",
secondaryGradient: "linear-gradient(90deg, #f28c28 0%, #ffc174 100%)",
surface: "#f8fbf4", surface: "#f8fbf4",
surfaceAlt: "#f2f1e9", surfaceAlt: "#f2f1e9",
border: "#d9e5cc", border: "#d9e5cc",
@ -109,8 +119,10 @@ export const useThemeStore = defineStore("theme", {
root.style.setProperty("--theme-accent", theme.accent); root.style.setProperty("--theme-accent", theme.accent);
root.style.setProperty("--theme-gradient-start", theme.primaryGradientStart); root.style.setProperty("--theme-gradient-start", theme.primaryGradientStart);
root.style.setProperty("--theme-gradient-end", theme.primaryGradientEnd); root.style.setProperty("--theme-gradient-end", theme.primaryGradientEnd);
root.style.setProperty("--theme-gradient", theme.primaryGradient);
root.style.setProperty("--theme-secondary-gradient-start", theme.secondaryGradientStart); root.style.setProperty("--theme-secondary-gradient-start", theme.secondaryGradientStart);
root.style.setProperty("--theme-secondary-gradient-end", theme.secondaryGradientEnd); root.style.setProperty("--theme-secondary-gradient-end", theme.secondaryGradientEnd);
root.style.setProperty("--theme-secondary-gradient", theme.secondaryGradient);
root.style.setProperty("--theme-surface", theme.surface); root.style.setProperty("--theme-surface", theme.surface);
root.style.setProperty("--theme-surface-alt", theme.surfaceAlt); root.style.setProperty("--theme-surface-alt", theme.surfaceAlt);
root.style.setProperty("--theme-border", theme.border); root.style.setProperty("--theme-border", theme.border);