Refactor and reformat frontend
This commit is contained in:
parent
51db72ed54
commit
c15455b008
|
@ -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}` },
|
||||||
});
|
});
|
||||||
};
|
};
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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"
|
|
||||||
|
|
|
@ -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 : ""))
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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 { };
|
||||||
|
|
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
Loading…
Reference in New Issue