diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/weatherApp/README.md b/weatherApp/README.md index f768e33..119d95a 100644 --- a/weatherApp/README.md +++ b/weatherApp/README.md @@ -1,8 +1,48 @@ -# React + Vite +# PR: Implementación de React Router y Mejoras en la UI para Weather App -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +## Descripción +Este PR introduce la implementación de React Router para navegación multi-página, la creación de nuevas páginas (Home, Weather, Map y About), la funcionalidad para guardar ciudades favoritas y mejoras en la UI utilizando Shadcn/ui. Además, se ha agregado una funcionalidad de mapa para visualizar datos meteorológicos y se han implementado mejoras avanzadas como modo oscuro, historial climático y comparación de clima. -Currently, two official plugins are available: +## Cambios Principales -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +### 1. Implementación de React Router +- Instalado **react-router-dom** con `npm install react-router-dom` +- Configuradas rutas para: + - `/` → Home + - `/weather` → Weather + - `/map` → Map + - `/about` → About +- Creado un componente de navegación para cambiar entre las páginas. + +### 2. Creación de Nuevas Páginas +- **Home**: Mensaje de bienvenida y enlaces rápidos. +- **Weather**: Mantiene la funcionalidad de búsqueda y visualización de clima. +- **Map**: Vista de mapa para datos meteorológicos utilizando **react-leaflet**. +- **About**: Información sobre la aplicación y sus características. + +### 3. Funcionalidad para Guardar Ciudades Favoritas +- Implementado almacenamiento de ciudades favoritas en **localStorage**. +- Creado un listado de ciudades favoritas para acceso rápido. + +### 4. Mejora de UI con Shadcn/ui +- Instalado **Shadcn/ui** con `pm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react`. +- Sustituido CSS por componentes de Shadcn/ui. +- Implementación de componentes con tailwind. + +### 5. Implementación de Funcionalidad de Mapa +- Integrado **react-leaflet** para mostrar datos meteorológicos geográficamente. +- Mostrados íconos del clima en el mapa para ciudades buscadas o favoritas. +- Agregada funcionalidad de clic en el mapa para obtener clima en ubicaciones seleccionadas. + +### 6. Mejoras Avanzadas Implementadas +- **Modo Oscuro**: Implementado un switch para cambiar entre modos claro y oscuro utilizando el sistema de temas de Shadcn/ui. +- **Historial Climático**: Agregado un selector de fecha para consultar datos históricos de clima de una ciudad. +- **Comparación de Clima**: Implementada funcionalidad para comparar el clima de múltiples ciudades en un gráfico interactivo usando **recharts**. + +## Pruebas Realizadas +- [x] Navegación fluida entre páginas. +- [x] Almacenamiento y recuperación de ciudades favoritas desde localStorage. +- [x] Integración correcta de mapa y visualización de datos meteorológicos. +- [x] Funcionalidad de modo oscuro operativa. +- [x] Histórico de búsquedas. +- [x] Comparación de clima entre dos ciudades. \ No newline at end of file diff --git a/weatherApp/components.json b/weatherApp/components.json new file mode 100644 index 0000000..500e6bc --- /dev/null +++ b/weatherApp/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": false, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/weatherApp/components/mode-toggle.jsx b/weatherApp/components/mode-toggle.jsx new file mode 100644 index 0000000..d70a48c --- /dev/null +++ b/weatherApp/components/mode-toggle.jsx @@ -0,0 +1,37 @@ +import { Moon, Sun } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { useTheme } from "@/components/theme-provider" + +export function ModeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ) +} diff --git a/weatherApp/components/theme-provider.jsx b/weatherApp/components/theme-provider.jsx new file mode 100644 index 0000000..5686ea3 --- /dev/null +++ b/weatherApp/components/theme-provider.jsx @@ -0,0 +1,62 @@ +/* eslint-disable react/prop-types */ +import { createContext, useState, useEffect, useContext } from "react" + +const initialState = { + theme: "system", + setTheme: () => null +} + +const ThemeProviderContext = createContext(initialState) + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey ="vite-ui-theme", + ...props +}) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey)) || defaultTheme + ) + + useEffect(() => { + const root = window.document.documentElement + + root.classList.remove("light", "dark") + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light" + + root.classList.add(systemTheme) + return + } + + root.classList.add(theme) + }, [theme]) + + const value = { + theme, + setTheme: (theme) => { + localStorage.setItem(storageKey, theme) + setTheme(theme) + }, + } + + return ( + + {children} + + ) + } + + + export const useTheme = () => { + const context = useContext(ThemeProviderContext) + + if (context === undefined) + throw new Error("useTheme must be used within a ThemeProvider") + + return context + } \ No newline at end of file diff --git a/weatherApp/components/ui/button.jsx b/weatherApp/components/ui/button.jsx new file mode 100644 index 0000000..eb534c9 --- /dev/null +++ b/weatherApp/components/ui/button.jsx @@ -0,0 +1,54 @@ + +import { Slot } from "@radix-ui/react-slot" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + destructive: + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", + outline: + "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}) { + const Comp = asChild ? Slot : "button" + + return ( + + ); +} + +export { Button, buttonVariants } diff --git a/weatherApp/components/ui/card.jsx b/weatherApp/components/ui/card.jsx new file mode 100644 index 0000000..3f99be9 --- /dev/null +++ b/weatherApp/components/ui/card.jsx @@ -0,0 +1,75 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ + className, + ...props +}) { + return ( +
+ ); +} + +function CardHeader({ + className, + ...props +}) { + return ( +
+ ); +} + +function CardTitle({ + className, + ...props +}) { + return ( +
+ ); +} + +function CardDescription({ + className, + ...props +}) { + return ( +
+ ); +} + +function CardContent({ + className, + ...props +}) { + return (
); +} + +function CardFooter({ + className, + ...props +}) { + return ( +
+ ); +} + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/weatherApp/components/ui/chart.jsx b/weatherApp/components/ui/chart.jsx new file mode 100644 index 0000000..0b9519f --- /dev/null +++ b/weatherApp/components/ui/chart.jsx @@ -0,0 +1,309 @@ +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { + light: "", + dark: ".dark" +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +function ChartContainer({ + id, + className, + children, + config, + ...props +}) { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ); +} + +const ChartStyle = ({ + id, + config +}) => { + const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color) + + if (!colorConfig.length) { + return null + } + + return ( +