Compare commits
2 Commits
ed378afe0f
...
eb2fe007ab
| Author | SHA1 | Date | |
|---|---|---|---|
| eb2fe007ab | |||
| f33d9fdf36 |
125
src/App.css
125
src/App.css
@@ -11,15 +11,17 @@ body {
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr); /* 4 zones in the top row */
|
||||
grid-template-rows: auto auto; /* Two rows */
|
||||
gap: 5px; /* Reduced gap between zones */
|
||||
padding: 5px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
border: 2px dashed #aaa;
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
@@ -36,7 +38,7 @@ body {
|
||||
}
|
||||
|
||||
.dropzone.runway {
|
||||
width: 300px;
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
background-color: #ffebee; /* Light red */
|
||||
}
|
||||
@@ -49,7 +51,20 @@ body {
|
||||
background-color: #fff3e0; /* Light orange */
|
||||
}
|
||||
|
||||
.dropzone.ppr {
|
||||
background-color: #fff3e0; /* Light orange */
|
||||
}
|
||||
|
||||
.dropzone.inbound {
|
||||
/* grid-column: 1 / 2; /* Place in the first column of the bottom row */
|
||||
background-color: #ede7f6; /* Light purple */
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column; /* Stack items vertically */
|
||||
justify-content: space-between;
|
||||
align-items: flex-start; /* Align items to the left */
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
background-color: #ffffff;
|
||||
@@ -59,8 +74,104 @@ body {
|
||||
cursor: grab;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between; /* Align registration left and type right */
|
||||
width: 100%;
|
||||
font-size: 1.2em; /* Make the registration larger */
|
||||
font-weight: bold; /* Optional: make the registration bold */
|
||||
}
|
||||
|
||||
.card-type {
|
||||
align-self: flex-end; /* Align type to the right on the bottom row */
|
||||
font-size: 0.9em; /* Optional: slightly smaller font for type */
|
||||
color: #666; /* Optional: subtle color for type */
|
||||
}
|
||||
|
||||
.card-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between; /* Align POB to the left and type to the right */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card:active {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.card-note {
|
||||
text-align: center; /* Center the note text */
|
||||
margin: 10px 0; /* Add spacing around the note */
|
||||
font-style: italic; /* Optional: make the note italic for emphasis */
|
||||
color: #555; /* Optional: subtle color for the note */
|
||||
width: 100%; /* Ensure the note spans the full width of the card */
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000; /* Ensure it appears above other elements */
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
width: 300px;
|
||||
max-width: 90%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.popup-content h3 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-content label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.popup-content input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.popup-content button {
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.popup-content button:first-of-type {
|
||||
background-color: #4caf50; /* Green for Add */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.popup-content button:last-of-type {
|
||||
background-color: #f44336; /* Red for Cancel */
|
||||
color: white;
|
||||
}
|
||||
|
||||
.popup-content button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
213
src/App.js
213
src/App.js
@@ -1,19 +1,39 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import "./App.css";
|
||||
|
||||
const App = () => {
|
||||
const [zones, setZones] = useState({
|
||||
awaitingDepart: [
|
||||
{ id: "1", registration: "G-ARYK", type: "C172", pob: 1 },
|
||||
{ id: "2", registration: "G-BIBT", type: "AA5", pob: 2 },
|
||||
{ id: "3", registration: "G-BAMC", type: "C150", pob: 2 },
|
||||
{ id: "4", registration: "G-GOHI", type: "C208", pob: 12 },
|
||||
{ id: "1", registration: "G-ARYK", type: "C172", pob: 1, note: "SLEAP" },
|
||||
{ id: "2", registration: "G-BIBT", type: "AA5", pob: 2, note: "EGBP" },
|
||||
{ id: "3", registration: "G-BAMC", type: "C152", pob: 2, note: "LCL" },
|
||||
{ id: "4", registration: "G-GOHI", type: "C208", pob: 12, note: "Here" },
|
||||
],
|
||||
runway: [],
|
||||
local: [],
|
||||
circuit: [],
|
||||
inbound: [],
|
||||
ppr: [],
|
||||
aog: [],
|
||||
});
|
||||
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
const [newCard, setNewCard] = useState({
|
||||
registration: "",
|
||||
type: "",
|
||||
pob: "",
|
||||
note: "",
|
||||
});
|
||||
const [editCard, setEditCard] = useState(null);
|
||||
|
||||
const registrationInputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (showPopup) {
|
||||
registrationInputRef.current?.focus();
|
||||
}
|
||||
}, [showPopup]);
|
||||
|
||||
const handleDragStart = (e, card, sourceZone) => {
|
||||
e.dataTransfer.setData("card", JSON.stringify(card));
|
||||
e.dataTransfer.setData("sourceZone", sourceZone);
|
||||
@@ -61,17 +81,95 @@ const App = () => {
|
||||
className="card"
|
||||
draggable
|
||||
onDragStart={(e) => handleDragStart(e, card, sourceZone)}
|
||||
onClick={() => handleCardClick(card, sourceZone)}
|
||||
>
|
||||
{card.registration}
|
||||
<br />
|
||||
Type: {card.type}
|
||||
<br />
|
||||
{card.pob} POB
|
||||
<div className="card-header">
|
||||
<span>{card.registration}</span>
|
||||
</div>
|
||||
<div className="card-note">{card.note}</div>
|
||||
<div className="card-bottom">
|
||||
<span>{card.pob} POB</span>
|
||||
<span>{card.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setNewCard((prev) => ({
|
||||
...prev,
|
||||
[name]: name === "registration" ? value.toUpperCase() : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleAddCard = () => {
|
||||
if (
|
||||
!newCard.registration ||
|
||||
!newCard.type ||
|
||||
isNaN(parseInt(newCard.pob, 10))
|
||||
) {
|
||||
alert("All fields are required and POB must be a number.");
|
||||
return;
|
||||
}
|
||||
|
||||
const card = {
|
||||
id: Date.now().toString(),
|
||||
registration: newCard.registration,
|
||||
type: newCard.type,
|
||||
pob: parseInt(newCard.pob, 10),
|
||||
note: newCard.note,
|
||||
};
|
||||
|
||||
setZones((prevZones) => ({
|
||||
...prevZones,
|
||||
awaitingDepart: [...prevZones.awaitingDepart, card],
|
||||
}));
|
||||
|
||||
setShowPopup(false);
|
||||
setNewCard({ registration: "", type: "", pob: "", note: "" });
|
||||
};
|
||||
|
||||
const handleCardClick = (card, sourceZone) => {
|
||||
setEditCard({ ...card, sourceZone });
|
||||
setNewCard(card);
|
||||
setShowPopup(true);
|
||||
};
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
if (
|
||||
!newCard.registration ||
|
||||
!newCard.type ||
|
||||
isNaN(parseInt(newCard.pob, 10))
|
||||
) {
|
||||
alert("All fields are required and POB must be a number.");
|
||||
return;
|
||||
}
|
||||
|
||||
setZones((prevZones) => {
|
||||
const updatedSourceZone = prevZones[editCard.sourceZone].map((item) =>
|
||||
item.id === editCard.id ? { ...newCard, id: editCard.id } : item
|
||||
);
|
||||
|
||||
return {
|
||||
...prevZones,
|
||||
[editCard.sourceZone]: updatedSourceZone,
|
||||
};
|
||||
});
|
||||
|
||||
setShowPopup(false);
|
||||
setEditCard(null);
|
||||
setNewCard({ registration: "", type: "", pob: "", note: "" });
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === "Enter") {
|
||||
editCard ? handleSaveEdit() : handleAddCard();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
{/* Top row */}
|
||||
<div
|
||||
className="dropzone awaitingDepart"
|
||||
onDragOver={handleDragOver}
|
||||
@@ -88,6 +186,14 @@ const App = () => {
|
||||
<h3>Active Runway</h3>
|
||||
{renderCards(zones.runway, "runway")}
|
||||
</div>
|
||||
<div
|
||||
className="dropzone circuit"
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={(e) => handleDrop(e, "circuit")}
|
||||
>
|
||||
<h3>Circuit</h3>
|
||||
{renderCards(zones.circuit, "circuit")}
|
||||
</div>
|
||||
<div
|
||||
className="dropzone local"
|
||||
onDragOver={handleDragOver}
|
||||
@@ -97,13 +203,92 @@ const App = () => {
|
||||
{renderCards(zones.local, "local")}
|
||||
</div>
|
||||
<div
|
||||
className="dropzone circuit"
|
||||
className="dropzone inbound"
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={(e) => handleDrop(e, "circuit")}
|
||||
onDrop={(e) => handleDrop(e, "inbound")}
|
||||
>
|
||||
<h3>Circuit</h3>
|
||||
{renderCards(zones.circuit, "circuit")}
|
||||
<h3>Inbound</h3>
|
||||
{renderCards(zones.inbound, "inbound")}
|
||||
</div>
|
||||
<div
|
||||
className="dropzone ppr"
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={(e) => handleDrop(e, "ppr")}
|
||||
>
|
||||
<h3>PPRs</h3>
|
||||
{renderCards(zones.ppr, "ppr")}
|
||||
</div>
|
||||
<div
|
||||
className="dropzone aog"
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={(e) => handleDrop(e, "aog")}
|
||||
>
|
||||
<h3>A/C on Ground</h3>
|
||||
{renderCards(zones.aog, "aog")}
|
||||
</div>
|
||||
<div className="bottom-container">
|
||||
<button onClick={() => setShowPopup(true)}>Add New Card</button>
|
||||
</div>
|
||||
{showPopup && (
|
||||
<div className="popup">
|
||||
<div className="popup-content">
|
||||
<h3>{editCard ? "Edit Card" : "Add New Card"}</h3>
|
||||
<label>
|
||||
Registration:
|
||||
<input
|
||||
type="text"
|
||||
name="registration"
|
||||
value={newCard.registration}
|
||||
onChange={handleInputChange}
|
||||
ref={registrationInputRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Type:
|
||||
<input
|
||||
type="text"
|
||||
name="type"
|
||||
value={newCard.type}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
POB:
|
||||
<input
|
||||
type="number"
|
||||
name="pob"
|
||||
value={newCard.pob}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Note:
|
||||
<input
|
||||
type="text"
|
||||
name="note"
|
||||
value={newCard.note}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</label>
|
||||
<button onClick={editCard ? handleSaveEdit : handleAddCard}>
|
||||
{editCard ? "Save" : "Add"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowPopup(false);
|
||||
setEditCard(null);
|
||||
setNewCard({ registration: "", type: "", pob: "", note: "" });
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user