Skip to content

Commit 51a1668

Browse files
committed
Create DateRangeSearch component, implement it in all the necessary views, create some search bars, calculate delivery notes summary / totals
1 parent 3a4ccc3 commit 51a1668

File tree

6 files changed

+347
-26
lines changed

6 files changed

+347
-26
lines changed

resources/js/Components/DataTable.vue

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup>
2-
import { computed, ref, watch } from 'vue';
2+
import { ref, watch } from 'vue';
33
44
const props = defineProps({
55
data: {
@@ -18,30 +18,30 @@ const props = defineProps({
1818
1919
const currentPage = ref(1);
2020
21-
const totalItems = computed(() => props.data.length);
22-
const totalPages = computed(() => Math.ceil(totalItems.value / props.itemsPerPage));
21+
const getTotalItems = () => props.data.length;
22+
const getTotalPages = () => Math.ceil(getTotalItems() / props.itemsPerPage);
2323
24-
const startIndex = computed(() => (currentPage.value - 1) * props.itemsPerPage);
25-
const endIndex = computed(() => Math.min(startIndex.value + props.itemsPerPage, totalItems.value));
24+
const getStartIndex = () => (currentPage.value - 1) * props.itemsPerPage;
25+
const getEndIndex = () => Math.min(getStartIndex() + props.itemsPerPage, getTotalItems());
2626
2727
const getNestedValue = (obj, path) => {
2828
return path.split('.').reduce((current, key) =>
2929
current ? current[key] : undefined, obj
3030
);
3131
};
3232
33-
const paginatedData = computed(() => {
34-
return props.data.slice(startIndex.value, endIndex.value);
35-
});
33+
const getPaginatedData = () => {
34+
return props.data.slice(getStartIndex(), getEndIndex());
35+
};
3636
37-
const displayedPages = computed(() => {
37+
const getDisplayedPages = () => {
3838
const delta = 2;
3939
const range = [];
4040
const rangeWithDots = [];
4141
let l;
4242
43-
for (let i = 1; i <= totalPages.value; i++) {
44-
if (i === 1 || i === totalPages.value ||
43+
for (let i = 1; i <= getTotalPages(); i++) {
44+
if (i === 1 || i === getTotalPages() ||
4545
(i >= currentPage.value - delta && i <= currentPage.value + delta)) {
4646
range.push(i);
4747
}
@@ -60,7 +60,7 @@ const displayedPages = computed(() => {
6060
});
6161
6262
return rangeWithDots;
63-
});
63+
};
6464
6565
const previousPage = () => {
6666
if (currentPage.value > 1) {
@@ -69,7 +69,7 @@ const previousPage = () => {
6969
};
7070
7171
const nextPage = () => {
72-
if (currentPage.value < totalPages.value) {
72+
if (currentPage.value < getTotalPages()) {
7373
currentPage.value++;
7474
}
7575
};
@@ -98,7 +98,7 @@ watch(() => props.data, () => {
9898
</tr>
9999
</thead>
100100
<tbody class="bg-white">
101-
<tr v-for="item in paginatedData"
101+
<tr v-for="item in getPaginatedData()"
102102
:key="item.id"
103103
class="hover:bg-gray-50 border-b border-gray-200">
104104
<td v-for="column in columns"
@@ -120,21 +120,21 @@ watch(() => props.data, () => {
120120
Anterior
121121
</button>
122122
<button @click="nextPage"
123-
:disabled="currentPage >= totalPages"
123+
:disabled="currentPage >= getTotalPages()"
124124
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
125-
:class="{ 'opacity-50 cursor-not-allowed': currentPage >= totalPages }">
125+
:class="{ 'opacity-50 cursor-not-allowed': currentPage >= getTotalPages() }">
126126
Siguiente
127127
</button>
128128
</div>
129129
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
130130
<div>
131131
<p class="text-sm text-gray-700">
132132
Mostrando
133-
<span class="font-medium">{{ startIndex + 1 }}</span>
133+
<span class="font-medium">{{ getStartIndex() + 1 }}</span>
134134
a
135-
<span class="font-medium">{{ endIndex }}</span>
135+
<span class="font-medium">{{ getEndIndex() }}</span>
136136
de
137-
<span class="font-medium">{{ totalItems }}</span>
137+
<span class="font-medium">{{ getTotalItems() }}</span>
138138
resultados
139139
</p>
140140
</div>
@@ -149,7 +149,7 @@ watch(() => props.data, () => {
149149
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
150150
</svg>
151151
</button>
152-
<button v-for="page in displayedPages"
152+
<button v-for="page in getDisplayedPages()"
153153
:key="page"
154154
@click="goToPage(page)"
155155
:class="[
@@ -161,9 +161,9 @@ watch(() => props.data, () => {
161161
{{ page }}
162162
</button>
163163
<button @click="nextPage"
164-
:disabled="currentPage >= totalPages"
164+
:disabled="currentPage >= getTotalPages()"
165165
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
166-
:class="{ 'opacity-50 cursor-not-allowed': currentPage >= totalPages }">
166+
:class="{ 'opacity-50 cursor-not-allowed': currentPage >= getTotalPages() }">
167167
<span class="sr-only">Siguiente</span>
168168
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
169169
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup>
2+
import { ref, watch } from 'vue';
3+
import InputLabel from '@/Components/InputLabel.vue';
4+
5+
const props = defineProps({
6+
startLabel: {
7+
type: String,
8+
default: 'Start Date'
9+
},
10+
endLabel: {
11+
type: String,
12+
default: 'End Date'
13+
}
14+
});
15+
16+
const emit = defineEmits(['update:dateRange']);
17+
18+
const startDate = ref('');
19+
const endDate = ref('');
20+
21+
watch([startDate, endDate], ([newStartDate, newEndDate]) => {
22+
emit('update:dateRange', {
23+
startDate: newStartDate,
24+
endDate: newEndDate
25+
});
26+
});
27+
</script>
28+
29+
<template>
30+
<div class="flex flex-col sm:flex-row gap-4">
31+
<div class="flex-1">
32+
<InputLabel :value="startLabel" class="mb-1" />
33+
<input
34+
type="date"
35+
v-model="startDate"
36+
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
37+
:max="endDate"
38+
/>
39+
</div>
40+
<div class="flex-1">
41+
<InputLabel :value="endLabel" class="mb-1" />
42+
<input
43+
type="date"
44+
v-model="endDate"
45+
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
46+
:min="startDate"
47+
/>
48+
</div>
49+
</div>
50+
</template>

resources/js/Pages/Clients.vue

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup>
22
import { Head } from '@inertiajs/vue3';
3+
import { ref } from 'vue';
34
import DataTable from '@/Components/DataTable.vue';
5+
import DateRangeSearch from '@/Components/DateRangeSearch.vue';
46
57
const props = defineProps({
68
clients: {
@@ -19,6 +21,47 @@ const columns = [
1921
{ key: 'postal_code', label: 'Código Postal' },
2022
{ key: 'registered_at', label: 'Fecha de Registro' }
2123
];
24+
25+
const dateRange = ref({
26+
startDate: '',
27+
endDate: ''
28+
});
29+
30+
const searchQuery = ref('');
31+
32+
function filterClients() {
33+
let filteredClients = props.clients;
34+
35+
// Filter by date range
36+
if (dateRange.value.startDate || dateRange.value.endDate) {
37+
filteredClients = filteredClients.filter(client => {
38+
const registeredDate = new Date(client.registered_at);
39+
const startDate = dateRange.value.startDate ? new Date(dateRange.value.startDate) : null;
40+
const endDate = dateRange.value.endDate ? new Date(dateRange.value.endDate) : null;
41+
42+
if (startDate && endDate) {
43+
return registeredDate >= startDate && registeredDate <= endDate;
44+
} else if (startDate) {
45+
return registeredDate >= startDate;
46+
} else if (endDate) {
47+
return registeredDate <= endDate;
48+
}
49+
50+
return true;
51+
});
52+
}
53+
54+
// Filter by search query (DNI or name)
55+
if (searchQuery.value) {
56+
const query = searchQuery.value.toLowerCase();
57+
filteredClients = filteredClients.filter(client =>
58+
client.DNI.toLowerCase().includes(query) ||
59+
client.name.toLowerCase().includes(query)
60+
);
61+
}
62+
63+
return filteredClients;
64+
}
2265
</script>
2366

2467
<template>
@@ -29,8 +72,25 @@ const columns = [
2972
Clientes
3073
</h1>
3174

75+
<div class="mb-6 space-y-4">
76+
<div class="flex items-center space-x-4">
77+
<input
78+
type="text"
79+
v-model="searchQuery"
80+
placeholder="Buscar por DNI o nombre..."
81+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
82+
/>
83+
</div>
84+
85+
<DateRangeSearch
86+
start-label="Fecha de registro desde"
87+
end-label="Fecha de registro hasta"
88+
@update:dateRange="newRange => dateRange = newRange"
89+
/>
90+
</div>
91+
3292
<DataTable
33-
:data="clients"
93+
:data="filterClients()"
3494
:columns="columns"
3595
:items-per-page="10"
3696
/>

resources/js/Pages/DeliveryNotes/Show.vue

+94-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup>
22
import { Head } from '@inertiajs/vue3';
3+
import { ref } from 'vue';
34
import DataTable from '@/Components/DataTable.vue';
5+
import DateRangeSearch from '@/Components/DateRangeSearch.vue';
46
57
const props = defineProps({
68
delivery_notes: {
@@ -20,6 +22,58 @@ const columns = [
2022
{ key: 'profit', label: 'Beneficio' },
2123
{ key: 'added_at', label: 'Fecha de Alta' }
2224
];
25+
26+
const searchQuery = ref('');
27+
const dateRange = ref({
28+
startDate: '',
29+
endDate: ''
30+
});
31+
32+
function calculateTotals() {
33+
const filteredData = filterDeliveryNotes();
34+
return {
35+
totalSold: filteredData.reduce((sum, note) => sum + parseFloat(note.RRP), 0),
36+
totalSpent: filteredData.reduce((sum, note) => sum + parseFloat(note.cost), 0),
37+
totalProfit: filteredData.reduce((sum, note) => sum + parseFloat(note.profit), 0),
38+
averageMargin: filteredData.length > 0
39+
? (filteredData.reduce((sum, note) => sum + parseFloat(note.margin), 0) / filteredData.length)
40+
: 0
41+
};
42+
}
43+
44+
function filterDeliveryNotes() {
45+
let filteredNotes = props.delivery_notes;
46+
47+
// Filter by date range
48+
if (dateRange.value.startDate || dateRange.value.endDate) {
49+
filteredNotes = filteredNotes.filter(note => {
50+
const addedDate = new Date(note.added_at);
51+
const startDate = dateRange.value.startDate ? new Date(dateRange.value.startDate) : null;
52+
const endDate = dateRange.value.endDate ? new Date(dateRange.value.endDate) : null;
53+
54+
if (startDate && endDate) {
55+
return addedDate >= startDate && addedDate <= endDate;
56+
} else if (startDate) {
57+
return addedDate >= startDate;
58+
} else if (endDate) {
59+
return addedDate <= endDate;
60+
}
61+
62+
return true;
63+
});
64+
}
65+
66+
// Filter by search query (family or supplier)
67+
if (searchQuery.value) {
68+
const query = searchQuery.value.toLowerCase();
69+
filteredNotes = filteredNotes.filter(note =>
70+
note.family.toLowerCase().includes(query) ||
71+
note.supplier.toLowerCase().includes(query)
72+
);
73+
}
74+
75+
return filteredNotes;
76+
}
2377
</script>
2478

2579
<template>
@@ -30,8 +84,47 @@ const columns = [
3084
Albaranes
3185
</h1>
3286

87+
<div class="mb-6 space-y-4">
88+
<div class="flex items-center space-x-4">
89+
<input
90+
type="text"
91+
v-model="searchQuery"
92+
placeholder="Buscar por familia o proveedor..."
93+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
94+
/>
95+
</div>
96+
97+
<DateRangeSearch
98+
start-label="Fecha de alta desde"
99+
end-label="Fecha de alta hasta"
100+
@update:dateRange="newRange => dateRange = newRange"
101+
/>
102+
</div>
103+
104+
<div class="bg-white rounded-lg shadow p-6 mb-6">
105+
<h2 class="text-lg font-semibold mb-4">Resumen de datos filtrados</h2>
106+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
107+
<div class="p-4 bg-gray-50 rounded-lg">
108+
<p class="text-sm text-gray-600">Total Vendido (PVP)</p>
109+
<p class="text-xl font-bold">{{ calculateTotals().totalSold.toFixed(2) }}€</p>
110+
</div>
111+
<div class="p-4 bg-gray-50 rounded-lg">
112+
<p class="text-sm text-gray-600">Total Gastado</p>
113+
<p class="text-xl font-bold">{{ calculateTotals().totalSpent.toFixed(2) }}€</p>
114+
</div>
115+
<div class="p-4 bg-gray-50 rounded-lg">
116+
<p class="text-sm text-gray-600">Beneficio Total</p>
117+
<p class="text-xl font-bold">{{ calculateTotals().totalProfit.toFixed(2) }}€</p>
118+
</div>
119+
<div class="p-4 bg-gray-50 rounded-lg">
120+
<p class="text-sm text-gray-600">Margen Promedio</p>
121+
<p class="text-xl font-bold">{{ calculateTotals().averageMargin.toFixed(2) }}%</p>
122+
</div>
123+
</div>
124+
</div>
125+
33126
<DataTable
34-
:data="delivery_notes"
127+
:data="filterDeliveryNotes()"
35128
:columns="columns"
36129
:items-per-page="10"
37130
/>

0 commit comments

Comments
 (0)