Refactor GoalFormModal to replace user search with customer search functionality, update related filtering logic, and improve progress calculation in DepartmentGoals.vue.
This commit is contained in:
@@ -76,10 +76,12 @@ const getGoalIconColor = (type) => {
|
||||
};
|
||||
|
||||
const getGoalProgress = (goal) => {
|
||||
// Currently, progress is not provided by the API.
|
||||
// We show 0 as default, or we could calculate if we had transaction data.
|
||||
// For a "rich" experience, we'll return 0 for now but the UI supports the value.
|
||||
return goal.progress || 0;
|
||||
// Use the .value to divided by .target and multiply by 100
|
||||
const value = parseFloat(goal.progress.value);
|
||||
const target = parseFloat(goal.criteria.target);
|
||||
if (isNaN(value) || isNaN(target) || target === 0) return 0;
|
||||
const progress = (value / target) * 100;
|
||||
return Math.min(Math.round(progress), 100);
|
||||
};
|
||||
|
||||
const getProgressClass = (progress) => {
|
||||
|
||||
+40
-36
@@ -3,6 +3,7 @@ import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
|
||||
import { searchCustomer, searchCustomerResults } from "@/components/search/economic/customerSearch.vue";
|
||||
|
||||
const props = defineProps({
|
||||
initialDepartments: {
|
||||
@@ -28,29 +29,29 @@ const isEdit = computed(() => !!props.initialGoal);
|
||||
const loading = ref(true);
|
||||
const departments = ref([]);
|
||||
const products = ref([]);
|
||||
const users = ref([]);
|
||||
const customers = ref([]);
|
||||
|
||||
const departmentSearch = ref('');
|
||||
const productSearch = ref('');
|
||||
const userSearch = ref('');
|
||||
const customerSearchQuery = ref('');
|
||||
|
||||
const filteredDepartments = computed(() => {
|
||||
if (!departmentSearch.value) return departments.value;
|
||||
return departments.value.filter(d => d.name?.toLowerCase().includes(departmentSearch.value.toLowerCase()));
|
||||
});
|
||||
|
||||
const searchUsers = useDebounceFn(async (query) => {
|
||||
const searchCustomers = useDebounceFn(async (query) => {
|
||||
if (!query) return;
|
||||
try {
|
||||
const res = await authenticatedRequest('/users', 'GET', { search: query, limit: 100 });
|
||||
const fetched = res.data?.data || [];
|
||||
await searchCustomer(query);
|
||||
const fetched = searchCustomerResults.value || [];
|
||||
fetched.forEach(item => {
|
||||
if (!users.value.find(u => u.customer_number === item.customer_number)) {
|
||||
users.value.push(item);
|
||||
if (!customers.value.find(c => c.customerNumber === item.customerNumber)) {
|
||||
customers.value.push(item);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to search users:", e);
|
||||
console.error("Failed to search customers:", e);
|
||||
}
|
||||
}, 400);
|
||||
|
||||
@@ -69,8 +70,8 @@ const searchProducts = useDebounceFn(async (query) => {
|
||||
}
|
||||
}, 400);
|
||||
|
||||
watch(userSearch, (val) => {
|
||||
if (val.length >= 2) searchUsers(val);
|
||||
watch(customerSearchQuery, (val) => {
|
||||
if (val.length >= 2) searchCustomers(val);
|
||||
});
|
||||
|
||||
watch(productSearch, (val) => {
|
||||
@@ -97,25 +98,24 @@ const filteredProducts = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const filteredUsers = computed(() => {
|
||||
const query = userSearch.value.toLowerCase();
|
||||
const filteredCustomers = computed(() => {
|
||||
const query = customerSearchQuery.value.toLowerCase();
|
||||
const selected = form.value.criteria.users || [];
|
||||
|
||||
let list = users.value.filter(u => {
|
||||
const isSelected = selected.includes(u.customer_number);
|
||||
let list = customers.value.filter(u => {
|
||||
const isSelected = selected.includes(u.customerNumber);
|
||||
if (!query) return true;
|
||||
const matches = (u.display_name?.toLowerCase().includes(query)) ||
|
||||
(u.email?.toLowerCase().includes(query)) ||
|
||||
(u.customer_number?.toString().includes(query));
|
||||
const matches = (u.name?.toLowerCase().includes(query)) ||
|
||||
(u.customerNumber?.toString().includes(query));
|
||||
return isSelected || matches;
|
||||
});
|
||||
|
||||
return list.sort((a, b) => {
|
||||
const aSel = selected.includes(a.customer_number);
|
||||
const bSel = selected.includes(b.customer_number);
|
||||
const aSel = selected.includes(a.customerNumber);
|
||||
const bSel = selected.includes(b.customerNumber);
|
||||
if (aSel && !bSel) return -1;
|
||||
if (!aSel && bSel) return 1;
|
||||
return (a.display_name || '').localeCompare(b.display_name || '');
|
||||
return (a.name || '').localeCompare(b.name || '');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -145,15 +145,15 @@ if (!form.value.criteria.users) form.value.criteria.users = [];
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [deps, prods, usersRes] = await Promise.all([
|
||||
const [deps, prods, customersRes] = await Promise.all([
|
||||
SessionUser.objects.departments.get.all({ limit: 1000 }),
|
||||
SessionUser.objects.products.get.all({ limit: 100 }),
|
||||
authenticatedRequest('/users', 'GET', { limit: 100 })
|
||||
authenticatedRequest('/customers', 'GET', { limit: 100 })
|
||||
]);
|
||||
|
||||
departments.value = deps || [];
|
||||
products.value = prods || [];
|
||||
users.value = usersRes.data?.data || [];
|
||||
customers.value = customersRes.data?.data || [];
|
||||
|
||||
if (isEdit.value) {
|
||||
await fetchInitialDetails();
|
||||
@@ -166,19 +166,23 @@ const fetchData = async () => {
|
||||
};
|
||||
|
||||
const fetchInitialDetails = async () => {
|
||||
// Fetch missing users
|
||||
const missingUserNumbers = (form.value.criteria.users || []).filter(
|
||||
num => !users.value.find(u => u.customer_number === num)
|
||||
// Fetch missing customers
|
||||
const missingCustomerNumbers = (form.value.criteria.users || []).filter(
|
||||
num => !customers.value.find(c => c.customerNumber === num)
|
||||
);
|
||||
if (missingUserNumbers.length > 0) {
|
||||
if (missingCustomerNumbers.length > 0) {
|
||||
try {
|
||||
const filters = missingUserNumbers.map(n => `customer_number:${n}`).join(',');
|
||||
const res = await authenticatedRequest('/users', 'GET', { filters });
|
||||
if (res.data?.data) {
|
||||
users.value.push(...res.data.data);
|
||||
for (const num of missingCustomerNumbers) {
|
||||
const res = await authenticatedRequest('/customers', 'GET', { customer_number: num });
|
||||
const customer = res.data?.data?.economic_customer || res.data?.data;
|
||||
if (customer && !Array.isArray(customer)) {
|
||||
if (!customers.value.find(c => c.customerNumber === customer.customerNumber)) {
|
||||
customers.value.push(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch missing users:", e);
|
||||
console.error("Failed to fetch missing customers:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,14 +377,14 @@ const isType = (type) => form.value.criteria.type === type;
|
||||
</span>
|
||||
</label>
|
||||
<div class="control mb-2">
|
||||
<input class="input is-small" type="text" v-model="userSearch" placeholder="Søg kunder...">
|
||||
<input class="input is-small" type="text" v-model="customerSearchQuery" placeholder="Søg kunder...">
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="scrollable-selection">
|
||||
<div v-for="user in filteredUsers" :key="user.id" class="selection-item">
|
||||
<div v-for="cust in filteredCustomers" :key="cust.customerNumber" class="selection-item">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" :value="user.customer_number" v-model="form.criteria.users">
|
||||
{{ user.display_name || user.email }} ({{ user.customer_number }})
|
||||
<input type="checkbox" :value="cust.customerNumber" v-model="form.criteria.users">
|
||||
{{ cust.name }} ({{ cust.customerNumber }})
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user