diff --git a/.gitignore b/.gitignore index 33331676..a92a53c0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ dist-ssr __pycache__ # Editor directories and files + .vscode/* !.vscode/extensions.json .idea @@ -66,3 +67,6 @@ backup* stats.html start.sh + +# Ignore the compiled static files +INSIGHTSAPI/static/ \ No newline at end of file diff --git a/INSIGHTSAPI/INSIGHTSAPI/settings.py b/INSIGHTSAPI/INSIGHTSAPI/settings.py index 29b5e0ae..8c22abe3 100644 --- a/INSIGHTSAPI/INSIGHTSAPI/settings.py +++ b/INSIGHTSAPI/INSIGHTSAPI/settings.py @@ -380,7 +380,7 @@ def str_to_bool(value: str) -> bool: AUTH_USER_MODEL = "users.User" # LDAP configuration -AUTH_LDAP_SERVER_URI = "ldap://CYC-SERVICES.COM.CO:389" +AUTH_LDAP_SERVER_URI = "ldap://DOMINIO-CYC.CYC-SERVICES.COM.CO:389" AUTH_LDAP_BIND_DN = "CN=StaffNet,OU=TECNOLOGÍA,OU=BOGOTA,DC=CYC-SERVICES,DC=COM,DC=CO" AUTH_LDAP_BIND_PASSWORD = os.environ["AdminLDAPPassword"] diff --git a/frontend/src/__test__/AddImagesCarouselDialog.test.jsx b/frontend/src/__test__/AddImagesCarouselDialog.test.jsx deleted file mode 100644 index 0b67091f..00000000 --- a/frontend/src/__test__/AddImagesCarouselDialog.test.jsx +++ /dev/null @@ -1,118 +0,0 @@ -import { render, screen, fireEvent, within } from '@testing-library/react'; -import AddImagesCarouselDialog from '@components/shared/AddImagesCarouselDialog'; -import { getApiUrl } from '@assets/getApi'; -import { handleError } from '@assets/handleError'; -import { vi } from 'vitest'; - -vi.mock('@assets/getApi'); -vi.mock('@assets/handleError'); -vi.mock('react-filepond', () => { - return { - __esModule: true, - FilePond: vi.fn(() => ( - - )), - registerPlugin: vi.fn(), - }; -}); -vi.spyOn(console, 'warn'); - -describe('AddImagesCarouselDialog Component', () => { - const mockSetOpenAddDialog = vi.fn(); - const mockGetCarouselImages = vi.fn(); - const mockSetImages = vi.fn(); - - const defaultProps = { - openAddDialog: true, - setOpenAddDialog: mockSetOpenAddDialog, - currentImages: [{ name: 'Image 1' }, { name: 'Image 2' }], - getCarouselImages: mockGetCarouselImages, - setImages: mockSetImages, - }; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('renders AddImagesCarouselDialog with correct elements', () => { - render(); - - expect(screen.getByText('Actualizar imagenes')).toBeInTheDocument(); - expect( - screen.getByText( - 'Añade la imagen que deseas mostrar en el carousel de la página principal.' - ) - ).toBeInTheDocument(); - expect(screen.getByLabelText(/Posición/i)).toBeInTheDocument(); - expect(screen.getByText('Cancelar')).toBeInTheDocument(); - expect(screen.getByText('Actualizar')).toBeInTheDocument(); - }); - - it('handles form submission with valid data using mocked FilePond', async () => { - getApiUrl.mockReturnValue({ apiUrl: 'http://localhost/' }); - handleError.mockResolvedValue(); - - // Render your component - render(); - - // Simulate opening the dropdown (for "position" field) - const positionInput = screen.getByDisplayValue('1'); - const positionBox = screen.getByLabelText(/Posición/i); - - // console.log(positionInput); - - fireEvent.mouseDown(positionBox); // Open the dropdown - - // Find the dropdown list within the document - const listbox = within(screen.getByRole('listbox', { hidden: true })); - - // Select the first option (e.g., value "1") - const firstOption = listbox.getByText('1'); - - fireEvent.click(firstOption); - - // Now, simulate a change event to ensure the value is set - fireEvent.change(positionInput, { target: { value: '1' } }); - - // Now, add the file using the mocked FilePond input - const fileInput = screen.getByTestId('filepond-input'); - const file = new File(['dummy content'], 'example.png', { - type: 'image/png', - }); - - fireEvent.change(fileInput, { target: { files: [file] } }); - - // Submit the form - const submitButton = screen.getByText('Actualizar'); - fireEvent.click(submitButton); - - // Assert form submission behavior - expect(mockSetOpenAddDialog).toHaveBeenCalledWith(false); - }); - - it('shows error when no image is added', () => { - render(); - - const submitButton = screen.getByText('Actualizar'); - fireEvent.click(submitButton); - - expect(screen.getByText('Debes añadir una imagen')).toBeInTheDocument(); - }); - - it('toggles link input visibility', () => { - render(); - - const checkbox = screen.getByLabelText( - /¿La imagen debería redireccionar a un link?/i - ); - fireEvent.click(checkbox); - - expect(screen.getByLabelText(/Link/)).toBeInTheDocument(); - }); -}); diff --git a/frontend/src/__test__/CoexistenceCommittee.test.jsx b/frontend/src/__test__/CoexistenceCommittee.test.jsx deleted file mode 100644 index 994e0c22..00000000 --- a/frontend/src/__test__/CoexistenceCommittee.test.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { describe, it, expect, vi } from 'vitest'; -import CoexistenceCommittee from '@components/pages/CoexistenceCommittee'; -import { SnackbarProvider } from '@components/context/SnackbarContext'; -import { ProgressbarProvider } from '@components/context/ProgressbarContext'; - -// Mock the fetch function and the custom hooks -global.fetch = vi.fn(() => - Promise.resolve({ - status: 201, - json: () => Promise.resolve({}), - }) -); - -const mockShowSnack = vi.fn(); -const mockShowProgressbar = vi.fn(); -const mockHideProgressbar = vi.fn(); - -vi.mock('@components/context/SnackbarContext', () => ({ - useSnackbar: () => ({ showSnack: mockShowSnack }), -})); - -vi.mock('@components/context/ProgressbarContext', () => ({ - useProgressbar: () => ({ - isProgressVisible: false, - showProgressbar: mockShowProgressbar, - hideProgressbar: mockHideProgressbar, - }), -})); - -describe('CoexistenceCommittee component', () => { - it('renders the form correctly', () => { - render( - - - - - - ); - - // Check if the form fields are rendered - expect(screen.getByLabelText('Motivo')).toBeInTheDocument(); - expect( - screen.getByLabelText('Deja tu mensaje aquí') - ).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: /Enviar/i }) - ).toBeInTheDocument(); - }); - - it('displays validation errors when fields are empty', async () => { - render( - - - - - - ); - - const submitButton = screen.getByRole('button', { name: /Enviar/i }); - - // Click submit without filling the form - fireEvent.click(submitButton); - - // Wait for validation errors to appear - await waitFor(() => { - const errors = screen.getAllByText('Campo requerido'); - expect(errors).toHaveLength(2); - }); - }); - - it('submits the form correctly when fields are filled', async () => { - render( - - - - - - ); - - const [motivoInput] = screen.getAllByRole('textbox'); - // Fill out the form - fireEvent.change(motivoInput, { - target: { value: 'Acoso laboral' }, - }); - fireEvent.change(screen.getByLabelText('Deja tu mensaje aquí'), { - target: { value: 'Test description' }, - }); - - const submitButton = screen.getByRole('button', { name: /Enviar/i }); - - // Submit the form - fireEvent.click(submitButton); - - // Wait for form submission and expect a success message - await waitFor(() => { - expect(mockShowSnack).toHaveBeenCalledWith( - 'success', - 'Mensaje enviado correctamente' - ); - }); - - expect(mockShowProgressbar).toHaveBeenCalled(); - expect(mockHideProgressbar).toHaveBeenCalled(); - }); - - it('handles submission error correctly', async () => { - // Mock fetch to return an error response - global.fetch = vi.fn(() => - Promise.resolve({ - status: 400, - json: () => Promise.resolve({ message: 'Error message' }), - }) - ); - - render( - - - - - - ); - - const [motivoInput] = screen.getAllByRole('textbox'); - // Fill out the form - fireEvent.change(motivoInput, { - target: { value: 'Acoso laboral' }, - }); - - fireEvent.change(screen.getByLabelText('Deja tu mensaje aquí'), { - target: { value: 'Test description' }, - }); - - const submitButton = screen.getByRole('button', { name: /Enviar/i }); - - // Submit the form - fireEvent.click(submitButton); - - // Wait for error handling - await waitFor(() => { - expect(mockShowSnack).toHaveBeenCalledWith( - 'error', - 'Error message' - ); - }); - - expect(mockShowProgressbar).toHaveBeenCalled(); - expect(mockHideProgressbar).toHaveBeenCalled(); - }); -}); diff --git a/frontend/src/__test__/Footer.test.jsx b/frontend/src/__test__/Footer.test.jsx index d8b5ce88..cd9e711a 100644 --- a/frontend/src/__test__/Footer.test.jsx +++ b/frontend/src/__test__/Footer.test.jsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { BrowserRouter } from 'react-router-dom'; +import { BrowserRouter } from 'react-router'; import Footer from '@components/common/Footer'; describe('Footer Component', () => { diff --git a/frontend/src/__test__/SnackbarContext.test.jsx b/frontend/src/__test__/SnackbarContext.test.jsx index 11480ce2..63262aa5 100644 --- a/frontend/src/__test__/SnackbarContext.test.jsx +++ b/frontend/src/__test__/SnackbarContext.test.jsx @@ -3,7 +3,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { SnackbarProvider, useSnackbar, -} from '@components/context/SnackbarContext'; +} from '@contexts/SnackbarContext'; import '@testing-library/jest-dom'; // Test component to use the Snackbar context diff --git a/frontend/src/__test__/SwiperSlider.test.jsx b/frontend/src/__test__/SwiperSlider.test.jsx deleted file mode 100644 index 6991baaa..00000000 --- a/frontend/src/__test__/SwiperSlider.test.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import SwiperSlider from '@components/shared/SwiperSlider'; - -describe('SwiperSlider Component', () => { - test('renders SwiperSlider with all managers', () => { - render(); - const managerNames = [ - 'Rodrigo Lozano', - 'Marcela Osorio', - 'Christian Moncaleano', - 'Adriana Barrera', - 'Katterene Castrillon', - 'Karen Romero', - 'Luis Pachon', - 'Luis Rodriguez', - 'Julio Cesar', - ]; - - managerNames.forEach((name) => { - expect(screen.getByText(name)).toBeInTheDocument(); - }); - }); - - test('renders SwiperSlider with correct number of slides', () => { - render(); - const slides = screen.getAllByRole('listitem'); // Assuming each slide has a role of "listitem" - expect(slides.length).toBe(9); - }); - - test('renders SwiperSlider with images', () => { - render(); - const images = screen.getAllByRole('img'); - expect(images.length).toBe(9); - images.forEach((img) => { - expect(img).toHaveAttribute('src'); - expect(img).toHaveAttribute('alt'); - }); - }); -}); diff --git a/frontend/src/components/pages/About.jsx b/frontend/src/components/pages/About.jsx index a97ffcef..94aa3657 100644 --- a/frontend/src/components/pages/About.jsx +++ b/frontend/src/components/pages/About.jsx @@ -16,6 +16,7 @@ import cesarGarzon from '@images/managers/cesar-garzon.jpg'; import yannethPinzon from '@images/managers/yanneth-pinzon.webp'; import angelaDuran from '@images/managers/angela-duran.jpeg'; import adrianaPaez from '@images/managers/adriana-paez.jpg'; +import manager1 from '@images/managers/52716114.webp'; import diegoGonzales from '@images/managers/diego-gonzales.jpg'; import riskInterControl from '@images/managers/risk-internal-control.jfif'; import melidaSandoval from '@images/managers/melida-sandoval.jpg'; @@ -62,15 +63,22 @@ const managements = [ 'Planificar, coordinar, dirigir y controlar las actividades que impactan el óptimo funcionamiento de la organización, garantizando un cumplimiento de los procesos establecidos con los más altos estándares de calidad.', }, { - name: 'Adriana Páez', + name: 'Adriana Barrera', management: 'Gerente de Operaciones', + image: manager1, + description: + 'Dirigir, organizar y supervisar las operaciones con equipos altamente productivos, promoviendo la excelencia en cada proceso para alcanzar resultados sobresalientes que superen las expectativas de nuestros clientes y garanticen la sostenibilidad del negocio.', + }, + { + name: 'Adriana Páez', + management: 'Gerente de Operaciones de Ventas', image: adrianaPaez, description: 'Liderar, planificar y controlar las operaciones de las campañas de Cobranzas, con equipos productivos y con alta calidad que garanticen los resultados frente a los clientes y la rentabilidad de cada una de ellas.', }, { name: 'Héctor Gabriel Sotelo', - management: 'Gerente de Operaciones de Ventas', + management: 'Gerente de Operaciones de Servicio al Cliente', image: hectorSotelo, description: 'Liderar, planificar y controlar las operaciones de las campañas de Servicios y Ventas, con equipos productivos y con alta calidad que garanticen los resultados frente a los clientes y la rentabilidad de cada una de ellas.', diff --git a/frontend/src/components/pages/Home.jsx b/frontend/src/components/pages/Home.jsx index c0c98364..c75fac1d 100644 --- a/frontend/src/components/pages/Home.jsx +++ b/frontend/src/components/pages/Home.jsx @@ -14,9 +14,10 @@ import { Typography, Box, Container, Card } from '@mui/material'; // Media import realBenefit2 from '@images/benefits/benefit-1.webp'; -import video from '@videos/futbol.mp4'; import cake from '@images/birthdays/cake.webp'; import AvatarImage from '@images/home-carousel/avatar.jpg'; +const fultbolVideo = `${getApiUrl().apiUrl}static/videos/futbol.mp4`; +const pointsVideo = `${getApiUrl().apiUrl}static/videos/points.mp4`; // Libraries import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -182,12 +183,7 @@ const Home = () => { }} controls > - + @@ -273,13 +269,15 @@ const Home = () => { ) : ( - + + + )} @@ -312,13 +310,15 @@ const Home = () => { ) : ( - + + + )} {' '} @@ -351,13 +351,15 @@ const Home = () => { ) : ( - + + + )} {' '} @@ -413,7 +415,7 @@ const Home = () => { }} controls > - + diff --git a/frontend/src/components/pages/Sgc.jsx b/frontend/src/components/pages/Sgc.jsx index 91cc071d..c28c0b6b 100644 --- a/frontend/src/components/pages/Sgc.jsx +++ b/frontend/src/components/pages/Sgc.jsx @@ -237,7 +237,6 @@ export const Sgc = () => { formData.append('sub_type', newRow.sub_type); formData.append('name', newRow.name); formData.append('version', newRow.version); - console.log(selectedFileUpdate); if (selectedFileUpdate) { formData.append('file', selectedFileUpdate); } diff --git a/frontend/src/components/pages/vacations/Vacations.jsx b/frontend/src/components/pages/vacations/Vacations.jsx index 3d87521e..919bce62 100644 --- a/frontend/src/components/pages/vacations/Vacations.jsx +++ b/frontend/src/components/pages/vacations/Vacations.jsx @@ -606,7 +606,7 @@ export const Vacations = () => { }; const handleCloseDialogPayslip = () => { - setOpenDialogPayslip(false); + setOpenDialogPayslip(false); setOpenObservationsInput(false); setButtonType('button'); }; diff --git a/frontend/src/components/shared/SwiperSlider.jsx b/frontend/src/components/shared/SwiperSlider.jsx index d8c22273..4ac6dd2a 100644 --- a/frontend/src/components/shared/SwiperSlider.jsx +++ b/frontend/src/components/shared/SwiperSlider.jsx @@ -1,17 +1,16 @@ -// Material-UI +import React, { useEffect, useState, useCallback } from 'react'; +import useEmblaCarousel from 'embla-carousel-react'; +import Autoplay from 'embla-carousel-autoplay'; import { Box, Typography } from '@mui/material'; - -// Import Swiper React components -import { Swiper, SwiperSlide } from 'swiper/react'; -import { Pagination, Autoplay } from 'swiper/modules'; -import 'swiper/css'; -import 'swiper/css/pagination'; +import { + DotButton, + useDotButton, +} from '@components/shared/embla-carousel/EmblaCarouselDotButton'; import '@src/index.css'; // Media -import managersJr1 from '@images/managers-jr/52716114.webp'; import managersJr2 from '@images/managers-jr/53069726.webp'; -import managersJr4 from '@images/managers-jr/1016002011.webp'; +import managersJr4 from '@images/managers-jr/1010198435.jpg'; import managersJr5 from '@images/managers-jr/1016033764.webp'; import managersJr6 from '@images/managers-jr/91498957.webp'; import managersJr8 from '@images/managers-jr/28553156.webp'; @@ -30,12 +29,6 @@ const managersJr = [ image: managersJr9, description: '', }, - { - name: 'Adriana Barrera', - management: 'GERENTE DE CUENTAS', - image: managersJr1, - description: '', - }, { name: 'Katterene Castrillon', management: 'GERENTE DE CUENTAS', @@ -43,8 +36,8 @@ const managersJr = [ description: '', }, { - name: 'Luis Pachon', - management: 'GERENTE DE CUENTAS', + name: 'Luis Peña', + management: 'GERENTE JR. INFRAESTRUCTURA Y REDES', image: managersJr4, description: '', }, @@ -63,59 +56,84 @@ const managersJr = [ ]; const SwiperSlider = () => { + const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, [ + Autoplay({ delay: 4000, stopOnInteraction: false }), + ]); + + const { selectedIndex, scrollSnaps, onDotButtonClick } = + useDotButton(emblaApi); + return ( - - {managersJr.map((manager, index) => ( - - - {manager.name} +
+
+ {managersJr.map((manager, index) => ( +
- - {manager.name} - - {manager.management} - - - - - ))} - + > + + {manager.name} + + + {manager.name} + + + {manager.management} + + + +
+ ))} +
+
+
+ {scrollSnaps.map((_, index) => ( + onDotButtonClick(index)} + className={'embla__dot'.concat( + index === selectedIndex + ? ' embla__dot--selected' + : '' + )} + /> + ))} +
+ ); }; diff --git a/frontend/src/components/shared/embla-carousel/EmblaCarousel.jsx b/frontend/src/components/shared/embla-carousel/EmblaCarousel.jsx index 2bab0cf6..5a17ab3b 100644 --- a/frontend/src/components/shared/embla-carousel/EmblaCarousel.jsx +++ b/frontend/src/components/shared/embla-carousel/EmblaCarousel.jsx @@ -1,7 +1,7 @@ // Libraries import useEmblaCarousel from 'embla-carousel-react'; import Autoplay from 'embla-carousel-autoplay'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { DotButton, useDotButton, @@ -14,93 +14,65 @@ import { useSnackbar } from '@contexts/SnackbarContext'; import { getApiUrl } from '@assets/getApi'; import { handleError } from '@assets/handleError'; import AddImagesCarouselDialog from '@components/shared/embla-carousel/AddImagesCarouselDialog'; - -// Icons -import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; -import AddIcon from '@mui/icons-material/Add'; - -// Material-UI -import { IconButton, Box } from '@mui/material'; - -const getCarouselImages = async (setImages, showSnack) => { - try { - const response = await fetch( - `${getApiUrl().apiUrl}carousel-images/banners/`, - { - method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - } - ); - - await handleError(response, showSnack); - - if (response.status === 200) { - const data = await response.json(); - setImages(data); - } - } catch (error) { - if (getApiUrl().environment === 'development') { - console.error(error); - } - } -}; - -const deleteCarouselImage = async (id, showSnack, setImages) => { - try { - const response = await fetch( - `${getApiUrl().apiUrl}carousel-images/banners/${id}/`, - { - method: 'DELETE', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - } - ); - - await handleError(response, showSnack); - - if (response.status === 204) { - showSnack('success', 'Imagen eliminada correctamente'); - getCarouselImages(setImages, showSnack); - } - } catch (error) { - if (getApiUrl().environment === 'development') { - console.error(error); - } - } -}; - -const IconButtonsStyle = { - backgroundColor: 'rgba(0, 0, 0, 0.5)', - color: 'white', - '&:hover': { - backgroundColor: 'white', - color: 'gray', - }, - transition: 'all 0.3s', -}; +import { LazyLoadImage } from '@components/shared/embla-carousel/EmblaCarouselLazyLoadImage'; export function EmblaCarousel() { const { showSnack } = useSnackbar(); const [images, setImages] = useState([]); const [openAddDialog, setOpenAddDialog] = useState(false); - const permissions = JSON.parse(localStorage.getItem('permissions')) || []; - - useEffect(() => { - getCarouselImages(setImages); - }, []); - + const [slidesInView, setSlidesInView] = useState([]); const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, [ Autoplay({ delay: 4000, stopOnInteraction: false }), ]); + const getCarouselImages = async () => { + try { + const response = await fetch( + `${getApiUrl().apiUrl}carousel-images/banners/`, + { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + await handleError(response, showSnack); + + if (response.status === 200) { + const data = await response.json(); + setImages(data); + } + } catch (error) { + if (getApiUrl().environment === 'development') { + console.error(error); + } + } + }; + + useEffect(() => { + getCarouselImages(); + }, []); + const { selectedIndex, scrollSnaps, onDotButtonClick } = useDotButton(emblaApi); + const updateSlidesInView = useCallback(() => { + if (!emblaApi) return; + + const inView = emblaApi.slidesInView(); + setSlidesInView((prev) => [...new Set([...prev, ...inView])]); + }, [emblaApi]); + + useEffect(() => { + if (!emblaApi) return; + + updateSlidesInView(emblaApi); + emblaApi.on('slidesInView', updateSlidesInView); + emblaApi.on('reInit', updateSlidesInView); + }, [emblaApi, updateSlidesInView, images]); + return ( <> -
+
- {images.length > 0 && - images.map((image, index) => ( - + {images.map((image, index) => ( + - - {permissions && - permissions.includes( - 'carousel_image.add_banner' - ) ? ( - - setOpenAddDialog(true) - } - sx={IconButtonsStyle} - > - - - ) : null} - {permissions && - permissions.includes( - 'carousel_image.delete_banner' - ) ? ( - - deleteCarouselImage( - image.id, - showSnack, - setImages - ) - } - sx={IconButtonsStyle} - > - - - ) : null} - - {image.title} - + image={image} + getCarouselImages={getCarouselImages} + setImages={setImages} + setOpenAddDialog={setOpenAddDialog} + inView={slidesInView.indexOf(index) > -1} + /> ))} +
{scrollSnaps.map((_, index) => ( diff --git a/frontend/src/components/shared/embla-carousel/EmblaCarouselLazyLoadImage.jsx b/frontend/src/components/shared/embla-carousel/EmblaCarouselLazyLoadImage.jsx new file mode 100644 index 00000000..0af35cc2 --- /dev/null +++ b/frontend/src/components/shared/embla-carousel/EmblaCarouselLazyLoadImage.jsx @@ -0,0 +1,118 @@ +import React, { useState, useCallback } from 'react'; + +// Material-UI +import { Box } from '@mui/material'; + +// Icons +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import AddIcon from '@mui/icons-material/Add'; +import IconButton from '@mui/material/IconButton'; + +// Custom Hooks +import { useSnackbar } from '@contexts/SnackbarContext'; + +// Custom Functions and Components +import { getApiUrl } from '@assets/getApi'; +import { handleError } from '@assets/handleError'; + +const PLACEHOLDER_SRC = `data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D`; + +const IconButtonsStyle = { + backgroundColor: 'rgba(0, 0, 0, 0.5)', + color: 'white', + '&:hover': { + backgroundColor: 'white', + color: 'gray', + }, + transition: 'all 0.3s', +}; + +export const LazyLoadImage = (props) => { + const { image, inView, setOpenAddDialog, setImages, getCarouselImages } = + props; + const [hasLoaded, setHasLoaded] = useState(false); + const permissions = JSON.parse(localStorage.getItem('permissions')) || []; + const { showSnack } = useSnackbar(); + + const setLoaded = useCallback(() => { + if (inView) setHasLoaded(true); + }, [inView, setHasLoaded]); + + const deleteCarouselImage = async (id) => { + try { + const response = await fetch( + `${getApiUrl().apiUrl}carousel-images/banners/${id}/`, + { + method: 'DELETE', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + await handleError(response, showSnack); + + if (response.status === 204) { + showSnack('success', 'Imagen eliminada correctamente'); + getCarouselImages(setImages, showSnack); + } + } catch (error) { + if (getApiUrl().environment === 'development') { + console.error(error); + } + } + }; + + return ( +
+
+ + {permissions && + permissions.includes('carousel_image.add_banner') ? ( + setOpenAddDialog(true)} + sx={IconButtonsStyle} + > + + + ) : null} + {permissions && + permissions.includes('carousel_image.delete_banner') ? ( + deleteCarouselImage(image.id)} + sx={IconButtonsStyle} + > + + + ) : null} + + {!hasLoaded && } + window.open(image.link) : null} + /> +
+
+ ); +}; diff --git a/frontend/src/images/about/organigrama.png b/frontend/src/images/about/organigrama.png index 836e88ba..d677e47b 100644 Binary files a/frontend/src/images/about/organigrama.png and b/frontend/src/images/about/organigrama.png differ diff --git a/frontend/src/images/cyc-logos/logotipo-navbar.png b/frontend/src/images/cyc-logos/logotipo-navbar.png deleted file mode 100644 index 23652400..00000000 Binary files a/frontend/src/images/cyc-logos/logotipo-navbar.png and /dev/null differ diff --git a/frontend/src/images/login/new-login-image.jpg b/frontend/src/images/login/new-login-image.jpg deleted file mode 100644 index 3f9774d0..00000000 Binary files a/frontend/src/images/login/new-login-image.jpg and /dev/null differ diff --git a/frontend/src/images/managers-jr/1010198435.jpg b/frontend/src/images/managers-jr/1010198435.jpg new file mode 100644 index 00000000..5df0a48c Binary files /dev/null and b/frontend/src/images/managers-jr/1010198435.jpg differ diff --git a/frontend/src/images/managers-jr/1010198435.webp b/frontend/src/images/managers-jr/1010198435.webp new file mode 100644 index 00000000..9f8d5419 Binary files /dev/null and b/frontend/src/images/managers-jr/1010198435.webp differ diff --git a/frontend/src/images/managers-jr/79509094.webp b/frontend/src/images/managers-jr/79509094.webp deleted file mode 100644 index 79b6c6b5..00000000 Binary files a/frontend/src/images/managers-jr/79509094.webp and /dev/null differ diff --git a/frontend/src/images/managers-jr/52716114.webp b/frontend/src/images/managers/52716114.webp similarity index 100% rename from frontend/src/images/managers-jr/52716114.webp rename to frontend/src/images/managers/52716114.webp diff --git a/frontend/src/images/quality/files.jpg b/frontend/src/images/quality/files.jpg deleted file mode 100644 index 1185496f..00000000 Binary files a/frontend/src/images/quality/files.jpg and /dev/null differ diff --git a/frontend/src/images/trivia/trivia.svg b/frontend/src/images/trivia/trivia.svg deleted file mode 100644 index 4755b561..00000000 --- a/frontend/src/images/trivia/trivia.svg +++ /dev/null @@ -1,6278 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/images/workstations/piso-2.png b/frontend/src/images/workstations/piso-2.png deleted file mode 100644 index ce8698bf..00000000 Binary files a/frontend/src/images/workstations/piso-2.png and /dev/null differ diff --git a/frontend/src/index.css b/frontend/src/index.css index 627050a4..43b6b489 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -140,14 +140,29 @@ calendar-month { } } -.embla__container { +/* .embla__container { max-width: 1280px; -} +} */ -@media (max-width: 1440px) { +/* @media (max-width: 1440px) { .embla__container { max-width: 1024px; } +} */ + +.embla__slide { + transform: translate3d(0, 0, 0); + flex: 0 0 100%; + min-width: 0; + padding-left: 1rem; +} + +.embla__slide__img { + border-radius: 1.8rem; + display: block; + height: auto; + width: 100%; + object-fit: cover; } .embla__dots { @@ -156,6 +171,7 @@ calendar-month { justify-content: center; align-items: center; } + .embla__dot { -webkit-tap-highlight-color: rgba(49, 49, 49, 0.5); -webkit-appearance: none; @@ -175,6 +191,7 @@ calendar-month { justify-content: center; border-radius: 50%; } + .embla__dot:after { box-shadow: inset 0 0 0 0.1rem rgb(187, 187, 187); width: 1.2rem; @@ -184,6 +201,54 @@ calendar-month { align-items: center; content: ''; } + .embla__dot--selected:after { box-shadow: inset 0 0 0 0.1rem rgb(54, 49, 61); } + +.embla__lazy-load { + position: relative; + height: 100%; +} + +.embla__lazy-load__spinner { + border: 0.4rem solid rgba(0, 0, 0, 0.1); + border-left: 0.4rem solid rgb(54, 49, 61); + font-size: 1rem; + display: inline-flex; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + margin: auto; + text-indent: -9999em; + animation: loading 1.1s infinite linear; + border-radius: 50%; + width: 5rem; + height: 5rem; +} + +.embla__lazy-load__spinner:after { + border-radius: inherit; + width: 5rem; + height: 5rem; +} + +.embla__lazy-load__img { + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +.embla__lazy-load--has-loaded .embla__lazy-load__img { + opacity: 1; +} + +@keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +}