diff --git a/.github/workflows/asw2122.yml b/.github/workflows/asw2122.yml index be5f522..68b025d 100644 --- a/.github/workflows/asw2122.yml +++ b/.github/workflows/asw2122.yml @@ -91,7 +91,7 @@ jobs: user: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_KEY }} command: | - wget https://raw.githubusercontent.com/Arquisoft/dede_es1a/Ignacio_BaseDeDatos/docker-compose-deploy.yml -O docker-compose.yml + wget https://raw.githubusercontent.com/Arquisoft/dede_es1a/master/docker-compose-deploy.yml -O docker-compose.yml docker-compose stop docker-compose rm -f docker-compose pull diff --git a/restapi/api.ts b/restapi/api.ts index b24dc10..03bafc2 100644 --- a/restapi/api.ts +++ b/restapi/api.ts @@ -1,10 +1,11 @@ + import express, { Request, Response, Router } from 'express'; import {check} from 'express-validator'; -import { addOrder, findOrdersByUserEmail, getDeliveryCosts } from './controllers/OrderController'; +import { addOrder, findOrdersByUserEmail, getBestSeller, getDeliveryCosts } from './controllers/OrderController'; + import {findRocks, addRock, deleteRock, findRocksSedimentary, findRocksMetamorphic, findRocksFiery, findByCritery, findRocksById} from './controllers/RockController'; import {findUsers, addUser, deleteUser, loginUser, logout} from './controllers/UserController'; -const User = require("./models/User"); -const Rock = require("./models/Rock"); + const api:Router = express.Router() @@ -38,6 +39,7 @@ api.post("/rocks/add", addRock); api.post("/rocks/delete", deleteRock); + //Methods for product of the app api.get("/orders/userList/:userEmail", findOrdersByUserEmail); @@ -45,4 +47,6 @@ api.post("/orders/add", addOrder); api.post("/orders/deliveryCosts", getDeliveryCosts) +api.get("/orders/bestSeller", getBestSeller); + export default api; \ No newline at end of file diff --git a/restapi/controllers/OrderController.ts b/restapi/controllers/OrderController.ts index 2cef1d4..fcf15ec 100644 --- a/restapi/controllers/OrderController.ts +++ b/restapi/controllers/OrderController.ts @@ -7,6 +7,7 @@ const options = { const geocoder = NodeGeocoder(options); const Order = require("../models/Order"); +const Rock = require("../models/Rock"); export const findOrdersByUserEmail = async (req:Request, res:Response) => { @@ -51,6 +52,28 @@ export const addOrder = async (req:Request, res:Response): Promise => { }; + export const getBestSeller = async (req:Request, res:Response): Promise => { + + const order = await Order.distinct("productName"); + + let num = 0; + let aux; + let aux2; + + for (let i =0; i < order.length; i++){ + aux= await Order.find({productName :order[i]}); + if (aux.length > num){ + num = aux.length; + aux2 = aux[0].productName; + } + } + + let bestSeller = await Rock.find({name: aux2}); + res.setHeader('Content-Type', 'application/json'); + res.status(200); + res.send(bestSeller); + + } export const getDeliveryCosts = async (req:Request, res:Response) : Promise =>{ @@ -60,7 +83,6 @@ export const addOrder = async (req:Request, res:Response): Promise => { let string = JSON.stringify(addressCordinates); let objectValue = JSON.parse(string); - console.log(addressCordinates); let latitudeAddress = objectValue[0].latitude let longitudeAddress = objectValue[0].longitude diff --git a/restapi/controllers/RockController.ts b/restapi/controllers/RockController.ts index ec9228b..1fe3127 100644 --- a/restapi/controllers/RockController.ts +++ b/restapi/controllers/RockController.ts @@ -1,4 +1,5 @@ -import express, { Request, Response, Router } from 'express'; +import express, { json, Request, Response, Router } from 'express'; +import { stringify } from 'querystring'; const Rock = require("../models/Rock"); const mongoose = require("mongoose"); @@ -42,8 +43,52 @@ export const findRocksMetamorphic = async (req:Request, res:Response) => { }; export const findByCritery = async (req:Request, res:Response) => { - let critery = req.body.critery; - const rocks = await Rock.find(critery) + + let name=req.query.nameSubString; + name=name?.toString().trimEnd().trimStart() + let nameRegEx; + if(name!==undefined) + nameRegEx=new RegExp(name, "gi"); + let critery = { + mohsHardness: + { + $gt:req.query.mohsMin, + $lt:req.query.mohsMax + }, + density: + { + $gt:req.query.densityMin, + $lt:req.query.densityMax + }, + price: + { + $gt:req.query.priceMin, + $lt:req.query.priceMax + }, + name: + { + $regex:nameRegEx + }, + type: + { + $regex:req.query.type + } + }; + console.log(critery) + var rocks + try { + rocks = await Rock.find(critery) + } catch (error) { + console.log(error) + console.log("Values:" + +"\n\tmohsMin: "+req.query.mohsMin + +"\n\tmohsMax: "+req.query.mohsMax + +"\n\tdensityMin: "+req.query.densityMin + +"\n\tpriceMin: "+req.query.priceMin + +"\n\tpriceMax: "+req.query.priceMax + +"\n\tnameSubString: "+req.query.nameSubString) + } + res.setHeader('Content-Type', 'application/json'); res.status(200); res.send(rocks); diff --git a/restapi/controllers/UserController.ts b/restapi/controllers/UserController.ts index 255de0c..9e7b8e4 100644 --- a/restapi/controllers/UserController.ts +++ b/restapi/controllers/UserController.ts @@ -1,7 +1,6 @@ -import express, { Request, Response, Router } from 'express'; +import { Request, Response } from 'express'; const User = require("../models/User"); -const mongoose = require("mongoose"); const crypto = require("crypto"); const jwt = require("jsonwebtoken"); @@ -95,7 +94,7 @@ export const loginUser = async (req: Request, res: Response): Promise => { //autenticado: true, // token: token //}); - res.send(req.session.usuario); + res.send(); } } diff --git a/restapi/tests/api.test.ts b/restapi/tests/api.test.ts index c94c723..16087d5 100644 --- a/restapi/tests/api.test.ts +++ b/restapi/tests/api.test.ts @@ -321,7 +321,6 @@ describe('product ', () => { const response: Response = await request(app).get("/api/rocks/list/critery") .send({critery : {name : "prueba"}}) .set('Accept', 'application/json'); - expect(response.body[0].name).toBe("prueba"); expect(response.statusCode).toBe(200); }); @@ -343,6 +342,12 @@ describe('product ', () => { expect(response.statusCode).toBe(200); }); + it('can be listed by id', async () => { + const response: Response = await request(app).get("/api/rocks/" + "prueba3") + expect(response.body[0].rockId).toBe("prueba3"); + expect(response.statusCode).toBe(200); + }); + it('can be deleted', async () => { const response: Response = await request(app).post("/api/rocks/delete") .send({ rockId: "prueba" }) @@ -374,21 +379,29 @@ describe('order ', () => { send({ orderId: "prueba", userEmail: "prueba", + productId: "prueba", price: 3, - productId: "prueba" + productName: "prueba", + productType: "prueba" }) .set('Accept', 'application/json') expect(response.statusCode).toBe(200); }); it('can be listed', async () => { - const response: Response = await request(app).get("/api/orders/userList") + const response: Response = await request(app).get("/api/orders/userList/" + "prueba") .send({userEmail : "prueba"}) .set('Accept', 'application/json'); expect(response.body[0].userEmail).toBe("prueba"); expect(response.statusCode).toBe(200); }); + it('cant be listed', async () => { + const response: Response = await request(app).get("/api/orders/userList/") + .set('Accept', 'application/json'); + expect(response.statusCode).toBe(404); + }); + it('can obtain deliveryCosts', async () => { const response: Response = await request(app).post("/api/orders/deliveryCosts") .send({address : "Palmira Villa, Oviedo"}) diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index e238b70..588606d 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -1,19 +1,14 @@ //import { useQuery } from 'react-query'; -import Box from '@mui/material/Box'; //import Link from '@mui/material/Link'; -import Grid from '@mui/material/Grid'; -import logo from './images/interfaz/logoRock.png'; -import Title from './components/titleUtil'; -import { createTheme, Drawer, List } from '@mui/material'; +import { Drawer } from '@mui/material'; import { useState, useEffect } from 'react'; import Welcome from './components/Welcome'; -import {getRocas} from './api/api'; import './css/App.css'; -import { Route, Routes, Navigate, BrowserRouter as Router, BrowserRouter } from "react-router-dom"; +import { Route, Routes, Navigate, BrowserRouter } from "react-router-dom"; import {Rock} from './shared/shareddtypes'; import Catalog from './components/Catalog'; import { ThemeProvider } from '@emotion/react'; @@ -25,9 +20,6 @@ import { Container } from "@mui/material"; import "./css/App.css" import ShoppingCart from "./components/ShoppingCart"; -import PaymentPage from "./components/payment/PaymentPage"; -import { ContentCopy } from "@mui/icons-material"; -import PaymentSummary from './components/payment/PaymentSummary'; import PaymentProcess from './components/payment/PaymentPage'; import OrderHistory from './components/Orders'; @@ -36,12 +28,6 @@ type Props = { }; function App(): JSX.Element { - const [rocks, setRocks] = useState([]); - - const refreshRockList = async () => { - setRocks(await getRocas()); - }; - // Shopping cart const [isNewCart, setNewCart] = useState(false); const [isCartOpen, setCartOpen] = useState(false); @@ -98,20 +84,17 @@ function App(): JSX.Element { ); }; - useEffect(() => { - refreshRockList(); - }, []); return ( setCartOpen(true)} /> - } /> + } /> } /> - } /> + } /> } /> - } /> + } /> } /> } /> }/> diff --git a/webapp/src/api/api.ts b/webapp/src/api/api.ts index c414000..8219065 100644 --- a/webapp/src/api/api.ts +++ b/webapp/src/api/api.ts @@ -1,7 +1,8 @@ import {User, Rock, Order} from '../shared/shareddtypes'; +const apiEndPoint =process.env.REACT_APP_API_URI || 'http://localhost:5000/api'; + export async function addUser(user:User):Promise{ - const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint+'/users/add', { method: 'POST', headers: {'Content-Type':'application/json'}, @@ -14,14 +15,12 @@ export async function addUser(user:User):Promise{ } export async function getUsers():Promise{ - const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint+'/users/list'); //The objects returned by the api are directly convertible to User objects return response.json() } export async function getRocas():Promise{ - const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint+'/rocks/list'); //The objects returned by the api are directly convertible to User objects return response.json() @@ -36,26 +35,31 @@ export async function getRocksById(rockId:String):Promise{ export async function getRocksSedimentary():Promise{ - const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint+'/rocks/list/sedimentary'); //The objects returned by the api are directly convertible to User objects return response.json() } export async function getRocksFiery():Promise{ - const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint+'/rocks/list/fiery'); //The objects returned by the api are directly convertible to User objects return response.json() } export async function getRocksMetamorphic():Promise{ - const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint+'/rocks/list/metamorphic'); //The objects returned by the api are directly convertible to User objects return response.json() } - +export async function getFilteredRocks(mohsMin:Number,mohsMax:Number,densityMin:Number,densityMax:Number,priceMin:Number,priceMax:Number,nameSubString:string,type:string):Promise { + let response = await fetch(apiEndPoint+'/rocks/list/critery?mohsMin='+mohsMin+"&mohsMax="+mohsMax+"&densityMin="+densityMin+"&densityMax="+densityMax+"&priceMin="+priceMin+"&priceMax="+priceMax+"&nameSubString="+nameSubString+"&type="+type); + //The objects returned by the api are directly convertible to User objects + return response.json() +} +export async function getMaxAndMins() { + let response = await fetch(apiEndPoint+'/rocks/maxvalues'); + //The objects returned by the api are directly convertible to User objects + return response.json() +} export async function checkUser(email:String,password:String):Promise{ - const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint+'/users/login', { method: 'POST', headers: {'Content-Type':'application/json'}, @@ -96,4 +100,18 @@ export async function getOrders(): Promise{ const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' let response = await fetch(apiEndPoint + "/orders/userList/" + sessionStorage.getItem("userLogged")); return response.json(); +} + +export async function addOrder(order:Order):Promise{ + const apiEndPoint= process.env.REACT_APP_API_URI || 'http://localhost:5000/api' + let response = await fetch(apiEndPoint+'/orders/add', { + method: 'POST', + headers: {'Content-Type':'application/json'}, + body: JSON.stringify({'userEmail':order.userEmail, 'price':order.price, 'productId':order.productId, + 'productName':order.productName, 'productType': order.productType}) + }); + if (response.status===200) + return true; + else + return false; } \ No newline at end of file diff --git a/webapp/src/components/Catalog.tsx b/webapp/src/components/Catalog.tsx index 13313b7..6e0f870 100644 --- a/webapp/src/components/Catalog.tsx +++ b/webapp/src/components/Catalog.tsx @@ -1,23 +1,139 @@ -import {Rock} from '../shared/shareddtypes'; -import List from '@mui/material/List'; -import Product from './Product'; - +import { Rock } from "../shared/shareddtypes"; +import Product from "./Product"; +import { + Accordion, + AccordionSummary, + Grid, + Typography, +} from "@mui/material"; +import { getFilteredRocks } from "../api/api"; +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import React from "react"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import Filter, { defaultCriteryForSearch } from "./Filter"; type RockListProps = { - rocks: Rock[]; - handleAddToCart(rock:Rock): void; + handleAddToCart(rock: Rock): void; + testRocks?: Rock[]; }; + + + +function useQuery() { + const { search } = useLocation(); + + return React.useMemo(() => new URLSearchParams(search), [search]); +} //a -function Catalogo(rocks: RockListProps): JSX.Element { +function Catalog(rockListPros: RockListProps): JSX.Element { + let query = useQuery(); + + const [rocks, setRocks] = useState([]); + + + //?densityMin=1&densityMax=3 + const refreshRockList = async () => { + const mohsMinStr = query.get("mohsMin"), + mohsMaxStr = query.get("mohsMax"), + densityMinStr = query.get("densityMin"), + densityMaxStr = query.get("densityMax"), + priceMinStr = query.get("priceMin"), + priceMaxStr = query.get("pirceMax"); + var nameSubstring = query.get("nameSubstring"); + var typeSearched = query.get("type"); + + nameSubstring = + nameSubstring === null || nameSubstring === "undefined" + ? "" + : nameSubstring; + + typeSearched = + typeSearched === null || + typeSearched === "undefined" || + typeSearched === "todas" + ? "" + : typeSearched; + let mohsMin: number, + mohsMax: number, + densityMin: number, + densityMax: number, + priceMin: number, + priceMax: number; + + if (mohsMinStr !== undefined && mohsMinStr !== null) + mohsMin = parseFloat(mohsMinStr); + else mohsMin = defaultCriteryForSearch.mohsMin; + + if (mohsMaxStr !== undefined && mohsMaxStr !== null) + mohsMax = parseFloat(mohsMaxStr); + else mohsMax = defaultCriteryForSearch.mohsMax; + + if (densityMinStr !== undefined && densityMinStr !== null) + densityMin = parseFloat(densityMinStr); + else densityMin = defaultCriteryForSearch.densityMin; + + if (densityMaxStr !== undefined && densityMaxStr !== null) + densityMax = parseFloat(densityMaxStr); + else densityMax = defaultCriteryForSearch.densityMax; + + if (priceMinStr !== undefined && priceMinStr !== null) + priceMin = parseFloat(priceMinStr); + else priceMin = defaultCriteryForSearch.priceMin; + + if (priceMaxStr !== undefined && priceMaxStr !== null) + priceMax = parseFloat(priceMaxStr); + else priceMax = defaultCriteryForSearch.priceMax; + if (rockListPros.testRocks === undefined) + setRocks( + await getFilteredRocks( + mohsMin, + mohsMax, + densityMin, + densityMax, + priceMin, + priceMax, + nameSubstring, + typeSearched + ) + ); + else setRocks(rockListPros.testRocks); + }; + + + + useEffect(() => { + refreshRockList(); + }, []); + //TODO: El nameSubstring se come la ultima letra + return ( <> - - {rocks.rocks.map((rock,index)=>{ - return - })} - - + + } + aria-controls="panel1a-content" + id="panel1a-header" + > + Filtrar + + + + + {rocks.map((rock, index) => { + return ( + + + + ); + })} + ); } -export default Catalogo; +export default Catalog; diff --git a/webapp/src/components/Filter.tsx b/webapp/src/components/Filter.tsx new file mode 100644 index 0000000..2ee711c --- /dev/null +++ b/webapp/src/components/Filter.tsx @@ -0,0 +1,165 @@ +import { Button, Grid } from "@mui/material"; +import { useState } from "react"; +import RangeSlider from "./Rangeslider"; +import BasicTextField from "./TextField"; +import BasicTextFieldWithOptions from "./TextFieldWithOptions"; + +export const TYPES_LIST = ["todas", "metamórfica", "sedimentaria", "ígnea"]; +export enum TYPES_INDEX { + ALL = 0, + METAMORPHIC, + SEDIMENTARY, + IGNEOUS, +} +export const defaultCriteryForSearch: DefaultSearchCritery = { + mohsMin: 0, + mohsMax: 10, + densityMin: 0, + densityMax: 100, + priceMin: 0, + priceMax: 100, + type: TYPES_LIST[TYPES_INDEX.ALL], + nameSubstring: "", + }; + type DefaultSearchCritery = { + mohsMin: number; + mohsMax: number; + densityMin: number; + densityMax: number; + priceMin: number; + priceMax: number; + type: string; + nameSubstring: string; + }; + export type SearchCritery = { + mohsMin?: number; + mohsMax?: number; + densityMin?: number; + densityMax?: number; + priceMin?: number; + priceMax?: number; + type?: string; + nameSubstring?: string; + }; +type FilterProps = { + refreshRockList:() => Promise +}; +function Filter(props: FilterProps): JSX.Element { + const [critery,setCritery]= useState( + defaultCriteryForSearch + ); + const handleChangeMohs = (low: number, high: number) => { + setCritery(critery => ({ + ...critery, + mohsMin:low, + mohsMax:high + })); + }; + const handleChangeDensity = (low: number, high: number) => { + setCritery(critery => ({ + ...critery, + densityMin:low, + densityMax:high + })); + }; + const handleChangePrice = (low: number, high: number) => { + setCritery(critery => ({ + ...critery, + priceMin:low, + priceMax:high + })); + }; + const handleChangeNameSubstring = (name:string)=>{ + + } + const handleChangeType = (name:string)=>{ + + } + let filterLink = + "/catalog?mohsMin=" + + critery.mohsMin + + "&mohsMax=" + + critery.mohsMax + + "&densityMin=" + + critery.densityMin + + "&densityMax=" + + critery.densityMax + + "&priceMin=" + + critery.priceMin + + "&priceMax=" + + critery.priceMax + + "&nameSubstring=" + + critery.nameSubstring + + "&type=" + + critery.type; + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} + +export default Filter; diff --git a/webapp/src/components/Login.tsx b/webapp/src/components/Login.tsx index 4bc4f02..446d522 100644 --- a/webapp/src/components/Login.tsx +++ b/webapp/src/components/Login.tsx @@ -40,7 +40,7 @@ function EmailForm(): JSX.Element { const handleLogin = (email: String, pass: String) => { - axios.post("http://localhost:5000/user/login",{"email":email,"password":pass}) + axios.post(("http://localhost:5000/api/user/login"|| process.env.REACT_APP_API_URI +"/users/login"),{"email":email,"password":pass}) .then(res => { if(res.status == 201){ Swal.fire({ @@ -79,7 +79,7 @@ function EmailForm(): JSX.Element { setNotificationStatus(true); setNotification({ severity:'error', - message:'There\'s been an error in the register proccess.' + message:'El usuario o contraseña son incorrectos, vuelva a introducirlos' }); } } diff --git a/webapp/src/components/Orders.tsx b/webapp/src/components/Orders.tsx index 410f097..eab7872 100644 --- a/webapp/src/components/Orders.tsx +++ b/webapp/src/components/Orders.tsx @@ -10,8 +10,9 @@ import Paper from '@mui/material/Paper'; import { getOrders } from '../api/api'; import OrderUser from '../components/OrderUser'; -type Id = { - email:String +type OrdersProps = { + email:string + ordersTest?:Order[] } const StyledTableCell = styled(TableCell)(({ theme }) => ({ @@ -28,17 +29,20 @@ async function ordersByEmail(order:Order[], orderEmail:Order[], email:String){ order.forEach(e => {if(e.userEmail == email){ orderEmail.push(e) }}); } -const OrderHistory = (id: Id) => { +const OrderHistory = (props: OrdersProps) => { const [orders, setOrders] = React.useState([]); const[ordersEmail, setOrdersEmail] = React.useState([]); async function cargarPedidos() { - setOrdersEmail(await getOrders()); + if (props.ordersTest === undefined) + setOrdersEmail(await getOrders()); + else + setOrdersEmail(props.ordersTest); } useEffect(() => {cargarPedidos();}, []); - ordersByEmail(orders,ordersEmail,id.email); + ordersByEmail(orders,ordersEmail,props.email); return (
diff --git a/webapp/src/components/Product.tsx b/webapp/src/components/Product.tsx index 1e51a34..3d87f37 100644 --- a/webapp/src/components/Product.tsx +++ b/webapp/src/components/Product.tsx @@ -1,46 +1,73 @@ -import {Rock} from '../shared/shareddtypes'; -import ListItemText from '@mui/material/ListItemText'; -import Grid from '@mui/material/Grid'; +import { Rock } from '../shared/shareddtypes'; import Button from '@mui/material/Button'; +import { Card, CardActionArea, CardContent, CardMedia, Grid, Paper, Typography } from '@mui/material'; type ProductProps = { - product: Rock |null; - buyable:boolean; - handleAddToCart(r:Rock): void; + product: Rock | null; + buyable: boolean; + handleAddToCart(r: Rock): void; }; + //a function Product(product: ProductProps): JSX.Element { return ( -
-
- {product.product!==null ? - <> - {product.product.name.toString()}/ - - : <> - } -
-
- - {product.product!==null ? - <> -

{"nombre: "+product.product.name}

-

{"precio: "+product.product.price+"€"}

-

{"tipo: "+ product.product.type}

-

{"mohs: "+ product.product.mohsHardness}

-

{"densidad: "+product.product.density}

- - : <> - } - -
- -
+ + + + + + + {product.product !== null ? + + + + + {"Nombre: " + product.product.name} + + + + + {"Precio: " + product.product.price} + + + + + {"Densidad: " + product.product.density} + + + + + {"Mohs: " + product.product.mohsHardness} + + + + + + {"Tipo: " + product.product.type[0].toLocaleUpperCase()+product.product.type.substring(1,product.product.type.length)} + + + + + : <> + } + + + + + + + + + ); } diff --git a/webapp/src/components/Rangeslider.tsx b/webapp/src/components/Rangeslider.tsx new file mode 100644 index 0000000..e53220f --- /dev/null +++ b/webapp/src/components/Rangeslider.tsx @@ -0,0 +1,45 @@ +import * as React from "react"; +import Slider from "@mui/material/Slider"; +import { Typography } from "@mui/material"; + + +type SliderProps = { + max: number; + min: number; + actualMin:number; + actualMax:number; + valueName: string; + onValueChanged(low: Number, high: Number): void; +}; + +export default function RangeSlider(sliderProps: SliderProps) { + const [value, setValue] = React.useState([ + sliderProps.actualMin, + sliderProps.actualMax, + ]); + const handleChange = (event: Event, newValue: number | number[]) => { + setValue(newValue as number[]); + sliderProps.onValueChanged( + (newValue as number[])[0], + (newValue as number[])[1] + ); + }; + + return ( + <> + {sliderProps.valueName} + sliderProps.valueName} + value={value} + step={0.01} + max={sliderProps.max} + min={sliderProps.min} + onChange={handleChange} + valueLabelDisplay="auto" + id={sliderProps.valueName} + size="medium" + /> + + + ); +} diff --git a/webapp/src/components/Register.tsx b/webapp/src/components/Register.tsx index f817ab6..133b8c1 100644 --- a/webapp/src/components/Register.tsx +++ b/webapp/src/components/Register.tsx @@ -202,8 +202,8 @@ const getEmail = async (email: String) => {
-
- ¿Ya tienes una cuenta? Inicia sesión aqui! + + ¿Ya tienes una cuenta? Inicia sesión aqui! diff --git a/webapp/src/components/Showcase.tsx b/webapp/src/components/Showcase.tsx index 421cd3e..69704f2 100644 --- a/webapp/src/components/Showcase.tsx +++ b/webapp/src/components/Showcase.tsx @@ -1,37 +1,158 @@ -import { AppBar, ListItemText } from '@mui/material'; -import { useState } from 'react'; -import {Rock} from '../shared/shareddtypes'; -import Product from './Product'; +import { AppBar, Button, Card, Grid } from "@mui/material"; +import { useEffect, useState } from "react"; +import { Rock } from "../shared/shareddtypes"; +import Product from "./Product"; +import { defaultCriteryForSearch, SearchCritery } from "./Filter"; +import { getFilteredRocks } from "../api/api"; type RockListProps = { - rocks: Rock[]; - name:String; - handleAddToCart(r:Rock): void; + name: string; + handleAddToCart(r: Rock): void; + search: SearchCritery; + testRocks?:Rock[] }; - +export const NUMBER_OF_PRODUCTS_SHOWN: number = 2; //a function Showcase(prefilteredbox: RockListProps): JSX.Element { + const [rocks, setRocks] = useState([]); + + var link: string; + + let mohsMin: number, + mohsMax: number, + densityMin: number, + densityMax: number, + priceMin: number, + priceMax: number, + nameSubstring: string, + type: string; + + if ( + prefilteredbox.search.mohsMin !== undefined && + prefilteredbox.search.mohsMin !== null + ) + mohsMin = prefilteredbox.search.mohsMin; + else mohsMin = defaultCriteryForSearch.mohsMin; + + if ( + prefilteredbox.search.mohsMax !== undefined && + prefilteredbox.search.mohsMax !== null + ) + mohsMax = prefilteredbox.search.mohsMax; + else mohsMax = defaultCriteryForSearch.mohsMax; + + if ( + prefilteredbox.search.densityMin !== undefined && + prefilteredbox.search.densityMin !== null + ) + densityMin = prefilteredbox.search.densityMin; + else densityMin = defaultCriteryForSearch.densityMin; + + if ( + prefilteredbox.search.densityMax !== undefined && + prefilteredbox.search.densityMax !== null + ) + densityMax = prefilteredbox.search.densityMax; + else densityMax = defaultCriteryForSearch.densityMax; + if ( + prefilteredbox.search.priceMin !== undefined && + prefilteredbox.search.priceMin !== null + ) + priceMin = prefilteredbox.search.priceMin; + else priceMin = defaultCriteryForSearch.priceMin; + + if ( + prefilteredbox.search.priceMax !== undefined && + prefilteredbox.search.priceMax !== null + ) + priceMax = prefilteredbox.search.priceMax; + else priceMax = defaultCriteryForSearch.priceMax; + + if ( + prefilteredbox.search.nameSubstring !== undefined && + prefilteredbox.search.nameSubstring !== null + ) + nameSubstring = prefilteredbox.search.nameSubstring; + else nameSubstring = defaultCriteryForSearch.nameSubstring; + + if ( + prefilteredbox.search.type !== undefined && + prefilteredbox.search.type !== null + ) + type = prefilteredbox.search.type; + else type = defaultCriteryForSearch.type; + + if(type==="all"){ + type=""; + } + link = + "/catalog?mohsMin=" + + mohsMin + + "&mohsMax=" + + mohsMax + + "&densityMin=" + + densityMin + + "&densityMax=" + + densityMax + + "&priceMin=" + + priceMin + + "&priceMax=" + + priceMax + + "&nameSubstring=" + + nameSubstring + + "&type=" + + type; + const refreshRockList = async () => { + if(prefilteredbox.testRocks!==undefined){ + setRocks(prefilteredbox.testRocks) + }else + setRocks( + await getFilteredRocks( + mohsMin, + mohsMax, + densityMin, + densityMax, + priceMin, + priceMax, + nameSubstring, + type + ) + ); + }; + useEffect(() => { + refreshRockList(); + }, []); return ( - <> -
- + +
+

{prefilteredbox.name}

-
- { - prefilteredbox.rocks.map((_,product)=>{ - if(prefilteredbox.rocks[product]!==undefined) - return ; - - }) - } -
- -
- + + + {rocks.slice(0, NUMBER_OF_PRODUCTS_SHOWN).map((_, product) => { + return ( + + + + ); + })} + + + +
+ ); } diff --git a/webapp/src/components/Showcases.tsx b/webapp/src/components/Showcases.tsx index 2699cc9..b5a3534 100644 --- a/webapp/src/components/Showcases.tsx +++ b/webapp/src/components/Showcases.tsx @@ -1,37 +1,44 @@ -import { useEffect, useState } from "react"; -import { getRocksFiery, getRocksMetamorphic, getRocksSedimentary } from "../api/api"; import { Rock } from "../shared/shareddtypes"; import Showcase from "./Showcase"; -import prefilters from "../code/Prefilters" +import { Grid } from "@mui/material"; +import { SearchCritery } from "./Filter"; type RockListProps = { - handleAddToCart(r:Rock): void; + handleAddToCart(r: Rock): void; }; +export const LIST_OF_CRITERIES: SearchCritery[] = [ + { type: "sedimentaria" }, + { type: "magmática" }, + {type: "ígnea"}, + {type: "todas"} +]; +export const LIST_OF_NAMES:string[]=[ + "Sedimentarias", + "Metamórficas", + "Volcánicas", + "Las Mas Vendidas!" +] + function Showcases(prefilteredbox: RockListProps): JSX.Element { - const [prefilteredRocks,setPrefilteredRocks] = useState([]); - const [nameOfFilters,setNameOfFilters]=useState([]); - - - useEffect(()=>{ - const refreshRockList = async () => { - - setPrefilteredRocks([...prefilteredRocks,await getRocksMetamorphic(),await getRocksSedimentary(),await getRocksFiery()]) - setNameOfFilters(prefilters) - } - refreshRockList(); - - },[]); + return ( <> - - {prefilteredRocks.map((_, element) => { - - return (); - - })} - + + { + LIST_OF_CRITERIES.map((value,index)=> + + + + ) + } + ); } -export default Showcases \ No newline at end of file +export default Showcases; diff --git a/webapp/src/components/TextField.tsx b/webapp/src/components/TextField.tsx new file mode 100644 index 0000000..c0c00fe --- /dev/null +++ b/webapp/src/components/TextField.tsx @@ -0,0 +1,32 @@ + +import * as React from 'react'; +import TextField from '@mui/material/TextField'; + +type BasicTextFieldProps={ + label:string, + placeholder:string, + value:string, + actualValue:string, + onChange(nameSubstring:string):void +} +export default function BasicTextField(basicTextFieldProps:BasicTextFieldProps) { + const [value, setValue] = React.useState(basicTextFieldProps.value); + + const handleTextField=(event:React.ChangeEvent)=>{ + + setValue(event.target.value) + basicTextFieldProps.onChange(value) + console.log(value) + } + return ( + + ); +} \ No newline at end of file diff --git a/webapp/src/components/TextFieldWithOptions.tsx b/webapp/src/components/TextFieldWithOptions.tsx new file mode 100644 index 0000000..a6a7f59 --- /dev/null +++ b/webapp/src/components/TextFieldWithOptions.tsx @@ -0,0 +1,40 @@ +import * as React from "react"; +import TextField from "@mui/material/TextField"; +import { MenuItem } from "@mui/material"; + +type BasicTextFieldProps = { + values: string[]; + actualValue:string; + titleText: string; + helperText: string; + onValueChanged(type: string): void; +}; +export default function BasicTextFieldWithOptions( + basicTextFieldProps: BasicTextFieldProps +) { + + const [value, setValue] = React.useState(basicTextFieldProps.actualValue); + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.value); + basicTextFieldProps.onValueChanged(event.target.value); + }; + + return ( + + {basicTextFieldProps.values.map((value, number) => ( + + {value} + + ))} + + ); +} diff --git a/webapp/src/components/Welcome.tsx b/webapp/src/components/Welcome.tsx index 103b640..fa04495 100644 --- a/webapp/src/components/Welcome.tsx +++ b/webapp/src/components/Welcome.tsx @@ -15,7 +15,7 @@ function Welcome(prefilteredbox: RockListProps): JSX.Element { logo -
+
diff --git a/webapp/src/components/payment/PaymentPage.tsx b/webapp/src/components/payment/PaymentPage.tsx index 474c7e7..12cd19b 100644 --- a/webapp/src/components/payment/PaymentPage.tsx +++ b/webapp/src/components/payment/PaymentPage.tsx @@ -8,7 +8,7 @@ import { CardContent, Typography } from '@mui/material'; import { North } from '@mui/icons-material'; import { useState, useEffect } from 'react'; import { Rock } from '../../shared/shareddtypes'; -import { getDeliveryCosts } from '../../api/api'; +import { addOrder, getDeliveryCosts } from '../../api/api'; import { findConfigFile } from 'typescript'; import PaymentSummary from './PaymentSummary'; import CartItem from '../CartItem'; @@ -90,11 +90,42 @@ const PaymentPage: React.FC = ({cartContent, setNewCart}) => { return } + function getUserEmail() { + const userEmail = sessionStorage.getItem("userLogged"); + + if (userEmail) + return userEmail; + else + return ""; + + } + + const addOrders = async (rock:Rock) => { + let rockId = rock.rockId; + let price = rock.price; + let name = rock.name; + let type = rock.type; + console.log(getUserEmail()); + await addOrder({ userEmail: getUserEmail(), price: price, productId: rockId, productName: name, productType: type, orderId: "", date: new Date}); + } + const handlePay = () => { setNewCart(true); - //TODO: añadir a bd - alert("Pagado -> no se añade bd"); + + if(sessionStorage.getItem("userLogged")){ + cartContent.forEach(function(rock){ + + for (let i = 0; i< rock.quantityCart; i++){ + addOrders(rock); + } + }); + alert("Su pedido ha sido realizado correctamente"); + }else{ + alert("Se ha producido un error en la creación de su pedido"); + } } + + return (
diff --git a/webapp/src/components/tests/Catalog.test.tsx b/webapp/src/components/tests/Catalog.test.tsx new file mode 100644 index 0000000..4b8f241 --- /dev/null +++ b/webapp/src/components/tests/Catalog.test.tsx @@ -0,0 +1,37 @@ +import { render } from "@testing-library/react"; +import { Rock } from "../../shared/shareddtypes"; +import Catalog from "../Catalog"; +import { BrowserRouter as Router } from "react-router-dom"; +import { LIST_OF_ROCKS_TEST } from "./code/shared"; +test("Check that the showcases render properly", async () => { + const { container } = render( + + + + ); + + expect(container).toHaveTextContent("Mohs010"); + expect(container).toHaveTextContent("Mohs010"); + expect(container).toHaveTextContent("Mohs010"); + expect(container).toHaveTextContent("Mohs010"); + + for (let index = 0; index < LIST_OF_ROCKS_TEST.length; index++) { + var rock = LIST_OF_ROCKS_TEST[index]; + expect(container).toHaveTextContent("Nombre: " + rock.name.toString()); + expect(container).toHaveTextContent( + "Mohs: " + rock.mohsHardness.toString() + ); + expect(container).toHaveTextContent("Precio: " + rock.price.toString()); + expect(container).toHaveTextContent( + "Tipo: " + + rock.type.toString()[0].toUpperCase() + + rock.type.toString().substring(1, rock.type.toString().length) + ); + } + expect(container).toHaveTextContent(/Comprar/); +}); diff --git a/webapp/src/components/tests/Filter.test.tsx b/webapp/src/components/tests/Filter.test.tsx new file mode 100644 index 0000000..5d62425 --- /dev/null +++ b/webapp/src/components/tests/Filter.test.tsx @@ -0,0 +1,24 @@ +import { render } from "@testing-library/react"; +import { Rock } from "../../shared/shareddtypes"; +import Catalog from "../Catalog"; +import { BrowserRouter as Router } from "react-router-dom"; +import { LIST_OF_ROCKS_TEST } from "./code/shared"; +test("Check that the filter from the catalog renders properly", async () => { + const { container } = render( + + + + ); + + expect(container).toHaveTextContent("Mohs010"); + expect(container).toHaveTextContent("Densidad0100"); + expect(container).toHaveTextContent("Precio0100"); + expect(container).toHaveTextContent("Selecciona el tipo de roca"); + + +}); diff --git a/webapp/src/components/tests/Showcase.test.tsx b/webapp/src/components/tests/Showcase.test.tsx new file mode 100644 index 0000000..18cf37d --- /dev/null +++ b/webapp/src/components/tests/Showcase.test.tsx @@ -0,0 +1,35 @@ +import { render } from "@testing-library/react"; +import Showcase, { NUMBER_OF_PRODUCTS_SHOWN } from "../Showcase"; +import { Rock } from "../../shared/shareddtypes"; +import { LIST_OF_ROCKS_TEST } from "./code/shared"; + + +test("Check that the showcases render properly", async () => { + const { container } = render( + + ); + + for (let index = 0; index < NUMBER_OF_PRODUCTS_SHOWN; index++) { + var rock = LIST_OF_ROCKS_TEST[index]; + expect(container).toHaveTextContent("Nombre: "+rock.name.toString()); + expect(container).toHaveTextContent("Mohs: "+rock.mohsHardness.toString()); + expect(container).toHaveTextContent("Precio: "+rock.price.toString()); + expect(container).toHaveTextContent("Tipo: "+rock.type.toString()[0].toUpperCase()+rock.type.toString().substring(1,rock.type.toString().length)); + } +}); diff --git a/webapp/src/components/tests/Showcases.test.tsx b/webapp/src/components/tests/Showcases.test.tsx index 5707314..e745d48 100644 --- a/webapp/src/components/tests/Showcases.test.tsx +++ b/webapp/src/components/tests/Showcases.test.tsx @@ -1,25 +1,19 @@ -import React from 'react' -import {getByText, render,screen } from "@testing-library/react"; -import Showcase from '../Showcase'; +import {render } from "@testing-library/react"; import { Rock } from '../../shared/shareddtypes'; +import Showcases from '../Showcases'; +import {LIST_OF_NAMES} from '../Showcases' test('Check that the showcases render properly', async () => { - const { container } = render(); - expect(container).toHaveTextContent(/testtest/) - expect(container).toHaveTextContent(/122/) - expect(container).toHaveTextContent(/densidad/) - expect(container).toHaveTextContent(/asdf/) + for (let index = 0; index < LIST_OF_NAMES.length; index++) { + var type=LIST_OF_NAMES[index] + if(type===undefined) + type='' + expect(container).toHaveTextContent(type) + } + }); diff --git a/webapp/src/components/tests/code/shared.tsx b/webapp/src/components/tests/code/shared.tsx new file mode 100644 index 0000000..2f0c0ef --- /dev/null +++ b/webapp/src/components/tests/code/shared.tsx @@ -0,0 +1,84 @@ +import { Rock } from "../../../shared/shareddtypes"; +import { Order } from "../../../shared/shareddtypes" + +export const LIST_OF_ROCKS_TEST : Rock[]= [ + { + id: 0, + name: "test0", + img: "test0img", + price: 0, + mohsHardness: 0, + density: 0, + type: "test0type", + quantityCart: 0, + }, + { + id: 1, + name: "test1", + img: "test1img", + price: 1, + mohsHardness: 1, + density: 1, + type: "test1type", + quantityCart: 1, + }, + { + id: 2, + name: "test2", + img: "test2img", + price: 2, + mohsHardness: 2, + density: 2, + type: "test2type", + quantityCart: 2, + }, + { + id: 3, + name: "test3", + img: "test3img", + price: 3, + mohsHardness: 3, + density: 3, + type: "test3type", + quantityCart: 3, + }, + ]; + +export const LIST_OF_ORDERS_TEST : Order[]= [ + { + orderId : "123", + date: new Date("2020-12-12"), + price : 150, + productId: "1", + userEmail : "admin@gmail.com", + productName : "Granito", + productType : "magmática" + }, + { + orderId : "1234", + date: new Date("2021-12-12"), + price : 180, + productId: "12", + userEmail : "admin@gmail.com", + productName : "Cuarcita", + productType : "metamórfica" + }, + { + orderId : "12345", + date: new Date("2022-12-12"), + price : 190, + productId: "123", + userEmail : "admin@gmail.com", + productName : "Andesita", + productType : "ígnea" + }, + { + orderId : "123567", + date: new Date("2019-12-12"), + price : 110, + productId: "1011", + userEmail : "admin@gmail.com", + productName : "Marmol", + productType : "metamórfica" + }, +]; diff --git a/webapp/src/components/tests/user/Login.test.tsx b/webapp/src/components/tests/user/Login.test.tsx new file mode 100644 index 0000000..174616d --- /dev/null +++ b/webapp/src/components/tests/user/Login.test.tsx @@ -0,0 +1,21 @@ +import { render } from '@testing-library/react'; +import { BrowserRouter as Router } from "react-router-dom"; + +import Login from '../../Login'; + +test('Check words of Login', async () => { + + const { container } = render( + + + + ) + + expect(container).toHaveTextContent("Entrar en Sesión") + expect(container).toHaveTextContent("email") + expect(container).toHaveTextContent("password") + expect(container).toHaveTextContent("Iniciar Sesión") + expect(container).toHaveTextContent("¿Aún no estás registrado? Regístrate aqui!") + + +}); \ No newline at end of file diff --git a/webapp/src/components/tests/user/Orders.test.tsx b/webapp/src/components/tests/user/Orders.test.tsx new file mode 100644 index 0000000..06499f5 --- /dev/null +++ b/webapp/src/components/tests/user/Orders.test.tsx @@ -0,0 +1,44 @@ +import Orders from '../../Orders'; +import { render } from '@testing-library/react'; +import { BrowserRouter as Router } from "react-router-dom"; +import { LIST_OF_ORDERS_TEST } from "../code/shared"; + +test('Check basic content of user Orders', async () => { + + const { container } = render( + + + + ) + + expect(container).toHaveTextContent("Nombre") + expect(container).toHaveTextContent("Tipo") + expect(container).toHaveTextContent("Fecha") + expect(container).toHaveTextContent("Precio") + +}); + +test('Check user orders', async () => { + + const { container } = render( + + + + ) + + expect(container).toHaveTextContent("Nombre") + expect(container).toHaveTextContent("Tipo") + expect(container).toHaveTextContent("Fecha") + expect(container).toHaveTextContent("Precio") + + for (let index = 0; index < LIST_OF_ORDERS_TEST.length; index++) { + var order = LIST_OF_ORDERS_TEST[index]; + expect(container).toHaveTextContent(order.productName.toString()); + expect(container).toHaveTextContent(order.productType.toString()); + /* expect(container).toHaveTextContent(order.date.getDay().toString());*/ + expect(container).toHaveTextContent(order.price.toString()); + } + +}); \ No newline at end of file diff --git a/webapp/src/components/tests/user/Register.test.tsx b/webapp/src/components/tests/user/Register.test.tsx new file mode 100644 index 0000000..ee10844 --- /dev/null +++ b/webapp/src/components/tests/user/Register.test.tsx @@ -0,0 +1,24 @@ +import { render } from '@testing-library/react'; +import { Route, Routes, Navigate, BrowserRouter as Router } from "react-router-dom"; + +import Register from '../../Register'; + +test('Check words of Register', async () => { + + const { container } = render( + + + + ) + + expect(container).toHaveTextContent("Crear cuenta") + expect(container).toHaveTextContent("Email:") + expect(container).toHaveTextContent("Name:") + expect(container).toHaveTextContent("DNI:") + expect(container).toHaveTextContent("Password:") + expect(container).toHaveTextContent("Confirm Password:") + expect(container).toHaveTextContent("Regístrate") + expect(container).toHaveTextContent("¿Ya tienes una cuenta? Inicia sesión aqui!") + + +}); \ No newline at end of file diff --git a/webapp/src/css/App.css b/webapp/src/css/App.css index 5095fd6..cfed196 100644 --- a/webapp/src/css/App.css +++ b/webapp/src/css/App.css @@ -1,12 +1,12 @@ .principal { margin-top: 7em; - background-color: rgb(230, 237, 241); + background-color: rgb(241, 236, 230); } body { - background-color: #ced9ee; + } header { diff --git a/webapp/src/css/Catalog.css b/webapp/src/css/Catalog.css index d7585c4..7cfda28 100644 --- a/webapp/src/css/Catalog.css +++ b/webapp/src/css/Catalog.css @@ -3,6 +3,15 @@ display: grid; grid-template-columns: repeat(4,1fr); padding-bottom: 5em; - + gap: 1em; + row-gap: 3em; } +#catalogFilter{ + padding: 2em ; +} +#accordeonFilter{ + margin-top: 5em !important; + top: 1em; + margin-bottom: 3em; +} \ No newline at end of file diff --git a/webapp/src/css/Product.css b/webapp/src/css/Product.css index 13a815b..ac010d0 100644 --- a/webapp/src/css/Product.css +++ b/webapp/src/css/Product.css @@ -1,49 +1,35 @@ .product { padding: 1em; - margin-bottom: 3em; text-align: center; height: 100%; + } -.infoProduct { - display: flex; - margin-top: 1em; - flex-wrap: wrap; - gap: 0.3em; -} -.imageProductContainer { - height: 50%; - display: flex; - justify-content: center; - align-items: center; - border-radius: 1em; - +.btnBuy{ + position: relative; + top:3em !important; } -.imageProductContainer img{ - border-radius: 1em; - margin: 1em; +.productDataText{ + + position: relative; + bottom: 100%; } -.infoProduct .datoProduct:first-of-type{ - width: 100%; -} - -.datoProduct { - background-color: #ced9ee; - padding: 0.5em; - border-radius: 0.3em; - border: rgb(230, 237, 241) 0.15em solid; - height: 80%; - width: 43%; +.productDataContainer{ - display: flex; /* To make the text inside center vertically*/ - justify-content: center; - align-items: center; - margin: 0; + height: 100%; + padding-top: 1em; + } -.btnBuy{ - position: relative; - top:2em !important; +@media only screen and (max-width: 1000px) { + .productDataText{ + margin-top: -1.5em !important; + } +} +@media only screen and (max-width: 800px) { + .productDataText{ + margin-top: -1em !important; + } } \ No newline at end of file diff --git a/webapp/src/css/Showcases.css b/webapp/src/css/Showcases.css index 4d52d27..62ba719 100644 --- a/webapp/src/css/Showcases.css +++ b/webapp/src/css/Showcases.css @@ -3,6 +3,9 @@ background-color: #d8e3f7; border-radius: 1em; display: grid; + padding: 1em; + gap: 1em; + row-gap: 3em; grid-template-columns: repeat(2,1fr); grid-template-rows: repeat(2,1fr); position: relative; @@ -11,11 +14,8 @@ } #showcases { max-width: max-content; - border-radius: 1em; - gap: 1em; - padding: 1em; display: grid; grid-template-columns: repeat(2, 1fr); @@ -27,9 +27,8 @@ font-size: 1.5em; height: 3em; - position: relative; - top: 4em; } + @media only screen and (max-width: 1000px) { .btnBuy{ font-size: 12px !important; diff --git a/webapp/src/shared/shareddtypes.ts b/webapp/src/shared/shareddtypes.ts index bf42f25..fd7ef16 100644 --- a/webapp/src/shared/shareddtypes.ts +++ b/webapp/src/shared/shareddtypes.ts @@ -8,14 +8,13 @@ export type User = { }; export type Rock = { id:React.Key; + rockId:string; name: string; img: string; price: number; mohsHardness:number; - density:string; + density:number; type:string; - - quantityCart:number } export type Order = {