Refactor and reformat frontend

This commit is contained in:
Santiago Lo Coco 2023-10-30 19:45:01 -03:00
parent 51db72ed54
commit c15455b008
36 changed files with 1071 additions and 1269 deletions

View File

@ -2,55 +2,55 @@ import { Axios, AxiosError } from "axios";
import { Credentials, Token, User, Flight, FlightCreate } from "./Types"; import { Credentials, Token, User, Flight, FlightCreate } from "./Types";
const instance = new Axios({ const instance = new Axios({
baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/", baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/",
headers: { headers: {
accept: "application/json", accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
validateStatus: (x) => { return !(x < 200 || x > 204) } validateStatus: (x) => { return !(x < 200 || x > 204) }
}); });
instance.interceptors.request.use((request) => { instance.interceptors.request.use((request) => {
request.data = JSON.stringify(request.data); request.data = JSON.stringify(request.data);
return request; return request;
}); });
instance.interceptors.response.use( instance.interceptors.response.use(
(response) => { (response) => {
return JSON.parse(response.data); return JSON.parse(response.data);
}, },
(error) => { (error) => {
const err = error as AxiosError; const err = error as AxiosError;
return Promise.reject(err); return Promise.reject(err);
} }
); );
export const createUser = ( export const createUser = (
credentials: Credentials credentials: Credentials
): Promise<{ id?: string; message: string }> => { ): Promise<{ id?: string; message: string }> => {
return instance.post("users", credentials); return instance.post("users", credentials);
}; };
export const fetchUsers = (): Promise<User[]> => { export const fetchUsers = (): Promise<User[]> => {
return instance.get("users"); return instance.get("users");
}; };
export const fetchUserById = (id: number): Promise<User> => { export const fetchUserById = (id: number): Promise<User> => {
return instance.get("users/" + id); return instance.get("users/" + id);
}; };
export const logIn = ( export const logIn = (
credentials: Credentials credentials: Credentials
): Promise<Token & Partial<{ message: string; user_id: number }>> => { ): Promise<Token & Partial<{ message: string; user_id: number }>> => {
return instance.post("auth/login", credentials); return instance.post("auth/login", credentials);
}; };
export const tokenStatus = ( export const tokenStatus = (
token: string token: string
): Promise<User & { message?: string }> => { ): Promise<User & { message?: string }> => {
return instance.get("auth/status", { return instance.get("auth/status", {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
}; };
export const fetchZones = (origin: string | null): Promise<Flight[]> => { export const fetchZones = (origin: string | null): Promise<Flight[]> => {
@ -58,10 +58,10 @@ export const fetchZones = (origin: string | null): Promise<Flight[]> => {
}; };
export const createFlight = ( export const createFlight = (
flight_data: FlightCreate, flight_data: FlightCreate,
token: string token: string
): Promise<Flight> => { ): Promise<Flight> => {
return instance.post("flights", flight_data, { return instance.post("flights", flight_data, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
}; };

View File

@ -5,18 +5,18 @@ import { Home } from "./components/Home/Home";
import { CreateFlight } from "./components/CreateFlight/CreateFlight"; import { CreateFlight } from "./components/CreateFlight/CreateFlight";
import { Button } from "antd"; import { Button } from "antd";
import useAuth, { AuthProvider } from "./useAuth"; import useAuth, { AuthProvider } from "./useAuth";
function Router() { function Router() {
const { user, logout, isAirline } = useAuth(); const { user, logout, isAirline } = useAuth();
return ( return (
<div className="App"> <div className="App">
<Routes> <Routes>
<Route path="/login" element={<LogIn />} /> <Route path="/login" element={<LogIn />} />
<Route path="/signup" element={<SignUp />} /> <Route path="/signup" element={<SignUp />} />
<Route path="/home" element={!user ? <LogIn/> :<Home/>} /> <Route path="/home" element={!user ? <LogIn /> : <Home />} />
<Route path="/create-flight" element={!isAirline ? <LogIn/> :<CreateFlight/>} /> <Route path="/create-flight" element={!isAirline ? <LogIn /> : <CreateFlight />} />
<Route path="/" element={!user ? <LogIn/> :<Home/>} /> <Route path="/" element={!user ? <LogIn /> : <Home />} />
</Routes> </Routes>
<div className="LogoutButton"> <div className="LogoutButton">
{ {
@ -28,16 +28,16 @@ import useAuth, { AuthProvider } from "./useAuth";
</Button> </Button>
} }
</div> </div>
</div> </div>
); );
} }
function App() { function App() {
return ( return (
<AuthProvider> <AuthProvider>
<Router/> <Router />
</AuthProvider> </AuthProvider>
); );
} }
export default App; export default App;

View File

@ -1,29 +1,29 @@
export interface Credentials { export interface Credentials {
password: string; password: string;
email: string; email: string;
username?: string; username?: string;
} }
export interface Token { export interface Token {
refresh_token: string; refresh_token: string;
access_token: string; access_token: string;
} }
export interface TokenData { export interface TokenData {
sub: string; sub: string;
airline: boolean; airline: boolean;
} }
export interface User { export interface User {
id: number; id: number;
username: string; username: string;
email: string; email: string;
created_date?: Date; created_date?: Date;
} }
export interface Zone { export interface Zone {
id: number; id: number;
name: string; name: string;
} }
export interface Flight { export interface Flight {
@ -35,9 +35,9 @@ export interface Flight {
departure_time: string; departure_time: string;
arrival_time: string; arrival_time: string;
gate: string; gate: string;
} }
export interface FlightCreate { export interface FlightCreate {
flight_code: string; flight_code: string;
status: string; status: string;
origin: string; origin: string;
@ -45,4 +45,4 @@ export interface Flight {
departure_time: string; departure_time: string;
arrival_time: string; arrival_time: string;
gate: string; gate: string;
} }

View File

@ -5,13 +5,13 @@ import { render, screen } from "@testing-library/react";
import { Button } from "antd"; import { Button } from "antd";
describe("Button Component Test", () => { describe("Button Component Test", () => {
test("Display button label and clicked", async () => { test("Display button label and clicked", async () => {
const onClick = jest.fn(); const onClick = jest.fn();
render(<Button onClick={() => onClick()}>Button</Button>); render(<Button onClick={() => onClick()}>Button</Button>);
expect(screen.getByText("Button")).toBeVisible(); expect(screen.getByText("Button")).toBeVisible();
await userEvent.click(screen.getByText("Button")); await userEvent.click(screen.getByText("Button"));
expect(onClick).toBeCalled(); expect(onClick).toBeCalled();
}); });
}); });

View File

@ -5,11 +5,9 @@ import "./FlightForm.css";
import { createFlight } from "../../Api"; import { createFlight } from "../../Api";
export const CreateFlight = () => { export const CreateFlight = () => {
const urlParams = new URLSearchParams(window.location.search);
const origin = urlParams.get('origin');
const navigate = useNavigate(); const navigate = useNavigate();
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [flight, setFlight] = useState<Flight>(); const [flight, setFlight] = useState<Flight>();
const [flightData, setFlightData] = useState<FlightCreate>({ const [flightData, setFlightData] = useState<FlightCreate>({
flight_code: "ABC123", flight_code: "ABC123",
@ -19,9 +17,9 @@ export const CreateFlight = () => {
departure_time: "2023-10-09 10:00 AM", departure_time: "2023-10-09 10:00 AM",
arrival_time: "2023-10-09 12:00 PM", arrival_time: "2023-10-09 12:00 PM",
gate: "A1", gate: "A1",
}); });
const handleSubmit = async (event: React.FormEvent) => { const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault(); event.preventDefault();
setError(null); setError(null);
@ -40,79 +38,79 @@ export const CreateFlight = () => {
.catch((error) => { .catch((error) => {
setError(error as string); setError(error as string);
}); });
}; };
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<label> <label>
Flight Code: Flight Code:
<input <input
type="text" type="text"
value={flightData.flight_code} value={flightData.flight_code}
onChange={(e) => onChange={(e) =>
setFlightData({ ...flightData, flight_code: e.target.value }) setFlightData({ ...flightData, flight_code: e.target.value })
} }
/> />
</label> </label>
<label> <label>
Status: Status:
<input <input
type="text" type="text"
value={flightData.status} value={flightData.status}
onChange={(e) => onChange={(e) =>
setFlightData({ ...flightData, status: e.target.value }) setFlightData({ ...flightData, status: e.target.value })
} }
/> />
</label> </label>
<label> <label>
Origin: Origin:
<input <input
type="text" type="text"
value={flightData.origin} value={flightData.origin}
onChange={(e) => onChange={(e) =>
setFlightData({ ...flightData, origin: e.target.value }) setFlightData({ ...flightData, origin: e.target.value })
} }
/> />
</label> </label>
<label> <label>
Destination: Destination:
<input <input
type="text" type="text"
value={flightData.destination} value={flightData.destination}
onChange={(e) => onChange={(e) =>
setFlightData({ ...flightData, destination: e.target.value }) setFlightData({ ...flightData, destination: e.target.value })
} }
/> />
</label> </label>
<label> <label>
Departure Time: Departure Time:
<input <input
type="text" type="text"
value={flightData.departure_time} value={flightData.departure_time}
onChange={(e) => onChange={(e) =>
setFlightData({ ...flightData, departure_time: e.target.value }) setFlightData({ ...flightData, departure_time: e.target.value })
} }
/> />
</label> </label>
<label> <label>
Arrival Time: Arrival Time:
<input <input
type="text" type="text"
value={flightData.arrival_time} value={flightData.arrival_time}
onChange={(e) => onChange={(e) =>
setFlightData({ ...flightData, arrival_time: e.target.value }) setFlightData({ ...flightData, arrival_time: e.target.value })
} }
/> />
</label> </label>
<label> <label>
Gate: Gate:
<input <input
type="text" type="text"
value={flightData.gate} value={flightData.gate}
onChange={(e) => setFlightData({ ...flightData, gate: e.target.value })} onChange={(e) => setFlightData({ ...flightData, gate: e.target.value })}
/> />
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
); );
}; };

View File

@ -5,26 +5,26 @@
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
} }
label { label {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 10px;
} }
input { input {
width: 100%; width: 100%;
padding: 8px; padding: 8px;
margin-top: 4px; margin-top: 4px;
margin-bottom: 10px; margin-bottom: 10px;
box-sizing: border-box; box-sizing: border-box;
} }
button { button {
background-color: #4caf50; background-color: #4caf50;
color: white; color: white;
padding: 10px 15px; padding: 10px 15px;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
} }

View File

@ -5,10 +5,10 @@ import "../../../matchMedia.mock";
import { Card } from "./Card"; import { Card } from "./Card";
describe("Card Component Test", () => { describe("Card Component Test", () => {
test("Display initial, name and icon", async () => { test("Display initial, name and icon", async () => {
// render(<Card name="Belgrano" />); // render(<Card name="Belgrano" />);
// expect(screen.getByText("Belgrano📍")).toBeVisible(); // expect(screen.getByText("Belgrano📍")).toBeVisible();
// expect(screen.getByText("B")).toBeVisible(); // expect(screen.getByText("B")).toBeVisible();
}); });
}); });

View File

@ -5,59 +5,59 @@ import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined,
import "./Card.css"; import "./Card.css";
interface FlightProps { interface FlightProps {
flight_code: string; flight_code: string;
status: string; status: string;
origin: string; origin: string;
destination: string; destination: string;
departure_time: string; departure_time: string;
arrival_time: string; arrival_time: string;
gate: string; gate: string;
} }
interface CardProps { interface CardProps {
flight: FlightProps; flight: FlightProps;
} }
const { Text } = Typography; const { Text } = Typography;
export const Card: React.FC<CardProps> = ({ flight }) => { export const Card: React.FC<CardProps> = ({ flight }) => {
return ( return (
<div className="flight-card"> <div className="flight-card">
<Space size={8} align="center"> <Space size={8} align="center">
<Avatar size={64} icon={<RightOutlined />} /> <Avatar size={64} icon={<RightOutlined />} />
<div> <div>
<Text strong>{flight.flight_code}</Text> <Text strong>{flight.flight_code}</Text>
<div> <div>
<Text type="secondary"> <Text type="secondary">
{flight.origin} <SwapOutlined /> {flight.destination} {flight.origin} <SwapOutlined /> {flight.destination}
</Text> </Text>
</div> </div>
</div>
</Space>
<div className="flight-details">
<Space size={8} direction="vertical">
<Text strong>Status:</Text>
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
</Space>
<Space size={8} direction="vertical">
<Text strong>Departure:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.departure_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Arrival:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.arrival_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Gate:</Text>
<Text>{flight.gate}</Text>
</Space>
</div>
</div> </div>
</Space> );
<div className="flight-details">
<Space size={8} direction="vertical">
<Text strong>Status:</Text>
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
</Space>
<Space size={8} direction="vertical">
<Text strong>Departure:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.departure_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Arrival:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.arrival_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Gate:</Text>
<Text>{flight.gate}</Text>
</Space>
</div>
</div>
);
}; };

View File

@ -1,8 +1,8 @@
const mockedUsedNavigate = jest.fn(); const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({ jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"), ...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate, useNavigate: () => mockedUsedNavigate,
})); }));
import "../../matchMedia.mock"; import "../../matchMedia.mock";
@ -11,18 +11,18 @@ import { render, screen } from "@testing-library/react";
import { Home } from "./Home"; import { Home } from "./Home";
describe("Home View Test", () => { describe("Home View Test", () => {
test("Display initial, name and icon", async () => { test("Display initial, name and icon", async () => {
// render( // render(
// <Home // <Home
// zones={[ // zones={[
// { id: 1, name: "Belgrano" }, // { id: 1, name: "Belgrano" },
// { id: 2, name: "San Isidro" }, // { id: 2, name: "San Isidro" },
// ]} // ]}
// /> // />
// ); // );
// expect(screen.getByText("Zones")).toBeVisible(); // expect(screen.getByText("Zones")).toBeVisible();
// expect(screen.getByText("Belgrano📍")).toBeVisible(); // expect(screen.getByText("Belgrano📍")).toBeVisible();
// expect(screen.getByText("San Isidro📍")).toBeVisible(); // expect(screen.getByText("San Isidro📍")).toBeVisible();
}); });
}); });

View File

@ -6,13 +6,13 @@ import { useNavigate } from "react-router";
import useAuth from "../../useAuth"; import useAuth from "../../useAuth";
interface Props { interface Props {
flights?: Flight[]; flights?: Flight[];
} }
export const Home: React.FC<Props> = (props) => { export const Home: React.FC<Props> = (props) => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const origin = urlParams.get('origin'); const origin = urlParams.get('origin');
const { zones, error } = useFetchZones(origin); const { zones, error } = useFetchZones(origin);
const navigate = useNavigate() const navigate = useNavigate()
const { loading, isAirline } = useAuth(); const { loading, isAirline } = useAuth();
@ -21,16 +21,16 @@ export const Home: React.FC<Props> = (props) => {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
return ( return (
<div className="Box"> <div className="Box">
{isAirline ? <button onClick={() => { navigate("/create-flight") }}>CREATE FLIGHT</button> : <></>} {isAirline ? <button onClick={() => { navigate("/create-flight") }}>CREATE FLIGHT</button> : <></>}
<h2>Flights</h2> <h2>Flights</h2>
<div className="Items"> <div className="Items">
{(props.flights ? props.flights : zones).map((u) => { {(props.flights ? props.flights : zones).map((u) => {
return <Card key={u.id} flight={u} />; return <Card key={u.id} flight={u} />;
})} })}
{error ? <div className="Disconnected">{error}</div> : <></>} {error ? <div className="Disconnected">{error}</div> : <></>}
</div> </div>
</div> </div>
); );
}; };

View File

@ -4,42 +4,37 @@ import useAuth from "../../useAuth";
export const LogIn = () => { export const LogIn = () => {
const { login, loading, error } = useAuth(); const { login, loading, error } = useAuth();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
return ( return (
<div className="Box Small"> <div className="Box Small">
<div className="Section"> <div className="Section">
<img <div className="Section">
alt="logo" <Input
className="Image" placeholder="User"
src="https://www.seekpng.com/png/full/353-3537757_logo-itba.png" onChange={(ev) => setEmail(ev.target.value)}
/> />
<div className="Section"> <Input.Password
<Input placeholder="Password"
placeholder="User" onChange={(ev) => setPassword(ev.target.value)}
onChange={(ev) => setEmail(ev.target.value)} />
/> <Button
<Input.Password style={{ width: "100%" }}
placeholder="Password" onClick={async () =>
onChange={(ev) => setPassword(ev.target.value)} login({ email, password })
/> }
<Button loading={loading}
style={{ width: "100%" }} >
onClick={async () => Log in
login({email, password}) </Button>
} {error ? (
loading={loading} <div className="Disconnected">{error}</div>
> ) : (
Log in <></>
</Button> )}
{error ? ( </div>
<div className="Disconnected">{error}</div> </div>
) : ( </div>
<></> );
)}
</div>
</div>
</div>
);
}; };

View File

@ -3,61 +3,56 @@ import { Button, Input } from "antd";
import { useCreateUser } from "../../hooks/useCreateUser"; import { useCreateUser } from "../../hooks/useCreateUser";
export const SignUp = () => { export const SignUp = () => {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [repeatPassword, setRepeatPassword] = useState(""); const [repeatPassword, setRepeatPassword] = useState("");
const { createUser, isLoading, error } = useCreateUser(); const { createUser, isLoading, error } = useCreateUser();
return ( return (
<div className="Box Small"> <div className="Box Small">
<div className="Section"> <div className="Section">
<img <div className="Section">
alt="logo" <Input
className="Image" type="email"
src="https://www.seekpng.com/png/full/353-3537757_logo-itba.png" placeholder="Email"
/> onChange={(ev) => setEmail(ev.target.value)}
<div className="Section"> />
<Input <Input
type="email" placeholder="Username"
placeholder="Email" onChange={(ev) => setUsername(ev.target.value)}
onChange={(ev) => setEmail(ev.target.value)} />
/> <Input.Password
<Input placeholder="Password"
placeholder="Username" onChange={(ev) => setPassword(ev.target.value)}
onChange={(ev) => setUsername(ev.target.value)} />
/> <Input.Password
<Input.Password placeholder="Repeat password"
placeholder="Password" onChange={(ev) => setRepeatPassword(ev.target.value)}
onChange={(ev) => setPassword(ev.target.value)} />
/> <Button
<Input.Password style={{ width: "100%" }}
placeholder="Repeat password" onClick={async () =>
onChange={(ev) => setRepeatPassword(ev.target.value)} await createUser({ email, password, username })
/> }
<Button loading={isLoading}
style={{ width: "100%" }} disabled={
onClick={async () => email === "" ||
await createUser({ email, password, username }) username === "" ||
} password === "" ||
loading={isLoading} password !== repeatPassword
disabled={ }
email === "" || >
username === "" || Sign up
password === "" || </Button>
password !== repeatPassword {error ? (
} <div className="Disconnected">{error}</div>
> ) : (
Sign up <></>
</Button> )}
{error ? ( </div>
<div className="Disconnected">{error}</div> </div>
) : ( </div>
<></> );
)}
</div>
</div>
</div>
);
}; };

View File

@ -4,11 +4,11 @@ import { User, Flight, FlightCreate } from "../Types";
import { createFlight } from "../Api"; import { createFlight } from "../Api";
export const useCreateFlight = (flight_data: FlightCreate) => { export const useCreateFlight = (flight_data: FlightCreate) => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [flight, setFlight] = useState<Flight>(); const [flight, setFlight] = useState<Flight>();
useEffect(() => { useEffect(() => {
setError(null); setError(null);
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (!token) { if (!token) {
@ -16,14 +16,14 @@ export const useCreateFlight = (flight_data: FlightCreate) => {
return; return;
} }
createFlight(flight_data, token) createFlight(flight_data, token)
.then((data) => { .then((data) => {
setFlight(data); setFlight(data);
}) })
.catch((error) => { .catch((error) => {
setError(error as string); setError(error as string);
}); });
}, []); }, []);
return { flight, error }; return { flight, error };
}; };

View File

@ -4,28 +4,28 @@ import { createUser as createUserAPI } from "../Api";
import useAuth from "../useAuth"; import useAuth from "../useAuth";
export const useCreateUser = () => { export const useCreateUser = () => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const { login } = useAuth(); const { login } = useAuth();
const createUser = async (credentials: Credentials): Promise<void> => { const createUser = async (credentials: Credentials): Promise<void> => {
try { try {
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
const createResponse = await createUserAPI(credentials); const createResponse = await createUserAPI(credentials);
if (createResponse.id) { if (createResponse.id) {
login(credentials); login(credentials);
} else { } else {
setError(createResponse.message); setError(createResponse.message);
} }
} catch (error) { } catch (error) {
setError(error as string); setError(error as string);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
return { createUser, isLoading, error }; return { createUser, isLoading, error };
}; };

View File

@ -4,18 +4,18 @@ import { User, Flight } from "../Types";
import { fetchZones } from "../Api"; import { fetchZones } from "../Api";
export const useFetchZones = (origin: string | null) => { export const useFetchZones = (origin: string | null) => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [zones, setZones] = useState<Flight[]>([]); const [zones, setZones] = useState<Flight[]>([]);
useEffect(() => { useEffect(() => {
setError(null); setError(null);
fetchZones(origin) fetchZones(origin)
.then((data) => { .then((data) => {
setZones(data); setZones(data);
}) })
.catch((error) => {}); .catch((error) => { });
}, []); }, []);
return { zones, error }; return { zones, error };
}; };

View File

@ -1,106 +1,106 @@
body { body {
margin: 0; margin: 0;
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif; "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;
} }
.App { .App {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: #eff2f7; background-color: #eff2f7;
} }
.Box { .Box {
border-radius: 20px; border-radius: 20px;
box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2); box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2);
padding: 50px; padding: 50px;
gap: 30px; gap: 30px;
background-color: white; background-color: white;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
.Small { .Small {
width: 250px; width: 250px;
height: 400px; height: 400px;
} }
.Section { .Section {
flex: 1; flex: 1;
width: 100%; width: 100%;
padding: 30px 50px; padding: 30px 50px;
gap: 30px; gap: 30px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
.Image { .Image {
width: 150px; width: 150px;
} }
.Connected { .Connected {
color: green; color: green;
} }
.Disconnected { .Disconnected {
color: red; color: red;
} }
.FloatingStatus { .FloatingStatus {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 50px; right: 50px;
} }
.LogoutButton { .LogoutButton {
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
right: 50px; right: 50px;
} }
.Card { .Card {
border-radius: 8px; border-radius: 8px;
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2); box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2);
gap: 10px; gap: 10px;
padding: 10px; padding: 10px;
width: 100%; width: 100%;
background-color: white; background-color: white;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.Items { .Items {
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
} }
.List { .List {
width: 100%; width: 100%;
height: 500px; height: 500px;
gap: 30px; gap: 30px;
padding: 20px; padding: 20px;
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }

View File

@ -1,10 +1,10 @@
import React, {createContext, ReactNode, useContext, useEffect, useMemo, useState} from "react"; import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { Credentials, TokenData, User } from "./Types"; import { Credentials, TokenData, User } from "./Types";
import { fetchUserById, logIn, tokenStatus } from "./Api"; import { fetchUserById, logIn, tokenStatus } from "./Api";
import jwt_decode from "jwt-decode"; import jwt_decode from "jwt-decode";
interface AuthContextType { interface AuthContextType {
user?: User; user?: User;
loading: boolean; loading: boolean;
isAirline: boolean; isAirline: boolean;
@ -12,28 +12,28 @@ import jwt_decode from "jwt-decode";
login: (credentials: Credentials) => void; login: (credentials: Credentials) => void;
signUp: (email: string, name: string, password: string) => void; signUp: (email: string, name: string, password: string) => void;
logout: () => void; logout: () => void;
} }
const AuthContext = createContext<AuthContextType>( const AuthContext = createContext<AuthContextType>(
{} as AuthContextType {} as AuthContextType
); );
export function AuthProvider({ export function AuthProvider({
children, children,
}: { }: {
children: ReactNode; children: ReactNode;
}): JSX.Element { }): JSX.Element {
const [user, setUser] = useState<User>(); const [user, setUser] = useState<User>();
const [error, setError] = useState<any>(); const [error, setError] = useState<any>();
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [loadingInitial, setLoadingInitial] = useState<boolean>(true); const [loadingInitial, setLoadingInitial] = useState<boolean>(true);
const [isAirline, setIsAirline] = useState(false); const [isAirline, setIsAirline] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (error) setError(undefined); if (error) setError(undefined);
}, [window.location.pathname]); }, [window.location.pathname]);
useEffect(() => { useEffect(() => {
const existingToken = localStorage.getItem("token"); const existingToken = localStorage.getItem("token");
if (existingToken) { if (existingToken) {
@ -48,68 +48,68 @@ import jwt_decode from "jwt-decode";
} }
tokenStatus(existingToken) tokenStatus(existingToken)
.then((res) => fetchUserById(res.id) .then((res) => fetchUserById(res.id)
.then((res) => setUser(res)) .then((res) => setUser(res))
.catch((_error) => {}) .catch((_error) => { })
.finally(() => setLoadingInitial(false)) .finally(() => setLoadingInitial(false))
) )
.catch((_error) => { .catch((_error) => {
setLoadingInitial(false) setLoadingInitial(false)
logout() logout()
}) })
// .finally(() => setLoadingInitial(false)); // .finally(() => setLoadingInitial(false));
} else { } else {
setLoadingInitial(false) setLoadingInitial(false)
} }
}, []); }, []);
function login(credentials: Credentials) { function login(credentials: Credentials) {
setLoading(true); setLoading(true);
const tokens = logIn(credentials) const tokens = logIn(credentials)
.then((x) => { .then((x) => {
localStorage.setItem("token", x.access_token); localStorage.setItem("token", x.access_token);
const airline = (jwt_decode(x.access_token) as TokenData).airline; const airline = (jwt_decode(x.access_token) as TokenData).airline;
setIsAirline(airline) setIsAirline(airline)
const user = fetchUserById(x.user_id as number) const user = fetchUserById(x.user_id as number)
.then(y => { .then(y => {
setUser(y); setUser(y);
navigate("/home") navigate("/home")
})
.catch((error) => setError(error))
.finally(() => setLoading(false));
}) })
.catch((error) => setError(error)) .catch((error) => setError(error))
.finally(() => setLoading(false));
})
.catch((error) => setError(error))
// .finally(() => setLoading(false)); // .finally(() => setLoading(false));
} }
function signUp(email: string, name: string, password: string) {} function signUp(email: string, name: string, password: string) { }
function logout() { function logout() {
localStorage.removeItem("token"); localStorage.removeItem("token");
setUser(undefined); setUser(undefined);
navigate("/login") navigate("/login")
} }
const memoedValue = useMemo( const memoedValue = useMemo(
() => ({ () => ({
user, user,
loading, loading,
isAirline, isAirline,
error, error,
login, login,
signUp, signUp,
logout, logout,
}), }),
[user, isAirline, loading, error] [user, isAirline, loading, error]
); );
return ( return (
<AuthContext.Provider value={memoedValue}> <AuthContext.Provider value={memoedValue}>
{!loadingInitial && children} {!loadingInitial && children}
</AuthContext.Provider> </AuthContext.Provider>
); );
} }
export default function useAuth() { export default function useAuth() {
return useContext(AuthContext); return useContext(AuthContext);
} }

View File

@ -1,8 +1,3 @@
#!/bin/bash #!/bin/bash
curl -X DELETE api:5000/ping npm run test
curl -X POST api:5000/ping
# npm test
echo "NPM TEST"

View File

@ -1,37 +1,37 @@
import { Axios, AxiosError } from "axios"; import { Axios, AxiosError } from "axios";
import { Credentials, User, Flight } from "./Types"; import { Flight } from "./Types";
const instance = new Axios({ const instance = new Axios({
baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/", baseURL: process.env.REACT_APP_ENDPOINT ? process.env.REACT_APP_ENDPOINT : "http://127.0.0.1:5000/",
headers: { headers: {
accept: "application/json", accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
validateStatus: (x) => { return !(x < 200 || x > 204) } validateStatus: (x) => { return !(x < 200 || x > 204) }
}); });
instance.interceptors.response.use( instance.interceptors.response.use(
(response) => { (response) => {
return JSON.parse(response.data); return JSON.parse(response.data);
}, },
(error) => { (error) => {
const err = error as AxiosError; const err = error as AxiosError;
return Promise.reject(err); return Promise.reject(err);
} }
); );
instance.interceptors.request.use((request) => { instance.interceptors.request.use((request) => {
request.data = JSON.stringify(request.data); request.data = JSON.stringify(request.data);
return request; return request;
}); });
export const ping = () => { export const ping = () => {
return instance.get("health"); return instance.get("health");
}; };
export const fetchZones = (origin: string | undefined, destination: string | undefined, lastUpdate: string | null): Promise<Flight[]> => { export const fetchZones = (origin: string | undefined, destination: string | undefined, lastUpdate: string | null): Promise<Flight[]> => {
return instance.get("flights" + return instance.get("flights" +
(origin ? "?origin=" + origin : "") + (origin ? "?origin=" + origin : "") +
(destination ? "?destination=" + destination : "") + (destination ? "?destination=" + destination : "") +
(lastUpdate ? ( origin ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : "")) (lastUpdate ? (origin ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : ""))
}; };

View File

@ -1,26 +1,22 @@
import React, { useEffect } from "react";
import { useIsConnected } from "./hooks/useIsConnected"; import { useIsConnected } from "./hooks/useIsConnected";
import { Route, Routes } from "react-router"; import { Route, Routes } from "react-router";
import { Departure } from "./components/Home/Departure"; import { Departure } from "./components/Home/Departure";
import { Arrival } from "./components/Home/Arrival"; import { Arrival } from "./components/Home/Arrival";
import { Home } from "./components/Home/Home"; import { Home } from "./components/Home/Home";
import { Button } from "antd";
import { initDB } from "./db";
function App() { function App() {
const connection = useIsConnected(); const connection = useIsConnected();
// initDB();
return ( return (
<div className="App"> <div className="App">
<Routes> <Routes>
<Route path="/departure" element={<Departure/>} /> <Route path="/departure" element={<Departure />} />
<Route path="/arrival" element={<Arrival/>} /> <Route path="/arrival" element={<Arrival />} />
<Route path="/" element={<Home/>} /> <Route path="/" element={<Home />} />
</Routes> </Routes>
<div className="FloatingStatus">{connection}</div> <div className="FloatingStatus">{connection}</div>
</div> </div>
); );
} }
export default App; export default App;

View File

@ -1,24 +1,24 @@
export interface Credentials { export interface Credentials {
password: string; password: string;
email: string; email: string;
username?: string; username?: string;
} }
export interface Token { export interface Token {
refresh_token: string; refresh_token: string;
access_token: string; access_token: string;
} }
export interface User { export interface User {
id: number; id: number;
username: string; username: string;
email: string; email: string;
created_date?: Date; created_date?: Date;
} }
export interface Zone { export interface Zone {
id: number; id: number;
name: string; name: string;
} }
export interface Flight { export interface Flight {
@ -30,4 +30,4 @@ export interface Flight {
departure_time: string; departure_time: string;
arrival_time: string; arrival_time: string;
gate: string; gate: string;
} }

View File

@ -5,13 +5,13 @@ import { render, screen } from "@testing-library/react";
import { Button } from "antd"; import { Button } from "antd";
describe("Button Component Test", () => { describe("Button Component Test", () => {
test("Display button label and clicked", async () => { test("Display button label and clicked", async () => {
const onClick = jest.fn(); const onClick = jest.fn();
render(<Button onClick={() => onClick()}>Button</Button>); render(<Button onClick={() => onClick()}>Button</Button>);
expect(screen.getByText("Button")).toBeVisible(); expect(screen.getByText("Button")).toBeVisible();
await userEvent.click(screen.getByText("Button")); await userEvent.click(screen.getByText("Button"));
expect(onClick).toBeCalled(); expect(onClick).toBeCalled();
}); });
}); });

View File

@ -7,14 +7,13 @@ import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css';
import { useFetchDestination } from "../../hooks/useFetchDestination"; import { useFetchDestination } from "../../hooks/useFetchDestination";
interface Props { interface Props {
flights?: Flight[]; flights?: Flight[];
} }
export const Arrival: React.FC<Props> = (props) => { export const Arrival: React.FC<Props> = (props) => {
// let origin = process.env.REACT_APP_ORIGIN;
let destination = process.env.REACT_APP_ORIGIN; let destination = process.env.REACT_APP_ORIGIN;
const { zones, error } = useFetchDestination(destination); const { zones, error } = useFetchDestination(destination);
const [startIndex, setStartIndex] = useState(0); const [startIndex, setStartIndex] = useState(0);
useEffect(() => { useEffect(() => {
@ -22,77 +21,45 @@ export const Arrival: React.FC<Props> = (props) => {
if (zones.length <= 10) { if (zones.length <= 10) {
return; return;
} }
// setStartIndex((prevIndex) => (prevIndex + 10) % zones.length); setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10));
setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10));
}, 5000); }, 5000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [zones]); }, [zones]);
return ( return (
<div > <div >
<h2>Arrival</h2> <h2>Arrival</h2>
<div className="Items"> <div className="Items">
{/* {(props.flights ? props.flights : zones).map((u) => { <Table>
return <Card key={u.id} flight={u} />; <Thead>
})} */} <Tr>
{/* <table> <Th>Code</Th>
<thead> <Th>Origin</Th>
<tr> <Th>Time</Th>
<th>Flight code</th> <Th>Gate</Th>
<th>Departure</th> <Th>Status</Th>
<th>Departure time</th> </Tr>
<th>Destination</th> </Thead>
<th>Arrival time</th> <Tbody>
<th>Gate</th> {zones.length > 0 && (
<th>Status</th> <>
</tr> {zones.slice(startIndex, startIndex + 10).map((flight) => (
</thead> <Tr key={flight.id} className={flight.status === 'Delayed' ? 'delayed-flight' : ''}>
<tbody> */} <Td>{flight.flight_code}</Td>
<Table> <Td>{flight.origin}</Td>
<Thead> <Td>{flight.arrival_time}</Td>
<Tr> <Td>{flight.gate}</Td>
<Th>Code</Th> <Td>{flight.status}</Td>
{/* <Th>Departure</Th> */} </Tr>
{/* <Th>Time</Th> */} ))}
{/* <Th>Destination</Th> */} </>
<Th>Origin</Th> )}
<Th>Time</Th> </Tbody>
<Th>Gate</Th> </Table>
<Th>Status</Th> {error ? <div className="Disconnected">{error}</div> : <></>}
</Tr> </div>
</Thead> </div>
<Tbody> );
{zones.length > 0 && (
<>
{zones.slice(startIndex, startIndex + 10).map((flight) => (
// {/* {Array.from({ length: zones.length < 10 ? zones.length : 10 }).map((_, index) => {
// const flightIndex = (startIndex + index) % zones.length;
// const flight = zones[flightIndex];
// return ( */}
<Tr key={flight.id} className={flight.status === 'Delayed' ? 'delayed-flight' : ''}>
{/* <td>{flight.id}</td> */}
<Td>{flight.id}-{flight.flight_code}</Td>
<Td>{flight.origin}</Td>
{/* <Td>{flight.departure_time}</Td> */}
{/* <Td>{flight.destination}</Td> */}
<Td>{flight.arrival_time}</Td>
<Td>{flight.gate}</Td>
<Td>{flight.status}</Td>
</Tr>
// );
))}
{/* })} */}
</>
)}
</Tbody>
</Table>
{/* </tbody>
</table> */}
{error ? <div className="Disconnected">{error}</div> : <></>}
</div>
</div>
);
}; };

View File

@ -1,7 +1,8 @@
/* .flight-card { .flight-card {
display: flex; display: flex;
flex-direction: column;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: flex-start;
padding: 16px; padding: 16px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 8px; border-radius: 8px;
@ -9,37 +10,14 @@
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff; background-color: #fff;
transition: box-shadow 0.3s ease; transition: box-shadow 0.3s ease;
&:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.flight-details {
display: flex;
align-items: center;
}
} */
.flight-card {
display: flex;
flex-direction: column; /* Display as a column instead of a row */
justify-content: space-between;
align-items: flex-start; /* Align items to the start of the column */
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
transition: box-shadow 0.3s ease;
&:hover { &:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
} }
.flight-details { .flight-details {
display: flex; display: flex;
flex-direction: column; /* Display details as a column */ flex-direction: column;
margin-top: 16px; /* Add some space between the two rows */ margin-top: 16px;
} }
} }

View File

@ -5,10 +5,10 @@ import "../../../matchMedia.mock";
import { Card } from "./Card"; import { Card } from "./Card";
describe("Card Component Test", () => { describe("Card Component Test", () => {
test("Display initial, name and icon", async () => { test("Display initial, name and icon", async () => {
// render(<Card name="Belgrano" />); // render(<Card name="Belgrano" />);
// expect(screen.getByText("Belgrano📍")).toBeVisible(); // expect(screen.getByText("Belgrano📍")).toBeVisible();
// expect(screen.getByText("B")).toBeVisible(); // expect(screen.getByText("B")).toBeVisible();
}); });
}); });

View File

@ -1,101 +1,63 @@
// import React from "react";
// import { Avatar, Button } from "antd";
// interface FlightProps {
// flight_code: string;
// status: string;
// origin: string;
// destination: string;
// departure_time: string;
// arrival_time: string;
// gate: string;
// }
// interface CardProps {
// flight: FlightProps;
// }
// export const Card: React.FC<CardProps> = ({
// flight: { flight_code, status, origin, destination, departure_time, arrival_time, gate },
// }) => {
// console.log(flight_code)
// return (
// <div className="Card">
// <Avatar size="large">{flight_code.slice(0, 1).toUpperCase()}</Avatar>
// <div>
// <div>Name: {flight_code}</div>
// <div>Status: {status}</div>
// <div>Origin: {origin}</div>
// <div>Destination: {destination}</div>
// <div>Departure Time: {departure_time}</div>
// <div>Arrival Time: {arrival_time}</div>
// <div>Gate: {gate}</div>
// </div>
// 📍
// </div>
// );
// };
import React from "react"; import React from "react";
import { Avatar, Space, Typography, Tag } from "antd"; import { Avatar, Space, Typography, Tag } from "antd";
import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined, CalendarOutlined } from "@ant-design/icons"; import { RightOutlined, ClockCircleOutlined, SwapOutlined, EnvironmentOutlined, CalendarOutlined } from "@ant-design/icons";
import "./Card.css"; // Import a CSS file for styling, you can create this file with your styles import "./Card.css";
interface FlightProps { interface FlightProps {
flight_code: string; flight_code: string;
status: string; status: string;
origin: string; origin: string;
destination: string; destination: string;
departure_time: string; departure_time: string;
arrival_time: string; arrival_time: string;
gate: string; gate: string;
} }
interface CardProps { interface CardProps {
flight: FlightProps; flight: FlightProps;
} }
const { Text } = Typography; const { Text } = Typography;
export const Card: React.FC<CardProps> = ({ flight }) => { export const Card: React.FC<CardProps> = ({ flight }) => {
return ( return (
<div className="flight-card"> <div className="flight-card">
<Space size={8} align="center"> <Space size={8} align="center">
<Avatar size={64} icon={<RightOutlined />} /> <Avatar size={64} icon={<RightOutlined />} />
<div> <div>
<Text strong>{flight.flight_code}</Text> <Text strong>{flight.flight_code}</Text>
<div> <div>
<Text type="secondary"> <Text type="secondary">
{flight.origin} <SwapOutlined /> {flight.destination} {flight.origin} <SwapOutlined /> {flight.destination}
</Text> </Text>
</div> </div>
</div>
</Space>
<div className="flight-details">
<Space size={8} direction="vertical">
<Text strong>Status:</Text>
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
</Space>
<Space size={8} direction="vertical">
<Text strong>Departure:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.departure_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Arrival:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.arrival_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Gate:</Text>
<Text>{flight.gate}</Text>
</Space>
</div>
</div> </div>
</Space> );
<div className="flight-details">
<Space size={8} direction="vertical">
<Text strong>Status:</Text>
<Tag color={flight.status === "En ruta" ? "green" : "orange"}>{flight.status}</Tag>
</Space>
<Space size={8} direction="vertical">
<Text strong>Departure:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.departure_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Arrival:</Text>
<Space size={2} align="baseline">
<CalendarOutlined />
{flight.arrival_time}
</Space>
</Space>
<Space size={8} direction="vertical">
<Text strong>Gate:</Text>
<Text>{flight.gate}</Text>
</Space>
</div>
</div>
);
}; };

View File

@ -7,14 +7,13 @@ import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table';
import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css'; import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css';
interface Props { interface Props {
flights?: Flight[]; flights?: Flight[];
} }
export const Departure: React.FC<Props> = (props) => { export const Departure: React.FC<Props> = (props) => {
let origin = process.env.REACT_APP_ORIGIN; let origin = process.env.REACT_APP_ORIGIN;
// let destination = process.env.REACT_APP_ORIGIN;
const { zones, error } = useFetchOrigin(origin); const { zones, error } = useFetchOrigin(origin);
const [startIndex, setStartIndex] = useState(0); const [startIndex, setStartIndex] = useState(0);
useEffect(() => { useEffect(() => {
@ -22,92 +21,62 @@ export const Departure: React.FC<Props> = (props) => {
if (zones.length <= 10) { if (zones.length <= 10) {
return; return;
} }
// setStartIndex((prevIndex) => (prevIndex + 10) % zones.length); setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10));
setStartIndex((prevIndex) => (prevIndex + 10) >= zones.length ? 0 : (prevIndex + 10));
}, 5000); }, 5000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [zones]); }, [zones]);
return ( return (
<div > <div >
<h2>Departure</h2> <h2>Departure</h2>
<div className="Items"> <div className="Items">
{/* {(props.flights ? props.flights : zones).map((u) => { <Table>
return <Card key={u.id} flight={u} />; <Thead>
})} */} <Tr>
{/* <table> <Th>Code</Th>
<thead> <Th>Time</Th>
<tr> <Th>Destination</Th>
<th>Flight code</th> <Th>Gate</Th>
<th>Departure</th> <Th>Status</Th>
<th>Departure time</th> </Tr>
<th>Destination</th> </Thead>
<th>Arrival time</th> <Tbody>
<th>Gate</th> {zones.length > 0 && (
<th>Status</th> <>
</tr> {zones.slice(startIndex, startIndex + 10).map((flight) => (
</thead> <Tr key={flight.id} className={flight.status === 'Delayed' ? 'delayed-flight' : ''}>
<tbody> */} <Td>{flight.flight_code}</Td>
<Table> <Td>{flight.departure_time}</Td>
<Thead> <Td>{flight.destination}</Td>
<Tr> <Td>{flight.gate}</Td>
<Th>Code</Th> <Td>{flight.status}</Td>
{/* <Th>Departure</Th> */} </Tr>
<Th>Time</Th> // );
<Th>Destination</Th> ))}
{/* <Th>Arrival time</Th> */} {startIndex + 10 >= zones.length && (
<Th>Gate</Th> <>
<Th>Status</Th> {Array.from({ length: startIndex + 10 - zones.length }).map((_, index) => {
</Tr> return (
</Thead> <Tr>
<Tbody> <Td></Td>
{zones.length > 0 && ( <Td></Td>
<> <Td></Td>
{zones.slice(startIndex, startIndex + 10).map((flight) => ( <Td></Td>
// {/* {Array.from({ length: zones.length < 10 ? zones.length : 10 }).map((_, index) => { <Td></Td>
// const flightIndex = (startIndex + index) % zones.length; </Tr>
// const flight = zones[flightIndex]; )
})}
// return ( */} </>
<Tr key={flight.id} className={flight.status === 'Delayed' ? 'delayed-flight' : ''}> )
{/* <td>{flight.id}</td> */} }
<Td>{flight.id}-{flight.flight_code}</Td> </>
{/* <Td>{flight.origin}</Td> */} )}
<Td>{flight.departure_time}</Td> </Tbody>
<Td>{flight.destination}</Td> </Table>
{/* <Td>{flight.arrival_time}</Td> */} {error ? <div className="Disconnected">{error}</div> : <></>}
<Td>{flight.gate}</Td> </div>
<Td>{flight.status}</Td> </div>
</Tr> );
// );
))}
{startIndex + 10 >= zones.length && (
<>
{Array.from({ length: startIndex + 10 - zones.length }).map((_, index) => {
return (
<Tr>
<Td></Td>
<Td></Td>
<Td></Td>
<Td></Td>
<Td></Td>
</Tr>
)
}) }
</>
)
}
{/* })} */}
</>
)}
</Tbody>
</Table>
{/* </tbody>
</table> */}
{error ? <div className="Disconnected">{error}</div> : <></>}
</div>
</div>
);
}; };

View File

@ -1,42 +1,43 @@
body { body {
font-family: 'Arial', sans-serif; font-family: 'Arial', sans-serif;
background-color: #f0f0f0; background-color: #f0f0f0;
} }
.App { .App {
text-align: center; text-align: center;
margin-top: 20px; margin-top: 20px;
} }
table { table {
width: 80%; width: 80%;
margin: 20px auto; margin: 20px auto;
border-collapse: collapse; border-collapse: collapse;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-color: #fff; background-color: #fff;
} }
th, td { th,
border: 1px solid #ddd; td {
padding: 10px; border: 1px solid #ddd;
text-align: left; padding: 10px;
text-align: left;
} }
th { th {
background-color: #4CAF50; background-color: #4CAF50;
color: #fff; color: #fff;
} }
tbody tr:hover { tbody tr:hover {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
tfoot { tfoot {
background-color: #4CAF50; background-color: #4CAF50;
color: #fff; color: #fff;
} }
.delayed-flight { .delayed-flight {
background-color: #ffcccc; /* Light red for delayed flights */ background-color: #ffcccc;
color: #ff0000; /* Red text for delayed flights */ color: #ff0000;
} }

View File

@ -1,8 +1,8 @@
const mockedUsedNavigate = jest.fn(); const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({ jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"), ...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate, useNavigate: () => mockedUsedNavigate,
})); }));
import "../../matchMedia.mock"; import "../../matchMedia.mock";
@ -11,18 +11,18 @@ import { render, screen } from "@testing-library/react";
// import { Home } from "./Departure"; // import { Home } from "./Departure";
describe("Home View Test", () => { describe("Home View Test", () => {
test("Display initial, name and icon", async () => { test("Display initial, name and icon", async () => {
// render( // render(
// <Home // <Home
// zones={[ // zones={[
// { id: 1, name: "Belgrano" }, // { id: 1, name: "Belgrano" },
// { id: 2, name: "San Isidro" }, // { id: 2, name: "San Isidro" },
// ]} // ]}
// /> // />
// ); // );
// expect(screen.getByText("Zones")).toBeVisible(); // expect(screen.getByText("Zones")).toBeVisible();
// expect(screen.getByText("Belgrano📍")).toBeVisible(); // expect(screen.getByText("Belgrano📍")).toBeVisible();
// expect(screen.getByText("San Isidro📍")).toBeVisible(); // expect(screen.getByText("San Isidro📍")).toBeVisible();
}); });
}); });

View File

@ -5,7 +5,7 @@ import { useNavigate } from "react-router";
import './Page.css' import './Page.css'
interface Props { interface Props {
flights?: Flight[]; flights?: Flight[];
} }
export const Home: React.FC<Props> = (props) => { export const Home: React.FC<Props> = (props) => {
@ -15,10 +15,10 @@ export const Home: React.FC<Props> = (props) => {
navigate(path); navigate(path);
}; };
return ( return (
<div> <div>
<button onClick={() => submitHandler("/departure")}>Departure</button> <button onClick={() => submitHandler("/departure")}>Departure</button>
<button onClick={() => submitHandler("/arrival")}>Arrival</button> <button onClick={() => submitHandler("/arrival")}>Arrival</button>
</div> </div>
); );
}; };

View File

@ -7,14 +7,14 @@ body {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh;
} }
div { div {
text-align: center; text-align: center;
} }
button { button {
background-color: #4CAF50; /* Green */ background-color: #4CAF50;
border: none; border: none;
color: white; color: white;
padding: 15px 32px; padding: 15px 32px;
@ -26,8 +26,8 @@ body {
cursor: pointer; cursor: pointer;
border-radius: 5px; border-radius: 5px;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
button:hover { button:hover {
background-color: #45a049; /* Darker green on hover */ background-color: #45a049;
} }

View File

@ -3,130 +3,90 @@ let db: IDBDatabase;
let version = 1; let version = 1;
export enum Stores { export enum Stores {
Departure = 'departure', Departure = 'departure',
Arrival = 'arrival' Arrival = 'arrival'
} }
interface EventTarget { export const initDB = (): Promise<boolean | IDBDatabase> => {
result: any return new Promise((resolve) => {
} request = indexedDB.open('myDB');
export const initDB = (): Promise<boolean|IDBDatabase> => { request.onupgradeneeded = (e) => {
return new Promise((resolve) => { let req = (e.target as IDBOpenDBRequest)
request = indexedDB.open('myDB'); db = req.result;
request.onupgradeneeded = (e) => { if (!db.objectStoreNames.contains(Stores.Arrival)) {
let req = (e.target as IDBOpenDBRequest) db.createObjectStore(Stores.Arrival, { keyPath: 'id' });
db = req.result; }
if (!db.objectStoreNames.contains(Stores.Departure)) {
db.createObjectStore(Stores.Departure, { keyPath: 'id' });
}
};
if (!db.objectStoreNames.contains(Stores.Arrival)) { request.onsuccess = (e) => {
db.createObjectStore(Stores.Arrival, { keyPath: 'id' }); let req = (e.target as IDBOpenDBRequest)
} db = req.result;
if (!db.objectStoreNames.contains(Stores.Departure)) { version = db.version;
db.createObjectStore(Stores.Departure, { keyPath: 'id' }); resolve(req.result);
} };
};
request.onsuccess = (e) => { request.onerror = (e) => {
let req = (e.target as IDBOpenDBRequest) resolve(false);
db = req.result; };
version = db.version; });
resolve(req.result);
};
request.onerror = (e) => {
resolve(false);
};
});
}; };
export const addData = <T>(storeName: string, data: T): Promise<T|string|null> => { export const addData = <T>(storeName: string, data: T): Promise<T | string | null> => {
return new Promise((resolve) => { return new Promise((resolve) => {
// request = indexedDB.open('myDB', version); const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
// request.onsuccess = (e) => { store.add(data);
// let req = (e.target as IDBOpenDBRequest) resolve(data);
// db = req.result; });
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
store.add(data);
resolve(data);
// };
// request.onerror = () => {
// const error = request.error?.message
// if (error) {
// resolve(error);
// } else {
// resolve('Unknown error');
// }
// };
});
}; };
export const deleteData = (storeName: string, key: number): Promise<boolean> => { export const deleteData = (storeName: string, key: number): Promise<boolean> => {
return new Promise((resolve) => { return new Promise((resolve) => {
// request = indexedDB.open('myDB', version); const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
// request.onsuccess = (e) => { const res = store.delete(key);
// let req = (e.target as IDBOpenDBRequest) console.log("removing" + key)
// db = req.result; res.onsuccess = () => {
const tx = db.transaction(storeName, 'readwrite'); console.log("success")
const store = tx.objectStore(storeName); resolve(true);
const res = store.delete(key); };
console.log("removing" + key) res.onerror = () => {
res.onsuccess = () => { console.log("error")
console.log("success") resolve(false);
resolve(true); }
}; });
res.onerror = () => {
console.log("error")
resolve(false);
}
// };
});
}; };
export const updateData = <T>(storeName: string, key: number, data: T): Promise<T|string|null> => { export const updateData = <T>(storeName: string, key: number, data: T): Promise<T | string | null> => {
return new Promise((resolve) => { return new Promise((resolve) => {
// request = indexedDB.open('myDB', version); const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
// request.onsuccess = (e) => { const res = store.get(key);
// let req = (e.target as IDBOpenDBRequest) res.onsuccess = () => {
// db = req.result; const newData = { ...res.result, ...data };
const tx = db.transaction(storeName, 'readwrite'); store.put(newData);
const store = tx.objectStore(storeName); resolve(newData);
const res = store.get(key); };
res.onsuccess = () => { res.onerror = () => {
const newData = { ...res.result, ...data }; resolve(null);
store.put(newData); }
resolve(newData); });
};
res.onerror = () => {
resolve(null);
}
// };
});
}; };
export const getStoreData = <T>(storeName: Stores): Promise<T[]|null> => { export const getStoreData = <T>(storeName: Stores): Promise<T[] | null> => {
return new Promise((resolve) => { return new Promise((resolve) => {
// request = indexedDB.open('myDB'); const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
// request.onsuccess = (e) => { const res = store.getAll();
// let req = (e.target as IDBOpenDBRequest) res.onsuccess = () => {
// if (!req.result) { resolve(res.result);
// resolve(null); };
// } });
// db = req.result;
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const res = store.getAll();
res.onsuccess = () => {
resolve(res.result);
};
// };
});
}; };
export {}; export { };

View File

@ -1,142 +1,135 @@
import React, { useEffect } from "react"; import { useEffect } from "react";
import { useState } from "react"; import { useState } from "react";
import { User, Flight } from "../Types"; import { Flight } from "../Types";
import { fetchZones } from "../Api"; import { fetchZones } from "../Api";
import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db'; import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db';
export const useFetchDestination = (destination: string | undefined) => { export const useFetchDestination = (destination: string | undefined) => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [zones, setZones] = useState<Flight[]>([]); const [zones, setZones] = useState<Flight[]>([]);
// const [today, setToday] = useState<Date>(new Date());
const isToday = (someDate: Date) => { const isToday = (someDate: Date) => {
let today = new Date(); let today = new Date();
return someDate.getDate() == today.getDate() && return someDate.getDate() == today.getDate() &&
someDate.getMonth() == today.getMonth() && someDate.getMonth() == today.getMonth() &&
someDate.getFullYear() == today.getFullYear() someDate.getFullYear() == today.getFullYear()
} }
useEffect(() => { useEffect(() => {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
// {isToday(new Date(flight.departure_time)) ? 's' : 'n'}
let today = localStorage.getItem('date') let today = localStorage.getItem('date')
if (today && !isToday(new Date(today))) { if (today && !isToday(new Date(today))) {
getStoreData<Flight>(Stores.Arrival) getStoreData<Flight>(Stores.Arrival)
.then((data) => { .then((data) => {
if (data) { if (data) {
data.map((u) => { data.map((u) => {
deleteData(Stores.Arrival, u.id) deleteData(Stores.Arrival, u.id)
}) })
} }
}) })
localStorage.setItem('date', new Date().toString()) localStorage.setItem('date', new Date().toString())
} }
}, 36000) }, 36000)
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, []) }, [])
useEffect(() => { useEffect(() => {
setError(null); setError(null);
let newUpdate = new Date().toISOString() let newUpdate = new Date().toISOString()
localStorage.setItem('date', new Date().toString()) localStorage.setItem('date', new Date().toString())
initDB().then((x) => { initDB().then((x) => {
console.log(x) console.log(x)
getStoreData<Flight>(Stores.Arrival) getStoreData<Flight>(Stores.Arrival)
.then((data) => { .then((data) => {
console.log(data) console.log(data)
if (data && data.length > 0) { if (data && data.length > 0) {
data.sort((a,b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime()) data.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
setZones(data) setZones(data)
} else { } else {
fetchZones(undefined, destination, null) fetchZones(undefined, destination, null)
.then((data) => { .then((data) => {
localStorage.setItem('lastUpdated', newUpdate) localStorage.setItem('lastUpdated', newUpdate)
let toAdd: Flight[] = [] let toAdd: Flight[] = []
data.map((u) => { data.map((u) => {
if (u.status != 'Deleted') { if (u.status != 'Deleted') {
addData(Stores.Arrival, u) addData(Stores.Arrival, u)
toAdd.push(u) toAdd.push(u)
} }
})
toAdd.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
setZones(toAdd);
}) })
// toAdd.sort((a,b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime()) .catch((error) => { });
toAdd.sort((a,b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime()) }
setZones(toAdd); })
})
.catch((error) => {});
}
})
} }
) )
}, [origin]); }, [origin]);
useEffect(() => { useEffect(() => {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
let lastUpdate = localStorage.getItem('lastUpdated') let lastUpdate = localStorage.getItem('lastUpdated')
let newUpdate = new Date().toISOString() let newUpdate = new Date().toISOString()
// let newUpdate = new Date().toTimeString()
fetchZones(undefined, destination, lastUpdate) fetchZones(undefined, destination, lastUpdate)
.then((data) => { .then((data) => {
localStorage.setItem('lastUpdated', newUpdate) localStorage.setItem('lastUpdated', newUpdate)
let toAdd: Flight[] = [] let toAdd: Flight[] = []
let toRemove: Flight[] = [] let toRemove: Flight[] = []
zones.forEach((c, i) => { zones.forEach((c, i) => {
let index = data.findIndex(x => x.id === c.id) let index = data.findIndex(x => x.id === c.id)
if (index >= 0) { if (index >= 0) {
console.log(data[index].departure_time + 'nuevo') console.log(data[index].departure_time + 'nuevo')
if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) { if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) {
console.log("sacamos") console.log("sacamos")
toRemove.push(data[index]) toRemove.push(data[index])
deleteData(Stores.Arrival, c.id) deleteData(Stores.Arrival, c.id)
} else { } else {
toAdd.push(data[index]); toAdd.push(data[index]);
updateData(Stores.Arrival, c.id, data[index]) updateData(Stores.Arrival, c.id, data[index])
}
} else {
console.log(new Date(c.departure_time))
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
console.log("sacamos?")
toRemove.push(c);
deleteData(Stores.Arrival, c.id)
} else {
toAdd.push(c);
}
}
});
// console.log(toAdd)
// console.log(toRemove)
let filtered = data.filter(o =>
!toAdd.some(b => { return o.id === b.id}) && !toRemove.some(b => { return o.id === b.id}) && !(new Date(o.departure_time) < new Date())
)
const newArray = toAdd.concat(filtered);
console.log(filtered)
console.log(newArray)
filtered.forEach(c => {
addData(Stores.Arrival, c)
})
// newArray.sort((a,b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
newArray.sort((a,b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
setZones(newArray);
})
.catch((error) => {
if (!!error.isAxiosError && !error.response) {
//
} }
}); } else {
}, 5000) console.log(new Date(c.departure_time))
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
return () => clearInterval(intervalId); console.log("sacamos?")
}, [origin, zones]) toRemove.push(c);
deleteData(Stores.Arrival, c.id)
} else {
toAdd.push(c);
}
}
});
return { zones, error }; let filtered = data.filter(o =>
!toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date())
)
const newArray = toAdd.concat(filtered);
console.log(filtered)
console.log(newArray)
filtered.forEach(c => {
addData(Stores.Arrival, c)
})
newArray.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
setZones(newArray);
})
.catch((error) => {
if (!!error.isAxiosError && !error.response) {
//
}
});
}, 5000)
return () => clearInterval(intervalId);
}, [origin, zones])
return { zones, error };
}; };

View File

@ -1,142 +1,135 @@
import React, { useEffect } from "react"; import { useEffect } from "react";
import { useState } from "react"; import { useState } from "react";
import { User, Flight } from "../Types"; import { Flight } from "../Types";
import { fetchZones } from "../Api"; import { fetchZones } from "../Api";
import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db'; import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db';
export const useFetchOrigin = (origin: string | undefined) => { export const useFetchOrigin = (origin: string | undefined) => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [zones, setZones] = useState<Flight[]>([]); const [zones, setZones] = useState<Flight[]>([]);
// const [today, setToday] = useState<Date>(new Date());
const isToday = (someDate: Date) => { const isToday = (someDate: Date) => {
let today = new Date(); let today = new Date();
return someDate.getDate() == today.getDate() && return someDate.getDate() == today.getDate() &&
someDate.getMonth() == today.getMonth() && someDate.getMonth() == today.getMonth() &&
someDate.getFullYear() == today.getFullYear() someDate.getFullYear() == today.getFullYear()
} }
useEffect(() => { useEffect(() => {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
// {isToday(new Date(flight.departure_time)) ? 's' : 'n'}
let today = localStorage.getItem('date') let today = localStorage.getItem('date')
if (today && !isToday(new Date(today))) { if (today && !isToday(new Date(today))) {
getStoreData<Flight>(Stores.Departure) getStoreData<Flight>(Stores.Departure)
.then((data) => { .then((data) => {
if (data) { if (data) {
data.map((u) => { data.map((u) => {
deleteData(Stores.Departure, u.id) deleteData(Stores.Departure, u.id)
}) })
} }
}) })
localStorage.setItem('date', new Date().toString()) localStorage.setItem('date', new Date().toString())
} }
}, 36000) }, 36000)
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, []) }, [])
useEffect(() => { useEffect(() => {
setError(null); setError(null);
let newUpdate = new Date().toISOString() let newUpdate = new Date().toISOString()
localStorage.setItem('date', new Date().toString()) localStorage.setItem('date', new Date().toString())
initDB().then((x) => { initDB().then((x) => {
console.log(x) console.log(x)
getStoreData<Flight>(Stores.Departure) getStoreData<Flight>(Stores.Departure)
.then((data) => { .then((data) => {
console.log(data) console.log(data)
if (data && data.length > 0) { if (data && data.length > 0) {
data.sort((a,b) => new Date(a.origin).getTime() - new Date(b.origin).getTime()) data.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
setZones(data) setZones(data)
} else { } else {
fetchZones(origin, undefined, null) fetchZones(origin, undefined, null)
.then((data) => { .then((data) => {
localStorage.setItem('lastUpdated', newUpdate) localStorage.setItem('lastUpdated', newUpdate)
let toAdd: Flight[] = [] let toAdd: Flight[] = []
data.map((u) => { data.map((u) => {
if (u.status != 'Deleted') { if (u.status != 'Deleted') {
addData(Stores.Departure, u) addData(Stores.Departure, u)
toAdd.push(u) toAdd.push(u)
} }
})
toAdd.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
setZones(toAdd);
}) })
// toAdd.sort((a,b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime()) .catch((error) => { });
toAdd.sort((a,b) => new Date(a.origin).getTime() - new Date(b.origin).getTime()) }
setZones(toAdd); })
})
.catch((error) => {});
}
})
} }
) )
}, [origin]); }, [origin]);
useEffect(() => { useEffect(() => {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
let lastUpdate = localStorage.getItem('lastUpdated') let lastUpdate = localStorage.getItem('lastUpdated')
let newUpdate = new Date().toISOString() let newUpdate = new Date().toISOString()
// let newUpdate = new Date().toTimeString()
fetchZones(origin, undefined, lastUpdate) fetchZones(origin, undefined, lastUpdate)
.then((data) => { .then((data) => {
localStorage.setItem('lastUpdated', newUpdate) localStorage.setItem('lastUpdated', newUpdate)
let toAdd: Flight[] = [] let toAdd: Flight[] = []
let toRemove: Flight[] = [] let toRemove: Flight[] = []
zones.forEach((c, i) => { zones.forEach((c, i) => {
let index = data.findIndex(x => x.id === c.id) let index = data.findIndex(x => x.id === c.id)
if (index >= 0) { if (index >= 0) {
console.log(data[index].departure_time + 'nuevo') console.log(data[index].departure_time + 'nuevo')
if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) { if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) {
console.log("sacamos") console.log("sacamos")
toRemove.push(data[index]) toRemove.push(data[index])
deleteData(Stores.Departure, c.id) deleteData(Stores.Departure, c.id)
} else { } else {
toAdd.push(data[index]); toAdd.push(data[index]);
updateData(Stores.Departure, c.id, data[index]) updateData(Stores.Departure, c.id, data[index])
}
} else {
console.log(new Date(c.departure_time))
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
console.log("sacamos?")
toRemove.push(c);
deleteData(Stores.Departure, c.id)
} else {
toAdd.push(c);
}
}
});
// console.log(toAdd)
// console.log(toRemove)
let filtered = data.filter(o =>
!toAdd.some(b => { return o.id === b.id}) && !toRemove.some(b => { return o.id === b.id}) && !(new Date(o.departure_time) < new Date())
)
const newArray = toAdd.concat(filtered);
console.log(filtered)
console.log(newArray)
filtered.forEach(c => {
addData(Stores.Departure, c)
})
// newArray.sort((a,b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
newArray.sort((a,b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
setZones(newArray);
})
.catch((error) => {
if (!!error.isAxiosError && !error.response) {
//
} }
}); } else {
}, 5000) console.log(new Date(c.departure_time))
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
return () => clearInterval(intervalId); console.log("sacamos?")
}, [origin, zones]) toRemove.push(c);
deleteData(Stores.Departure, c.id)
} else {
toAdd.push(c);
}
}
});
return { zones, error }; let filtered = data.filter(o =>
!toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date())
)
const newArray = toAdd.concat(filtered);
console.log(filtered)
console.log(newArray)
filtered.forEach(c => {
addData(Stores.Departure, c)
})
newArray.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
setZones(newArray);
})
.catch((error) => {
if (!!error.isAxiosError && !error.response) {
//
}
});
}, 5000)
return () => clearInterval(intervalId);
}, [origin, zones])
return { zones, error };
}; };

View File

@ -3,29 +3,29 @@ import { useState } from "react";
import { ping } from "../Api"; import { ping } from "../Api";
export const useIsConnected = () => { export const useIsConnected = () => {
const [connected, setConnected] = useState(true); const [connected, setConnected] = useState(true);
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
ping() ping()
.then(() => { .then(() => {
setConnected(true); setConnected(true);
}) })
.catch(() => { .catch(() => {
setConnected(false); setConnected(false);
}); });
}, 5000); }, 5000);
return () => clearInterval(interval);
}, []);
return ( return () => clearInterval(interval);
<div> }, []);
{connected ? (
<p className="Connected">Connected</p> return (
) : ( <div>
<p className="Disconnected">Disconnected</p> {connected ? (
)} <p className="Connected">Connected</p>
</div> ) : (
); <p className="Disconnected">Disconnected</p>
)}
</div>
);
}; };

View File

@ -1,106 +1,106 @@
body { body {
margin: 0; margin: 0;
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif; "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;
} }
.App { .App {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: #eff2f7; background-color: #eff2f7;
} }
.Box { .Box {
border-radius: 20px; border-radius: 20px;
box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2); box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.2);
padding: 50px; padding: 50px;
gap: 30px; gap: 30px;
background-color: white; background-color: white;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
.Small { .Small {
width: 250px; width: 250px;
height: 400px; height: 400px;
} }
.Section { .Section {
flex: 1; flex: 1;
width: 100%; width: 100%;
padding: 30px 50px; padding: 30px 50px;
gap: 30px; gap: 30px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
.Image { .Image {
width: 150px; width: 150px;
} }
.Connected { .Connected {
color: green; color: green;
} }
.Disconnected { .Disconnected {
color: red; color: red;
} }
.FloatingStatus { .FloatingStatus {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 50px; right: 50px;
} }
.LogoutButton { .LogoutButton {
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
right: 50px; right: 50px;
} }
.Card { .Card {
border-radius: 8px; border-radius: 8px;
box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2); box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.2);
gap: 10px; gap: 10px;
padding: 10px; padding: 10px;
width: 100%; width: 100%;
background-color: white; background-color: white;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.Items { .Items {
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
} }
.List { .List {
width: 100%; width: 100%;
height: 500px; height: 500px;
gap: 30px; gap: 30px;
padding: 20px; padding: 20px;
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }