Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
50 changes: 45 additions & 5 deletions weatherApp/README.md
Original file line number Diff line number Diff line change
@@ -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.
21 changes: 21 additions & 0 deletions weatherApp/components.json
Original file line number Diff line number Diff line change
@@ -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"
}
37 changes: 37 additions & 0 deletions weatherApp/components/mode-toggle.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
62 changes: 62 additions & 0 deletions weatherApp/components/theme-provider.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
)
}


export const useTheme = () => {
const context = useContext(ThemeProviderContext)

if (context === undefined)
throw new Error("useTheme must be used within a ThemeProvider")

return context
}
54 changes: 54 additions & 0 deletions weatherApp/components/ui/button.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props} />
);
}

export { Button, buttonVariants }
75 changes: 75 additions & 0 deletions weatherApp/components/ui/card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as React from "react"

import { cn } from "@/lib/utils"

function Card({
className,
...props
}) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props} />
);
}

function CardHeader({
className,
...props
}) {
return (
<div
data-slot="card-header"
className={cn("flex flex-col gap-1.5 px-6", className)}
{...props} />
);
}

function CardTitle({
className,
...props
}) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props} />
);
}

function CardDescription({
className,
...props
}) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props} />
);
}

function CardContent({
className,
...props
}) {
return (<div data-slot="card-content" className={cn("px-6", className)} {...props} />);
}

function CardFooter({
className,
...props
}) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6", className)}
{...props} />
);
}

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
Loading