npm-pkg-hook 1.10.6 → 1.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/hooks/index.js +2 -0
- package/src/hooks/useConnection/index.js +10 -8
- package/src/hooks/useLocalBackendIp/index.js +25 -0
- package/src/hooks/useMouse/index.ts +8 -9
- package/src/hooks/usePWAInstall/index.js +20 -20
- package/src/hooks/useProductsFood/index.js +43 -27
- package/src/hooks/useSales/index.js +27 -13
- package/src/hooks/useSales/useTotalSales.js +11 -12
- package/src/hooks/useStockMovements/helpers/index.js +2 -2
- package/src/hooks/useStockMovements/index.js +6 -4
- package/src/hooks/useWeeklyStockMovement/helpers/index.js +32 -0
- package/src/hooks/useWeeklyStockMovement/index.js +51 -0
- package/src/utils/UtilDateRange.js +31 -0
- package/src/utils/index.js +2 -0
package/package.json
CHANGED
package/src/hooks/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export * from './useStoreTable/index'
|
|
|
14
14
|
export * from './useSubscriptionValidation'
|
|
15
15
|
export * from './convertToMilitaryTime'
|
|
16
16
|
export * from './useRoles'
|
|
17
|
+
export * from './useLocalBackendIp'
|
|
17
18
|
export * from './statusOpenStores'
|
|
18
19
|
export * from './completeSchedules'
|
|
19
20
|
export * from './useLogout/helpers/BroadcastChannel'
|
|
@@ -129,3 +130,4 @@ export * from './useTotalProductsSold'
|
|
|
129
130
|
export * from './useTotalProductsInStock/index'
|
|
130
131
|
export * from './useTotalAllSales/index'
|
|
131
132
|
export * from './useTotalProductsSolded'
|
|
133
|
+
export * from './useWeeklyStockMovement'
|
|
@@ -10,13 +10,15 @@ export const useConnection = ({ setConnectionStatus }) => {
|
|
|
10
10
|
// Attaching event handler for the load event
|
|
11
11
|
// window.addEventListener('load', updateConnectionStatus);
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
if (typeof window !== 'undefined') {
|
|
14
|
+
// Attaching event handler for the online event
|
|
15
|
+
window.addEventListener('online', function () {
|
|
16
|
+
updateConnectionStatus()
|
|
17
|
+
})
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
// Attaching event handler for the offline event
|
|
20
|
+
window.addEventListener('offline', function () {
|
|
21
|
+
updateConnectionStatus()
|
|
22
|
+
})
|
|
23
|
+
}
|
|
22
24
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useQuery, gql } from '@apollo/client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GraphQL query to get the local IP of the backend server.
|
|
5
|
+
*/
|
|
6
|
+
const GET_LOCAL_BACKEND_IP = gql`
|
|
7
|
+
query GetLocalBackendIp {
|
|
8
|
+
getLocalBackendIp
|
|
9
|
+
}
|
|
10
|
+
`
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Custom hook to fetch the local IP address of the backend server.
|
|
14
|
+
*
|
|
15
|
+
* @returns {Object} - Contains the IP string, loading state, and error.
|
|
16
|
+
*/
|
|
17
|
+
export const useLocalBackendIp = () => {
|
|
18
|
+
const { data, loading, error } = useQuery(GET_LOCAL_BACKEND_IP)
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
ip: data?.getLocalBackendIp || null,
|
|
22
|
+
loading,
|
|
23
|
+
error
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import type { MouseEvent } from "react";
|
|
3
2
|
|
|
4
|
-
export function useMouse
|
|
5
|
-
options: { resetOnExit
|
|
3
|
+
export function useMouse(
|
|
4
|
+
options: { resetOnExit } = { resetOnExit: false }
|
|
6
5
|
) {
|
|
7
6
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
8
7
|
|
|
9
|
-
const ref = useRef
|
|
8
|
+
const ref = useRef();
|
|
10
9
|
|
|
11
|
-
const setMousePosition = (event
|
|
10
|
+
const setMousePosition = (event) => {
|
|
12
11
|
if (ref.current) {
|
|
13
12
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
14
13
|
|
|
@@ -36,14 +35,14 @@ export function useMouse<T extends HTMLElement = any>(
|
|
|
36
35
|
|
|
37
36
|
useEffect(() => {
|
|
38
37
|
const element = ref?.current ? ref.current : document;
|
|
39
|
-
element.addEventListener("mousemove", setMousePosition
|
|
38
|
+
element.addEventListener("mousemove", setMousePosition);
|
|
40
39
|
if (options.resetOnExit)
|
|
41
|
-
element.addEventListener("mouseleave", resetMousePosition
|
|
40
|
+
element.addEventListener("mouseleave", resetMousePosition);
|
|
42
41
|
|
|
43
42
|
return () => {
|
|
44
|
-
element.removeEventListener("mousemove", setMousePosition
|
|
43
|
+
element.removeEventListener("mousemove", setMousePosition);
|
|
45
44
|
if (options.resetOnExit)
|
|
46
|
-
element.removeEventListener("mouseleave", resetMousePosition
|
|
45
|
+
element.removeEventListener("mouseleave", resetMousePosition);
|
|
47
46
|
};
|
|
48
47
|
}, [ref.current]);
|
|
49
48
|
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
2
|
|
|
3
3
|
export const usePWAInstall = () => {
|
|
4
|
-
const [deferredPrompt, setDeferredPrompt] = useState(null)
|
|
5
|
-
const [isInstallable, setIsInstallable] = useState(false)
|
|
4
|
+
const [deferredPrompt, setDeferredPrompt] = useState(null)
|
|
5
|
+
const [isInstallable, setIsInstallable] = useState(false)
|
|
6
6
|
|
|
7
7
|
useEffect(() => {
|
|
8
8
|
const handleBeforeInstallPrompt = (e) => {
|
|
9
|
-
e.preventDefault()
|
|
10
|
-
setDeferredPrompt(e)
|
|
11
|
-
setIsInstallable(true)
|
|
12
|
-
}
|
|
9
|
+
e.preventDefault() // Evita que el navegador muestre el diálogo automáticamente
|
|
10
|
+
setDeferredPrompt(e) // Almacena el evento para que puedas llamarlo más tarde
|
|
11
|
+
setIsInstallable(true) // Marca que la PWA es instalable
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
|
14
|
+
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
|
15
15
|
|
|
16
16
|
return () => {
|
|
17
|
-
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
|
18
|
-
}
|
|
19
|
-
}, [])
|
|
17
|
+
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
|
18
|
+
}
|
|
19
|
+
}, [])
|
|
20
20
|
|
|
21
21
|
const installPWA = () => {
|
|
22
22
|
if (deferredPrompt) {
|
|
23
|
-
deferredPrompt.prompt()
|
|
23
|
+
deferredPrompt.prompt()
|
|
24
24
|
|
|
25
25
|
deferredPrompt.userChoice.then((choiceResult) => {
|
|
26
26
|
if (choiceResult.outcome === 'accepted') {
|
|
27
|
-
console.log('User accepted the install prompt')
|
|
27
|
+
console.log('User accepted the install prompt')
|
|
28
28
|
} else {
|
|
29
|
-
console.log('User dismissed the install prompt')
|
|
29
|
+
console.log('User dismissed the install prompt')
|
|
30
30
|
}
|
|
31
|
-
setDeferredPrompt(null)
|
|
32
|
-
setIsInstallable(false)
|
|
33
|
-
})
|
|
31
|
+
setDeferredPrompt(null) // Limpia el evento después de usarlo
|
|
32
|
+
setIsInstallable(false) // Oculta el botón de instalación
|
|
33
|
+
})
|
|
34
34
|
}
|
|
35
|
-
}
|
|
35
|
+
}
|
|
36
36
|
|
|
37
|
-
return { isInstallable, installPWA }
|
|
38
|
-
}
|
|
37
|
+
return { isInstallable, installPWA }
|
|
38
|
+
}
|
|
@@ -14,47 +14,63 @@ import {
|
|
|
14
14
|
export * from './useEditProduct'
|
|
15
15
|
|
|
16
16
|
export const useProductsFood = ({
|
|
17
|
-
categories,
|
|
18
|
-
desc,
|
|
17
|
+
categories = [],
|
|
18
|
+
desc = [],
|
|
19
19
|
fetchPolicy = 'cache-and-network',
|
|
20
|
-
fromDate,
|
|
21
|
-
gender,
|
|
20
|
+
fromDate = null,
|
|
21
|
+
gender = [],
|
|
22
22
|
max = 100,
|
|
23
|
-
min,
|
|
23
|
+
min = null,
|
|
24
24
|
pState,
|
|
25
25
|
search = null,
|
|
26
|
-
toDate
|
|
26
|
+
toDate = null,
|
|
27
|
+
dataSale = [],
|
|
28
|
+
isShopppingCard = false
|
|
27
29
|
}) => {
|
|
28
30
|
const [showMore, setShowMore] = useState(500)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
loading,
|
|
32
|
-
fetchMore,
|
|
33
|
-
refetch,
|
|
34
|
-
error,
|
|
35
|
-
called
|
|
36
|
-
} = useQuery(GET_ALL_PRODUCT_STORE, {
|
|
31
|
+
|
|
32
|
+
const { data, loading, fetchMore, refetch, error, called } = useQuery(GET_ALL_PRODUCT_STORE, {
|
|
37
33
|
fetchPolicy,
|
|
38
34
|
notifyOnNetworkStatusChange: true,
|
|
39
35
|
nextFetchPolicy: 'cache-first',
|
|
40
36
|
refetchWritePolicy: 'merge',
|
|
41
|
-
variables:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
min: min || null,
|
|
37
|
+
variables: {
|
|
38
|
+
categories,
|
|
39
|
+
desc,
|
|
40
|
+
fromDate,
|
|
41
|
+
gender,
|
|
42
|
+
max,
|
|
43
|
+
min,
|
|
49
44
|
pState,
|
|
50
|
-
search
|
|
51
|
-
toDate
|
|
45
|
+
search,
|
|
46
|
+
toDate
|
|
52
47
|
}
|
|
53
48
|
})
|
|
54
|
-
|
|
49
|
+
|
|
50
|
+
const productsFood = data?.productFoodsAll?.data ?? []
|
|
51
|
+
|
|
52
|
+
if (!isShopppingCard) {
|
|
53
|
+
return [
|
|
54
|
+
productsFood, {
|
|
55
|
+
pagination: data?.productFoodsAll?.pagination || {},
|
|
56
|
+
loading: called ? false : loading,
|
|
57
|
+
error,
|
|
58
|
+
showMore,
|
|
59
|
+
fetchMore,
|
|
60
|
+
refetch,
|
|
61
|
+
setShowMore
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const updatedProductsFood = productsFood.map(product => ({
|
|
67
|
+
...product,
|
|
68
|
+
existsInSale: dataSale.some(item => item.pId === product.pId)
|
|
69
|
+
}));
|
|
70
|
+
|
|
55
71
|
return [
|
|
56
|
-
|
|
57
|
-
pagination: data?.productFoodsAll
|
|
72
|
+
updatedProductsFood, {
|
|
73
|
+
pagination: data?.productFoodsAll?.pagination || {},
|
|
58
74
|
loading: called ? false : loading,
|
|
59
75
|
error,
|
|
60
76
|
showMore,
|
|
@@ -75,6 +75,7 @@ export const useSales = ({
|
|
|
75
75
|
const [search, setSearch] = useState('')
|
|
76
76
|
const [datCat] = useCatWithProduct({})
|
|
77
77
|
const [categories, setCategories] = useState([])
|
|
78
|
+
const [currentPage, setCurrentPage] = useState(1)
|
|
78
79
|
useEffect(() => {
|
|
79
80
|
setCategories(datCat)
|
|
80
81
|
}, [datCat])
|
|
@@ -102,6 +103,9 @@ export const useSales = ({
|
|
|
102
103
|
const [valuesDates, setValuesDates] = useState(() => {
|
|
103
104
|
return { fromDate: yearMonthDay, toDate: '' }
|
|
104
105
|
})
|
|
106
|
+
const [product, setProduct] = useState({
|
|
107
|
+
PRODUCT: {}
|
|
108
|
+
})
|
|
105
109
|
const [loadingExtraProduct, setLoadingExtraProduct] = useState(false)
|
|
106
110
|
const [dataOptional, setDataOptional] = useState([])
|
|
107
111
|
const [dataExtra, setDataExtra] = useState([])
|
|
@@ -134,12 +138,9 @@ export const useSales = ({
|
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const [productFoodsOne, { data: dataProduct }] = useLazyQuery(
|
|
141
|
-
GET_ONE_PRODUCTS_FOOD
|
|
142
|
-
)
|
|
141
|
+
|
|
142
|
+
const [productFoodsOne, { data: dataProduct }] = useLazyQuery(GET_ONE_PRODUCTS_FOOD)
|
|
143
|
+
|
|
143
144
|
const [ExtProductFoodsSubOptionalAll, { loading: loadingExtProductFoodsSubOptionalAll }] = useLazyQuery(
|
|
144
145
|
GET_EXTRAS_PRODUCT_FOOD_OPTIONAL,
|
|
145
146
|
{
|
|
@@ -168,9 +169,11 @@ export const useSales = ({
|
|
|
168
169
|
toDate: valuesDates?.toDate,
|
|
169
170
|
fromDate: valuesDates?.fromDate,
|
|
170
171
|
max: showMore,
|
|
171
|
-
min: 0
|
|
172
|
+
min: 0,
|
|
173
|
+
isShopppingCard: true,
|
|
174
|
+
dataSale: (Array.isArray(saveDataState?.PRODUCT) && saveDataState?.PRODUCT) ?? [],
|
|
175
|
+
callback: () => { return null }
|
|
172
176
|
})
|
|
173
|
-
const [currentPage, setCurrentPage] = useState(1)
|
|
174
177
|
|
|
175
178
|
const handlePageChange = (pageNumber) => {
|
|
176
179
|
setCurrentPage(pageNumber)
|
|
@@ -379,7 +382,7 @@ export const useSales = ({
|
|
|
379
382
|
|
|
380
383
|
// Validar si se intenta superar el stock disponible
|
|
381
384
|
const finalQuantity = Math.min(value, productExist.stock)
|
|
382
|
-
if (value > productExist.stock) {
|
|
385
|
+
if ((value > productExist.stock) && productExist.manageStock) {
|
|
383
386
|
sendNotification({
|
|
384
387
|
title: 'Stock insuficiente',
|
|
385
388
|
backgroundColor: 'warning',
|
|
@@ -481,7 +484,8 @@ export const useSales = ({
|
|
|
481
484
|
}
|
|
482
485
|
|
|
483
486
|
// Validar si se supera el stock
|
|
484
|
-
|
|
487
|
+
console.log(OurProduct)
|
|
488
|
+
if (newQuantity >= OurProduct?.stock && OurProduct?.manageStock) {
|
|
485
489
|
sendNotification({
|
|
486
490
|
title: 'Stock insuficiente',
|
|
487
491
|
backgroundColor: 'warning',
|
|
@@ -489,7 +493,6 @@ export const useSales = ({
|
|
|
489
493
|
})
|
|
490
494
|
return items // Retornar el producto sin modificar
|
|
491
495
|
}
|
|
492
|
-
|
|
493
496
|
return {
|
|
494
497
|
...items,
|
|
495
498
|
ProQuantity: newQuantity,
|
|
@@ -788,7 +791,8 @@ export const useSales = ({
|
|
|
788
791
|
const OurProduct = productsFood?.find((item) => item.pId === pId)
|
|
789
792
|
const isFree = productExist?.free
|
|
790
793
|
const currentQuantity = productExist?.ProQuantity || 0
|
|
791
|
-
|
|
794
|
+
|
|
795
|
+
if (OurProduct?.manageStock && isStockInsufficient(currentQuantity, OurProduct?.stock)) {
|
|
792
796
|
sendAlertStock(stock)
|
|
793
797
|
return state
|
|
794
798
|
}
|
|
@@ -1286,6 +1290,16 @@ export const useSales = ({
|
|
|
1286
1290
|
// Filtrar los productos de productsFood por los carProIds obtenidos
|
|
1287
1291
|
const filteredProducts = filterProductsByCarProId(productsFood, carProIds)
|
|
1288
1292
|
|
|
1293
|
+
const allProducts = useMemo(() => {
|
|
1294
|
+
const productMap = new Map(data.PRODUCT.map(item => [String(item.pId), item.ProQuantity || 0]))
|
|
1295
|
+
|
|
1296
|
+
return filteredProducts.map(product => ({
|
|
1297
|
+
...product,
|
|
1298
|
+
existsInSale: productMap.has(String(product.pId)),
|
|
1299
|
+
ProQuantity: productMap.get(String(product.pId)) || 0
|
|
1300
|
+
}))
|
|
1301
|
+
}, [data.PRODUCT, filteredProducts])
|
|
1302
|
+
|
|
1289
1303
|
return {
|
|
1290
1304
|
loading: loading || loadingSale,
|
|
1291
1305
|
loadingExtraProduct,
|
|
@@ -1310,7 +1324,7 @@ export const useSales = ({
|
|
|
1310
1324
|
search,
|
|
1311
1325
|
values,
|
|
1312
1326
|
initialStateSales,
|
|
1313
|
-
productsFood:
|
|
1327
|
+
productsFood: allProducts,
|
|
1314
1328
|
modalItem,
|
|
1315
1329
|
sumExtraProducts,
|
|
1316
1330
|
oneProductToComment: oneProductToComment ?? null,
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { useApolloClient, useQuery } from '@apollo/client'
|
|
2
|
-
import { useState } from 'react'
|
|
3
2
|
import { GET_ALL_COUNT_SALES } from './queries'
|
|
4
3
|
|
|
5
4
|
export const useTotalSales = () => {
|
|
6
|
-
const [count, setCount] = useState(0)
|
|
7
5
|
const client = useApolloClient()
|
|
8
6
|
|
|
9
|
-
const { loading, error } = useQuery(GET_ALL_COUNT_SALES, {
|
|
10
|
-
onCompleted: (data) => {
|
|
11
|
-
if (data) {
|
|
12
|
-
client.writeQuery({ query: GET_ALL_COUNT_SALES, data }) // Almacena la respuesta en la cache
|
|
13
|
-
}
|
|
14
|
-
if (data?.getTodaySales) {
|
|
15
|
-
setCount(data?.getTodaySales || 0)
|
|
16
|
-
}
|
|
17
|
-
},
|
|
7
|
+
const { data, loading, error } = useQuery(GET_ALL_COUNT_SALES, {
|
|
18
8
|
fetchPolicy: 'cache-and-network',
|
|
19
9
|
notifyOnNetworkStatusChange: true,
|
|
20
10
|
nextFetchPolicy: 'cache-first',
|
|
21
11
|
refetchWritePolicy: 'merge'
|
|
22
12
|
})
|
|
23
|
-
|
|
13
|
+
|
|
14
|
+
// Guarda la respuesta en la caché manualmente (opcional)
|
|
15
|
+
if (data) {
|
|
16
|
+
client.writeQuery({ query: GET_ALL_COUNT_SALES, data })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return [data?.getTodaySales || 0, {
|
|
20
|
+
loading,
|
|
21
|
+
error
|
|
22
|
+
}]
|
|
24
23
|
}
|
|
@@ -6,10 +6,10 @@ export const fillMissingDates = (data, days = 7) => {
|
|
|
6
6
|
const date = new Date()
|
|
7
7
|
date.setDate(today.getDate() - i)
|
|
8
8
|
|
|
9
|
-
const formattedDate = date.
|
|
9
|
+
const formattedDate = date.toISOString().split('T')[0] // "YYYY-MM-DD"
|
|
10
10
|
const existingData = data.find(item => item.date === formattedDate)
|
|
11
11
|
|
|
12
|
-
filledData.push(existingData || { date: formattedDate, TotalIn: 0, TotalOut: 0 })
|
|
12
|
+
filledData.push(existingData || { date: formattedDate, TotalIn: 0, TotalOut: 0, TotalAdjustment: 0 })
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
return filledData
|
|
@@ -6,7 +6,8 @@ const GET_STOCK_MOVEMENTS = gql`
|
|
|
6
6
|
query {
|
|
7
7
|
getStockMovementsByDay {
|
|
8
8
|
date
|
|
9
|
-
total_in
|
|
9
|
+
total_in,
|
|
10
|
+
total_adjustment
|
|
10
11
|
total_out
|
|
11
12
|
}
|
|
12
13
|
}
|
|
@@ -24,9 +25,10 @@ export const useStockMovements = () => {
|
|
|
24
25
|
if (data && data.getStockMovementsByDay) {
|
|
25
26
|
// Transform data to be compatible with Recharts
|
|
26
27
|
const formattedData = data.getStockMovementsByDay.map(entry => ({
|
|
27
|
-
date:
|
|
28
|
-
TotalIn: entry.total_in,
|
|
29
|
-
TotalOut: entry.total_out
|
|
28
|
+
date: entry.date,
|
|
29
|
+
TotalIn: entry.total_in ?? 0,
|
|
30
|
+
TotalOut: entry.total_out ?? 0,
|
|
31
|
+
TotalAdjustment: entry.total_adjustment ?? 0
|
|
30
32
|
}))
|
|
31
33
|
setChartData(formattedData)
|
|
32
34
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fills missing weeks for the last 7 weeks, ensuring continuous data.
|
|
3
|
+
* If a week is missing, it fills it with totalOut: 0 and percentageChange: "N/A".
|
|
4
|
+
*
|
|
5
|
+
* @param {Array} data - Array of weekly stock movement objects.
|
|
6
|
+
* @returns {Array} - Filled data ensuring the last 7 weeks are covered.
|
|
7
|
+
*/
|
|
8
|
+
export const fillLast7Weeks = (data) => {
|
|
9
|
+
const today = new Date()
|
|
10
|
+
const last7Weeks = []
|
|
11
|
+
|
|
12
|
+
// Generar las últimas 7 semanas (cada lunes)
|
|
13
|
+
for (let i = 6; i >= 0; i--) {
|
|
14
|
+
const weekDate = new Date(today)
|
|
15
|
+
weekDate.setDate(today.getDate() - today.getDay() - (i * 7) + 1) // Ajustar para que sea lunes
|
|
16
|
+
const weekStart = weekDate.toISOString().split('T')[0] // Formato YYYY-MM-DD
|
|
17
|
+
last7Weeks.push(weekStart)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Mapear los datos existentes para acceso rápido
|
|
21
|
+
const dataMap = new Map(data.map(item => [item.weekStart, item]))
|
|
22
|
+
|
|
23
|
+
// Construir la nueva lista asegurando que todas las semanas estén presentes
|
|
24
|
+
return last7Weeks.map(weekStart => (
|
|
25
|
+
dataMap.get(weekStart) || {
|
|
26
|
+
weekStart,
|
|
27
|
+
totalOut: 0,
|
|
28
|
+
prevTotalOut: null,
|
|
29
|
+
percentageChange: 'N/A'
|
|
30
|
+
}
|
|
31
|
+
))
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { gql, useQuery } from '@apollo/client'
|
|
2
|
+
import { fillLast7Weeks } from './helpers' // Asegúrate de importar el helper
|
|
3
|
+
|
|
4
|
+
const GET_WEEKLY_STOCK_MOVEMENT = gql`
|
|
5
|
+
query GetStockMovementPercentageChange {
|
|
6
|
+
getStockMovementWeeklyComparison {
|
|
7
|
+
weekStart
|
|
8
|
+
totalOut
|
|
9
|
+
prevTotalOut
|
|
10
|
+
percentageChange
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Custom hook to fetch and format weekly stock movement data.
|
|
17
|
+
* @returns {Object} - { data, loading, error, formattedData }
|
|
18
|
+
*/
|
|
19
|
+
export const useWeeklyStockMovement = () => {
|
|
20
|
+
const { data, loading, error } = useQuery(GET_WEEKLY_STOCK_MOVEMENT)
|
|
21
|
+
|
|
22
|
+
// Transform data and fill missing weeks
|
|
23
|
+
const rawData = data?.getStockMovementWeeklyComparison || []
|
|
24
|
+
const formattedData = fillLast7Weeks(
|
|
25
|
+
rawData.map(item => ({
|
|
26
|
+
...item,
|
|
27
|
+
percentageChange: formatPercentageChange(item.percentageChange)
|
|
28
|
+
}))
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return [formattedData, { loading, error }]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ensures percentageChange is a valid string percentage, clamping between -100% and 100%.
|
|
36
|
+
*
|
|
37
|
+
* @param {string | null} percentageChange - Raw percentage change value.
|
|
38
|
+
* @returns {string} - Formatted and clamped percentage change.
|
|
39
|
+
*/
|
|
40
|
+
const formatPercentageChange = (percentageChange) => {
|
|
41
|
+
if (percentageChange === null) return 'N/A'
|
|
42
|
+
|
|
43
|
+
// Convertir a número
|
|
44
|
+
let parsedValue = parseFloat(percentageChange)
|
|
45
|
+
|
|
46
|
+
// Clampear entre -100% y 100%
|
|
47
|
+
if (isNaN(parsedValue)) return 'N/A'
|
|
48
|
+
parsedValue = Math.max(-100, Math.min(100, parsedValue))
|
|
49
|
+
|
|
50
|
+
return `${parsedValue.toFixed(1)}`
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class UtilDateRange {
|
|
2
|
+
constructor (date = new Date()) {
|
|
3
|
+
this.date = new Date(date) // Asegura que sea un objeto Date
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
toLocalTime (date) {
|
|
7
|
+
const offset = -5 // UTC-5 (ajustar si es necesario)
|
|
8
|
+
const localDate = new Date(date)
|
|
9
|
+
localDate.setHours(localDate.getHours() + offset)
|
|
10
|
+
return localDate
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getStartOfDay () {
|
|
14
|
+
const localDate = this.toLocalTime(this.date)
|
|
15
|
+
localDate.setHours(0, 0, 0, 0)
|
|
16
|
+
return localDate
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getEndOfDay () {
|
|
20
|
+
const localDate = this.toLocalTime(this.date)
|
|
21
|
+
localDate.setHours(23, 59, 59, 999)
|
|
22
|
+
return localDate
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getRange () {
|
|
26
|
+
return {
|
|
27
|
+
start: this.getStartOfDay(),
|
|
28
|
+
end: this.getEndOfDay()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|