Feladat: egy ingatlankereskedés meglévő ingatlanjainak listázása, új ingatlan mentése, api-val. Van még a feladatban routing és formázás is.
A backend-et elég kitömöríteni, majd node index.js paranccsal indítható. A frontendhez írjuk be a szokásos npm create vite@latest parancsot! A routing-hoz ne felejtsd el telepíteni a szükséges modult: npm install react-router-dom

main.jsx
Az app.jsx, app.css fájlokat töröltem az egyszerűség kedvéért. A boostrap-et itt, a main.jsx-ben importáltam.
1 2 3 4 5 6 7 8 9 10 11 |
import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import "./assets/bootstrap.min.css"; import "./assets/openpage.css"; import Main from "./components/Main"; createRoot(document.getElementById('root')).render( <StrictMode> <Main/> </StrictMode>, ) |
Kezdőlap – components/Main.jsx
Ebben a feladatban nincs külön navigációs menü, hanem ugyanarra az oldalra töltjük be a „főoldal” hivatkozásait. Tehát a Main.jsx-ben lesz a routing megoldva. Az „oldalak” tartalmát a Tartalom nevű komponensbe töltjük be.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import { BrowserRouter, Route, Routes, Link, Outlet } from "react-router-dom"; import Kinalat from "./Kinalat"; import UjHirdetes from "./UjHirdetes"; import Admin from "./Admin"; export default function Main() { return ( <BrowserRouter> <Routes> {/* Fő útvonal, ami a Tartalom komponenst rendereli */} <Route path="/" element={<Tartalom />}> {/* Index route, ami alapértelmezettként a Foldal komponenst jeleníti meg */} <Route index element={<Fooldal />} /> <Route path="offers" element={<Kinalat />} /> <Route path="newad" element={<UjHirdetes />} /> <Route path="admin" element={<Admin />} /> </Route> </Routes> </BrowserRouter> ); } function Tartalom() { return ( <div> {/* Az aktuális komponens (Kinalat, Fooldal vagy UjHirdetes) ide kerül */} <Outlet /> </div> ); } function Fooldal() { return ( <div className="container"> <div className="start w-100"> <h1 className="text-center pt-2 pt-lg-4">Á.L.B. Ingatlanügynöség</h1> <div className="row"> <div className="col-12 col-sm-6 text-center"> <Link className="btn btn-primary" to="/offers">Nézze meg kínálatunkat!</Link> </div> <div className="col-12 col-sm-6 text-center"> <Link className="btn btn-primary" to="/newad">Hirdessen nálunk!</Link> <Link className="btn btn-primary" to="/admin">Adminisztráció</Link> </div> </div> </div> </div> ); } |
Ingatlanok adatainak lekérdezése – compponents/Kinalat.jsx
A feladatban megadott http://localhost:5000/api/ingatlan végpontról lekérjük az ingatlanok adatait. Az adataok megjelenítéséhez táblázatot használunk, amelyet bootstrap segítségével formázunk meg. Fetch segítségével lekérjük az api-tól az adatokat.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import { useEffect, useState } from "react"; export default function Kinalat() { const [kinalat, setKinalat] = useState([]); useEffect(() => { fetch("http://localhost:5000/api/ingatlan") .then((res) => res.json()) .then((data) => setKinalat(data)) .catch((error) => console.error("Hiba történt:", error)); }, []); return ( <> <h1 className="text-center text-body">Ajánlataink</h1> <div className="container" style={{ boxShadow: "0px 2px 5px rgba(0, 0, 0, 0.3)", }} > <table class="table table-responsive"> <thead class="table-success"> <tr> <th className="align-middle text-center">Kategória</th> <th className="align-middle text-center">Leírás</th> <th className="align-middle text-center">Hírdetés dátuma</th> <th className="align-middle text-center">Tehermentes</th> <th className="align-middle text-center">Ár</th> <th className="align-middle text-center">Fénykép</th> </tr> </thead> <tbody> {kinalat.map((ingatlan) => ( <tr key={ingatlan.id}> <td className="align-middle text-center"> {ingatlan.kategoriaNev} </td> <td className="align-middle text-center">{ingatlan.leiras}</td> <td className="align-middle text-center"> {ingatlan.hirdetesDatuma} </td> <td className="align-middle text-center"> {Number(ingatlan.tehermentes) === 0 ? ( <span style={{ color: "red" }}>Nem</span> ) : ( <span style={{ color: "green" }}>Igen</span> )} </td> <td className="align-middle text-center">{ingatlan.ar}</td> <td className="align-middle text-center"> <img style={{ maxHeight: "250px" }} src={ingatlan.kepUrl} ALT="Nincs kép" title={ingatlan.kepUrl} onError={(e) => { e.target.src = "alap.png";; }} /> </td> </tr> ))} </tbody> </table> </div> </> ); } |
Új ingatlan felvétele – UjHirdetes.jsx
A select-hez fel kell használni a http://localhost:5000/api/kategoriak végpontot, és megjeleníteni az adatbázisban található kategóriákat.
Az adatok felvételéhez kaptunk egy sablon űrlapot, ezt használjuk. Az űrlap adatainak felvételéhez a http://localhost:5000/api/ujingatlan végpontot használjuk, POST metódussal!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
import { useState, useEffect } from "react"; export default function UjHirdetes() { const [kategoriak, setKategoriak] = useState([]); const [error, setError] = useState(null); const [urlapAdatai, seturlapAdatai] = useState({ kategoriaId: 0, leiras: "", hirdetesDatuma: getCurrentDate(), tehermentes: false, ar: 0, kepUrl: "", }); useEffect(() => { fetch("http://localhost:5000/api/kategoriak") .then((res) => res.json()) .then((data) => { setKategoriak(data) console.log(data); }) .catch((err) => setError(err)); }, []); const handleChange = (e) => { const { name, value, type, checked } = e.target; seturlapAdatai((prevState) => ({ ...prevState, [name]: type === "checkbox" ? checked : value, })); }; const handleSubmit = (e) => { e.preventDefault(); fetch("http://localhost:5000/api/ujingatlan", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ kategoria: urlapAdatai.kategoriaId, leiras: urlapAdatai.leiras, hirdetesDatuma: urlapAdatai.hirdetesDatuma, tehermentes: urlapAdatai.tehermentes, ar: urlapAdatai.ar, kepUrl: urlapAdatai.kepUrl, }), }) .then((res) => res.json()) .then(() => { console.log("Sikeresen elküldve"); alert("Sikeresen elküldve!"); setError(null); seturlapAdatai({ kategoriaId: 0, leiras: "", hirdetesDatuma: getCurrentDate(), tehermentes: false, kepUrl: "", }); }) .catch((err) => setError(err)); }; // Függvény az aktuális dátum lekéréséhez YYYY-MM-DD formátumban function getCurrentDate() { const maiDatum = new Date(); const ev = maiDatum.getFullYear(); const honap = String(maiDatum.getMonth() + 1).padStart(2, "0"); const nap = String(maiDatum.getDate()).padStart(2, "0"); return `${ev}-${honap}-${nap}`; } return ( <> <div className="container"> <h1 className="text-center text-body">Új hirdetés</h1> <form onSubmit={handleSubmit}> <div className="row"> <div className="offset-lg-3 offset-md-2 col-lg-6 col-md-8 col-12"> <div className="mb-3"> <label htmlFor="category" className="form-label"> Ingatlan kategóriája </label> <select className="form-select" name="kategoriaId" value={urlapAdatai.kategoriaId} onChange={handleChange}> <option value="0">Kérem válasszon</option> {kategoriak.length > 0 && kategoriak.map(kat => ( <option key={kat.id} value={kat.id}>{kat.nev}</option> ))} </select> </div> <div className="mb-3"> <label htmlFor="date" className="form-label"> Hirdetés dátuma </label> <input type="date" className="form-control" name="hirdetesDatuma" value={urlapAdatai.hirdetesDatuma} onChange={handleChange} /> </div> <div className="mb-3"> <label htmlFor="description" className="form-label"> Ingatlan leírása </label> <textarea className="form-control" name="leiras" rows="3" value={urlapAdatai.leiras} onChange={handleChange} ></textarea> </div> <div className="form-check mb-3"> <input className="form-check-input" type="checkbox" name="tehermentes" checked={urlapAdatai.tehermentes} onChange={handleChange} /> <label className="form-check-label" htmlFor="creditFree"> Tehermentes ingatlan </label> </div> <div className="mb-3"> <label htmlFor="price" className="form-label">Ingatlan ára</label> <input type="number" className="form-control" name="ar" value={urlapAdatai.ar} onChange={handleChange} /> </div> <div className="mb-3"> <label htmlFor="pictureUrl" className="form-label"> Fénykép az ingatlanról </label> <input type="url" className="form-control" name="kepUrl" value={urlapAdatai.kepUrl} onChange={handleChange} /> </div> <div className="mb-3 text-center"> <button type="submit" className="btn btn-primary px-5"> Küldés </button>{" "} {/* Submit gomb */} </div> {error && ( <div className="alert alert-danger alert-dismissible" role="alert" > <strong>Hiba:</strong> {error.message} <button type="button" className="btn-close" data-bs-dismiss="alert" aria-label="Close" ></button> </div> )} </div> </div> </form> </div> </> ); } |


Fapados admin felület. Bővítsük ki a weboldalt törlés, szerkesztés lehetőséggel is! Természetesen ezt célszerű védeni, de most erre a részre nem térek ki, csak a crud műveletekre!
A törléshez felhasználtam a Kínalat.jsx táblázatát, csak kiegészítettem egy törlés+szekresztés gombbal minden ingatlant. Az ingatlanokat az id-jük segítségével azonosítjuk be.
Hasonlóképpen az UjIngatlan-jsx-ből is felhasználhatjuk az űrlapot a szerkesztéshez.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
import { useEffect, useState } from "react"; export default function Admin() { const [kinalat, setKinalat] = useState([]); const [kategoriak, setKategoriak] = useState([]); const [szerkesztes, setSzerkesztes] = useState(null); useEffect(() => { fetch("http://localhost:5000/api/ingatlan") .then((res) => res.json()) .then((data) => setKinalat(data)) .catch((error) => console.error("Hiba történt:", error)); fetch("http://localhost:5000/api/kategoriak") .then((res) => res.json()) .then((data) => setKategoriak(data)) .catch((error) => console.error("Hiba történt:", error)); }, []); const torolIngatlan = (id) => { if (!window.confirm("Biztosan törölni szeretnéd ezt az ingatlant?")) return; fetch(`http://localhost:5000/api/ingatlan/${id}`, { method: "DELETE" }) .then(() => setKinalat(kinalat.filter((ingatlan) => ingatlan.id !== id))) .catch((error) => console.error("Hiba történt a törlésnél:", error)); }; const szerkesztIngatlan = (ingatlan) => { setSzerkesztes(ingatlan); }; const handleSzerkesztesSubmit = (event) => { event.preventDefault(); fetch(`http://localhost:5000/api/ingatlan/${szerkesztes.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(szerkesztes), }) .then(() => { setKinalat(kinalat.map((i) => (i.id === szerkesztes.id ? szerkesztes : i))); setSzerkesztes(null); }) .catch((error) => console.error("Hiba történt a frissítésnél:", error)); }; return ( <> <h1 className="text-center text-body">Ajánlataink</h1> <div className="container" style={{ boxShadow: "0px 2px 5px rgba(0, 0, 0, 0.3)" }} > <table className="table table-responsive"> <thead className="table-success"> <tr> <th className="align-middle text-center">Kategória</th> <th className="align-middle text-center">Leírás</th> <th className="align-middle text-center">Hirdetés dátuma</th> <th className="align-middle text-center">Tehermentes</th> <th className="align-middle text-center">Ár</th> <th className="align-middle text-center">Fénykép</th> <th className="align-middle text-center">Műveletek</th> </tr> </thead> <tbody> {kinalat.map((ingatlan) => ( <tr key={ingatlan.id}> <td className="align-middle text-center"> {ingatlan.kategoriaNev} </td> <td className="align-middle text-center">{ingatlan.leiras}</td> <td className="align-middle text-center"> {ingatlan.hirdetesDatuma} </td> <td className="align-middle text-center"> {Number(ingatlan.tehermentes) === 0 ? ( <span style={{ color: "red" }}>Nem</span> ) : ( <span style={{ color: "green" }}>Igen</span> )} </td> <td className="align-middle text-center">{ingatlan.ar}</td> <td className="align-middle text-center"> <img style={{ maxHeight: "250px" }} src={ingatlan.kepUrl} alt="Nincs kép" title={ingatlan.kepUrl} onError={(e) => { e.target.src = "alap.png"; }} /> </td> <td className="align-middle text-center"> <button className="btn btn-danger mx-2" onClick={() => torolIngatlan(ingatlan.id)} > Törlés </button> <button className="btn btn-warning" onClick={() => szerkesztIngatlan(ingatlan)} > Szerkesztés </button> </td> </tr> ))} </tbody> </table> </div> {szerkesztes && ( <div className="container mt-4"> <h2>Ingatlan szerkesztése</h2> <form onSubmit={handleSzerkesztesSubmit}> <label>Kategória:</label> <select className="form-control" value={szerkesztes.kategoria} onChange={(e) => setSzerkesztes({ ...szerkesztes, kategoria: e.target.value })} > {kategoriak.map((kat) => ( <option key={kat.id} value={kat.id}> {kat.nev} </option> ))} </select> <label>Leírás:</label> <input type="text" className="form-control" value={szerkesztes.leiras} onChange={(e) => setSzerkesztes({ ...szerkesztes, leiras: e.target.value })} /> <label>Hirdetés dátuma:</label> <input type="date" className="form-control" value={szerkesztes.hirdetesDatuma} onChange={(e) => setSzerkesztes({ ...szerkesztes, hirdetesDatuma: e.target.value })} /> <label>Tehermentes:</label> <select className="form-control" value={szerkesztes.tehermentes} onChange={(e) => setSzerkesztes({ ...szerkesztes, tehermentes: e.target.value })} > <option value="1">Igen</option> <option value="0">Nem</option> </select> <label>Ár:</label> <input type="number" className="form-control" value={szerkesztes.ar} onChange={(e) => setSzerkesztes({ ...szerkesztes, ar: e.target.value })} /> <label>Kép URL:</label> <input type="text" className="form-control" value={szerkesztes.kepUrl} onChange={(e) => setSzerkesztes({ ...szerkesztes, kepUrl: e.target.value })} /> <button type="submit" className="btn btn-primary mt-3">Mentés</button> <button type="button" className="btn btn-secondary mt-3 mx-2" onClick={() => setSzerkesztes(null)} > Mégse </button> </form> </div> )} </> ); } |