1import { __ } from "@/translation";
2import { DocumentResource, Error } from "@/types";
3import { ODCustomer } from "@/types/doctypes";
4import { getErrorMessage } from "@/utils";
5import { call, createDocumentResource } from "frappe-ui";
6import { computed, h, markRaw, reactive, watch } from "vue";
7import { useRouter } from "vue-router";
8import LucideGlobe from "~icons/lucide/globe";
9import LucideMapPin from "~icons/lucide/map-pin";
10import LucideUser from "~icons/lucide/user";
11import LucideUsers from "~icons/lucide/users";
12import { OrganizationsIcon } from "../components/icons";
13
14const customerCache: Record<string, DocumentResource<ODCustomer>> = {};
15
16export function useCustomer(name: string) {
17 const router = useRouter();
18 const state = useCustomerState(name);
19 const doc = findCustomerDoc();
20
21 async function handleDelete({
22 deleteLinkedTickets,
23 }: {
24 deleteLinkedTickets: boolean;
25 }) {
26 try {
27 await call("opendesk.api.customer.delete_customer", {
28 name,
29 delete_tickets: deleteLinkedTickets,
30 });
31 router.push({ name: "CustomerList" });
32 } catch (err) {
33 getErrorMessage(err as Error, true);
34 throw err;
35 }
36 }
37
38 watch(
39 () => doc.doc,
40 (data) => {
41 if (data) {
42 state.name = data.customer_name || "";
43 state.customerType = data.customer_type || "Company";
44 state.domain = data.domain || "";
45 state.image = data.image || "";
46 state.country = data.country || "";
47 }
48 },
49 { immediate: true }
50 );
51
52 const isCustomerInfoChanged = computed(() => {
53 return (
54 state.domain !== (doc.doc?.domain || "") ||
55 state.image !== (doc.doc?.image || "") ||
56 state.country !== (doc.doc?.country || "") ||
57 state.customerType !== (doc.doc?.customer_type || "Company")
58 );
59 });
60 const hasNameChanged = computed(() => state.name !== doc.doc?.customer_name);
61 const isDirty = computed(() => {
62 return isCustomerInfoChanged.value || hasNameChanged.value || false;
63 });
64
65 function findCustomerDoc(): DocumentResource<ODCustomer> {
66 const key = name as string;
67 const cached = customerCache[key];
68 if (cached) return cached;
69 else {
70 const newDoc = createDocumentResource({
71 doctype: "OD Customer",
72 name: name,
73 whitelistedMethods: {
74 getContacts: "get_contacts",
75 addContacts: "add_contacts",
76 getPendingInvites: "get_pending_invites",
77 },
78 });
79 customerCache[key] = newDoc;
80 return newDoc;
81 }
82 }
83
84 return {
85 doc,
86 state,
87 isCustomerInfoChanged,
88 hasNameChanged,
89 isDirty,
90 handleDelete,
91 };
92}
93export function useCustomerState(name?: string | null) {
94 return reactive<Record<StateKey, string>>({
95 name: name ? name : "",
96 customerType: "Company",
97 domain: "",
98 image: "",
99 country: "",
100 });
101}
102
103function usePrimaryContactState() {
104 return reactive<Record<PrimaryContactKey, string>>({
105 firstName: "",
106 lastName: "",
107 email: "",
108 mobileNo: "",
109 });
110}
111
112/** Bundles the customer + primary-contact state for the New Customer dialog. */
113export function useNewCustomerForm() {
114 const state = useCustomerState();
115 const primaryContact = usePrimaryContactState();
116 applyDefaults();
117
118 function reset() {
119 Object.assign(state, useCustomerState());
120 Object.assign(primaryContact, usePrimaryContactState());
121 applyDefaults();
122 }
123
124 function applyDefaults() {
125 state.country = window.default_country || "";
126 }
127
128 return { state, primaryContact, reset };
129}
130
131type StateKey = "name" | "customerType" | "domain" | "image" | "country";
132
133type PrimaryContactKey = "firstName" | "lastName" | "email" | "mobileNo";
134
135interface FieldConfig {
136 key: StateKey;
137 type: "text" | "select" | "textarea" | "checkbox" | "autocomplete" | "Link";
138 label: string;
139 required?: boolean;
140 placeholder?: string;
141 description?: string;
142 prefix?: ReturnType<typeof markRaw> | ReturnType<typeof h>;
143 doctype?: string; // only for Link type
144 options?: SelectOption[]; // only for select type
145}
146
147interface SelectOption {
148 label: string;
149 value: string;
150 icon?: ReturnType<typeof markRaw> | ReturnType<typeof h>;
151}
152export const customerTypeOptions: SelectOption[] = [
153 {
154 label: __("Company"),
155 value: "Company",
156 icon: markRaw(OrganizationsIcon),
157 },
158 {
159 label: __("Individual"),
160 value: "Individual",
161 icon: markRaw(LucideUser),
162 },
163 {
164 label: __("Partnership"),
165 value: "Partnership",
166 icon: markRaw(LucideUsers),
167 },
168];
169
170export const customerFields: FieldConfig[] = [
171 {
172 key: "name",
173 type: "text",
174 label: __("Name"),
175 required: true,
176 },
177 {
178 key: "customerType",
179 type: "select",
180 label: __("Customer Type"),
181 options: customerTypeOptions,
182 },
183 {
184 key: "country",
185 type: "Link",
186 label: __("Country"),
187 placeholder: __("Select Country"),
188 prefix: h(LucideMapPin, { class: "size-4 mr-1.5" }),
189 doctype: "Country",
190 },
191 {
192 key: "domain",
193 type: "text",
194 label: __("Domain"),
195 placeholder: "example.com",
196 prefix: h(LucideGlobe, { class: "size-4" }),
197 },
198];
199