From 77206224a225b8de0950af4d0f18452392eb9d4f Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 9 Nov 2025 11:12:48 +0100 Subject: [PATCH] API updadte --- config/packages/security.yaml | 7 + config/services.yaml | 14 ++ public/css/auth.css | 96 +++++++++ public/css/calculator.css | 146 ++++++++++++++ public/js/calculator.js | 112 +++++++++++ src/Controller/AuthController.php | 96 +++++++++ src/Controller/HomeController.php | 32 ++- src/Controller/ImmobilienSaveController.php | 119 +++++++++++ src/Entity/User.php | 17 +- templates/auth/login.html.twig | 46 +++++ templates/auth/register.html.twig | 48 +++++ templates/base.html.twig | 14 +- templates/home/index.html.twig | 206 +++++++++++++++----- templates/immobilie/my_immobilien.html.twig | 152 +++++++++++++++ 14 files changed, 1048 insertions(+), 57 deletions(-) create mode 100644 public/css/auth.css create mode 100644 public/css/calculator.css create mode 100644 public/js/calculator.js create mode 100644 src/Controller/AuthController.php create mode 100644 src/Controller/ImmobilienSaveController.php create mode 100644 templates/auth/login.html.twig create mode 100644 templates/auth/register.html.twig create mode 100644 templates/immobilie/my_immobilien.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 368e055..f4999f2 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -37,6 +37,13 @@ security: main: lazy: true provider: app_user_provider + form_login: + login_path: app_login + check_path: app_login + default_target_path: app_home + logout: + path: app_logout + target: app_home # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used diff --git a/config/services.yaml b/config/services.yaml index 6bbad87..39e0cd0 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -15,6 +15,20 @@ services: # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/' + exclude: + - '../src/Entity/' + - '../src/Repository/' + - '../src/Kernel.php' + + # controllers are imported separately to make sure services can be injected + # as action arguments even if you don't extend any base controller class + App\Controller\: + resource: '../src/Controller/' + tags: ['controller.service_arguments'] + + # Explicitly register repositories + App\Repository\: + resource: '../src/Repository/' # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/public/css/auth.css b/public/css/auth.css new file mode 100644 index 0000000..7db3bba --- /dev/null +++ b/public/css/auth.css @@ -0,0 +1,96 @@ +.auth-container { + max-width: 450px; + margin: 50px auto; +} + +.auth-box { + background: white; + padding: 40px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.auth-box h2 { + text-align: center; + color: #333; + margin-bottom: 30px; + padding-bottom: 15px; + border-bottom: 2px solid #4CAF50; +} + +.auth-form .form-group { + margin-bottom: 20px; +} + +.auth-form label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #555; +} + +.auth-form input[type="text"], +.auth-form input[type="email"], +.auth-form input[type="password"] { + width: 100%; + padding: 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.auth-form input:focus { + outline: none; + border-color: #4CAF50; +} + +.auth-form .btn-submit { + width: 100%; + padding: 14px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 4px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.3s; +} + +.auth-form .btn-submit:hover { + background-color: #45a049; +} + +.error-message { + background-color: #ffebee; + color: #c62828; + padding: 12px; + border-radius: 4px; + margin-bottom: 20px; + border-left: 4px solid #c62828; +} + +.success-message { + background-color: #e8f5e9; + color: #2e7d32; + padding: 12px; + border-radius: 4px; + margin-bottom: 20px; + border-left: 4px solid #2e7d32; +} + +.auth-links { + text-align: center; + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #eee; +} + +.auth-links a { + color: #4CAF50; + text-decoration: none; +} + +.auth-links a:hover { + text-decoration: underline; +} diff --git a/public/css/calculator.css b/public/css/calculator.css new file mode 100644 index 0000000..3beb5db --- /dev/null +++ b/public/css/calculator.css @@ -0,0 +1,146 @@ +.calculator-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-top: 20px; +} + +.form-section, .results-section { + background: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.form-section h2, .results-section h2 { + color: #333; + border-bottom: 2px solid #4CAF50; + padding-bottom: 10px; + margin-bottom: 20px; +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + color: #555; +} + +.form-group input, +.form-group select { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.form-group input:focus, +.form-group select:focus { + outline: none; + border-color: #4CAF50; +} + +.checkbox-group { + display: flex; + align-items: center; +} + +.checkbox-group input[type="checkbox"] { + width: auto; + margin-right: 8px; +} + +.result-item { + padding: 15px; + margin-bottom: 15px; + background-color: #f8f9fa; + border-left: 4px solid #4CAF50; + border-radius: 4px; +} + +.result-item h3 { + margin: 0 0 5px 0; + color: #333; + font-size: 16px; +} + +.result-item .value { + font-size: 24px; + font-weight: bold; + color: #4CAF50; +} + +.result-item .description { + margin-top: 5px; + font-size: 12px; + color: #666; +} + +.button-group { + display: flex; + gap: 10px; + margin-top: 20px; +} + +.btn { + padding: 12px 24px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.3s; +} + +.btn-primary { + background-color: #4CAF50; + color: white; +} + +.btn-primary:hover { + background-color: #45a049; +} + +.btn-secondary { + background-color: #6c757d; + color: white; +} + +.btn-secondary:hover { + background-color: #5a6268; +} + +.share-link { + margin-top: 15px; + padding: 10px; + background-color: #e8f5e9; + border-radius: 4px; + display: none; +} + +.share-link input { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + margin-top: 5px; +} + +.info-banner { + background-color: #e3f2fd; + padding: 15px; + border-radius: 4px; + margin-bottom: 20px; + border-left: 4px solid #2196F3; +} + +@media (max-width: 768px) { + .calculator-container { + grid-template-columns: 1fr; + } +} diff --git a/public/js/calculator.js b/public/js/calculator.js new file mode 100644 index 0000000..bb6e323 --- /dev/null +++ b/public/js/calculator.js @@ -0,0 +1,112 @@ +$(document).ready(function() { + // Live calculation function + function calculate() { + const kaufpreis = parseFloat($('#kaufpreis').val()) || 0; + const wohnflaeche = parseFloat($('#wohnflaeche').val()) || 0; + const nutzflaeche = parseFloat($('#nutzflaeche').val()) || 0; + const baujahr = parseInt($('#baujahr').val()) || 0; + const abschreibungszeit = parseFloat($('#abschreibungszeit').val()) || 50; + const bundeslandSteuer = parseFloat($('#bundesland_id option:selected').data('steuer')) || 0; + + // Gesamtfläche + const gesamtflaeche = wohnflaeche + nutzflaeche; + $('#result-gesamtflaeche').text(gesamtflaeche.toLocaleString('de-DE') + ' m²'); + + // Preis pro m² + const preisProQm = wohnflaeche > 0 ? kaufpreis / wohnflaeche : 0; + $('#result-preis-pro-qm').text(preisProQm.toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €'); + + // Grunderwerbsteuer + const grunderwerbsteuer = kaufpreis * (bundeslandSteuer / 100); + $('#result-grunderwerbsteuer').text(grunderwerbsteuer.toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €'); + + // Gesamtkosten + const gesamtkosten = kaufpreis + grunderwerbsteuer; + $('#result-gesamtkosten').text(gesamtkosten.toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €'); + + // Jährliche Abschreibung + const abschreibung = abschreibungszeit > 0 ? kaufpreis / abschreibungszeit : 0; + $('#result-abschreibung').text(abschreibung.toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €'); + + // Alter der Immobilie + const currentYear = new Date().getFullYear(); + const alter = baujahr > 0 ? currentYear - baujahr : 0; + $('#result-alter').text(alter + ' Jahre'); + } + + // Trigger calculation on any input change + $('#immo-calculator-form input, #immo-calculator-form select').on('input change', function() { + calculate(); + }); + + // Generate shareable link + $('#share-link-btn').click(function() { + const formData = $('#immo-calculator-form').serializeArray(); + const params = new URLSearchParams(); + + formData.forEach(item => { + if (item.value) { + params.append(item.name, item.value); + } + }); + + const shareUrl = window.location.origin + window.location.pathname + '?' + params.toString(); + $('#share-url').val(shareUrl); + $('#share-link-container').slideDown(); + }); + + // Copy link to clipboard + $('#copy-link-btn').click(function() { + const shareUrl = $('#share-url'); + shareUrl.select(); + document.execCommand('copy'); + alert('Link wurde in die Zwischenablage kopiert!'); + }); + + // Reset form + $('#reset-btn').click(function() { + $('#immo-calculator-form')[0].reset(); + $('#share-link-container').slideUp(); + calculate(); + }); + + // Save immobilie (for logged in users) + $('#save-immobilie-btn').click(function() { + const formData = $('#immo-calculator-form').serializeArray(); + const data = {}; + + formData.forEach(item => { + if (item.name === 'garage') { + data[item.name] = $('#garage').is(':checked'); + } else { + data[item.name] = item.value; + } + }); + + $.ajax({ + url: '/immobilie/save', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(data), + success: function(response) { + if (response.success) { + alert(response.message + '\n\nSie können Ihre gespeicherten Immobilien unter "Meine Immobilien" einsehen.'); + } else { + alert('Fehler: ' + response.message); + } + }, + error: function(xhr) { + if (xhr.status === 401) { + alert('Sie müssen angemeldet sein, um Immobilien zu speichern.'); + window.location.href = '/login'; + } else { + const response = xhr.responseJSON; + alert('Fehler: ' + (response ? response.message : 'Unbekannter Fehler')); + } + } + }); + }); + + // Initial calculation on page load + calculate(); +}); diff --git a/src/Controller/AuthController.php b/src/Controller/AuthController.php new file mode 100644 index 0000000..54b8abd --- /dev/null +++ b/src/Controller/AuthController.php @@ -0,0 +1,96 @@ +getUser()) { + return $this->redirectToRoute('app_home'); + } + + // Get the login error if there is one + $error = $authenticationUtils->getLastAuthenticationError(); + + // Last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('auth/login.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } + + #[Route('/register', name: 'app_register')] + public function register( + Request $request, + UserPasswordHasherInterface $passwordHasher, + EntityManagerInterface $entityManager, + UserRepository $userRepository + ): Response { + // Redirect if already logged in + if ($this->getUser()) { + return $this->redirectToRoute('app_home'); + } + + $error = null; + + if ($request->isMethod('POST')) { + $name = $request->request->get('name'); + $email = $request->request->get('email'); + $password = $request->request->get('password'); + $passwordConfirm = $request->request->get('password_confirm'); + + // Validation + if (empty($name) || empty($email) || empty($password)) { + $error = 'Bitte füllen Sie alle Felder aus.'; + } elseif ($password !== $passwordConfirm) { + $error = 'Die Passwörter stimmen nicht überein.'; + } elseif (strlen($password) < 6) { + $error = 'Das Passwort muss mindestens 6 Zeichen lang sein.'; + } elseif ($userRepository->findOneBy(['email' => $email])) { + $error = 'Diese E-Mail-Adresse ist bereits registriert.'; + } else { + // Create new user + $user = new User(); + $user->setName($name); + $user->setEmail($email); + + // Hash the password + $hashedPassword = $passwordHasher->hashPassword($user, $password); + $user->setPassword($hashedPassword); + + $entityManager->persist($user); + $entityManager->flush(); + + $this->addFlash('success', 'Registrierung erfolgreich! Sie können sich jetzt anmelden.'); + + return $this->redirectToRoute('app_login'); + } + } + + return $this->render('auth/register.html.twig', [ + 'error' => $error, + ]); + } + + #[Route('/logout', name: 'app_logout')] + public function logout(): void + { + // This method can be blank - it will be intercepted by the logout key on your firewall + throw new \Exception('This should never be reached!'); + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index a6eeda2..a658c49 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -2,17 +2,45 @@ namespace App\Controller; +use App\Repository\BundeslandRepository; +use App\Repository\HeizungstypRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; class HomeController extends AbstractController { #[Route('/', name: 'app_home')] - public function index(): Response + public function index( + Request $request, + BundeslandRepository $bundeslandRepository, + HeizungstypRepository $heizungstypRepository + ): Response { + $bundeslaender = $bundeslandRepository->findAll(); + $heizungstypen = $heizungstypRepository->findAll(); + + // Load data from URL parameters if present + $immobilienData = [ + 'adresse' => $request->query->get('adresse', ''), + 'kaufpreis' => $request->query->get('kaufpreis', ''), + 'wohnflaeche' => $request->query->get('wohnflaeche', ''), + 'nutzflaeche' => $request->query->get('nutzflaeche', ''), + 'zimmer' => $request->query->get('zimmer', ''), + 'baujahr' => $request->query->get('baujahr', ''), + 'garage' => $request->query->get('garage', false), + 'etage' => $request->query->get('etage', ''), + 'typ' => $request->query->get('typ', ''), + 'bundesland_id' => $request->query->get('bundesland_id', ''), + 'heizungstyp_id' => $request->query->get('heizungstyp_id', ''), + 'abschreibungszeit' => $request->query->get('abschreibungszeit', '50'), + ]; + return $this->render('home/index.html.twig', [ - 'controller_name' => 'HomeController', + 'bundeslaender' => $bundeslaender, + 'heizungstypen' => $heizungstypen, + 'immobilienData' => $immobilienData, ]); } } diff --git a/src/Controller/ImmobilienSaveController.php b/src/Controller/ImmobilienSaveController.php new file mode 100644 index 0000000..eab4c7e --- /dev/null +++ b/src/Controller/ImmobilienSaveController.php @@ -0,0 +1,119 @@ +getUser()) { + return new JsonResponse([ + 'success' => false, + 'message' => 'Sie müssen angemeldet sein, um Immobilien zu speichern.', + ], 401); + } + + $data = json_decode($request->getContent(), true); + + // Validation + if (empty($data['adresse']) || empty($data['kaufpreis']) || empty($data['wohnflaeche'])) { + return new JsonResponse([ + 'success' => false, + 'message' => 'Bitte füllen Sie mindestens Adresse, Kaufpreis und Wohnfläche aus.', + ], 400); + } + + // Create new Immobilie + $immobilie = new Immobilie(); + $immobilie->setVerwalter($this->getUser()); + $immobilie->setAdresse($data['adresse']); + $immobilie->setKaufpreis((int) $data['kaufpreis']); + $immobilie->setWohnflaeche((int) $data['wohnflaeche']); + $immobilie->setNutzflaeche((int) ($data['nutzflaeche'] ?? 0)); + $immobilie->setZimmer((int) ($data['zimmer'] ?? 0)); + + // Set Typ from string to enum + $typ = ImmobilienTyp::WOHNUNG; // default + if (!empty($data['typ'])) { + $typ = match(strtolower($data['typ'])) { + 'haus' => ImmobilienTyp::HAUS, + 'gewerbe' => ImmobilienTyp::GEWERBE, + 'grundstück', 'grundstueck' => ImmobilienTyp::GRUNDSTUECK, + 'büro', 'buero' => ImmobilienTyp::BUERO, + default => ImmobilienTyp::WOHNUNG, + }; + } + $immobilie->setTyp($typ); + $immobilie->setGarage((bool) ($data['garage'] ?? false)); + + if (!empty($data['baujahr'])) { + $immobilie->setBaujahr((int) $data['baujahr']); + } + + if (!empty($data['etage'])) { + $immobilie->setEtage((int) $data['etage']); + } + + if (!empty($data['abschreibungszeit'])) { + $immobilie->setAbschreibungszeit((int) $data['abschreibungszeit']); + } + + if (!empty($data['beschreibung'])) { + $immobilie->setBeschreibung($data['beschreibung']); + } + + // Set Bundesland + if (!empty($data['bundesland_id'])) { + $bundesland = $bundeslandRepository->find($data['bundesland_id']); + if ($bundesland) { + $immobilie->setBundesland($bundesland); + } + } + + // Set Heizungstyp + if (!empty($data['heizungstyp_id'])) { + $heizungstyp = $heizungstypRepository->find($data['heizungstyp_id']); + if ($heizungstyp) { + $immobilie->setHeizungstyp($heizungstyp); + } + } + + $entityManager->persist($immobilie); + $entityManager->flush(); + + return new JsonResponse([ + 'success' => true, + 'message' => 'Immobilie erfolgreich gespeichert!', + 'id' => $immobilie->getId(), + ]); + } + + #[Route('/meine-immobilien', name: 'app_my_immobilien')] + public function myImmobilien(): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); + + $immobilien = $this->getUser()->getImmobilien(); + + return $this->render('immobilie/my_immobilien.html.twig', [ + 'immobilien' => $immobilien, + ]); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 2d07993..3d9b57c 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -50,6 +50,9 @@ class User implements UserInterface #[ORM\Column(type: 'string', length: 64, unique: true)] private string $apiKey; + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $password = null; + #[ORM\Column(type: 'datetime')] private \DateTimeInterface $createdAt; @@ -165,6 +168,18 @@ class User implements UserInterface return $this; } + public function getPassword(): ?string + { + return $this->password; + } + + public function setPassword(?string $password): self + { + $this->password = $password; + + return $this; + } + /** * UserInterface Methods. */ @@ -190,6 +205,6 @@ class User implements UserInterface public function eraseCredentials(): void { - // Nothing to erase as we use API keys + // Nothing to erase } } diff --git a/templates/auth/login.html.twig b/templates/auth/login.html.twig new file mode 100644 index 0000000..30627aa --- /dev/null +++ b/templates/auth/login.html.twig @@ -0,0 +1,46 @@ +{% extends 'base.html.twig' %} + +{% block title %}Login - {{ parent() }}{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} +
+
+

Anmelden

+ + {% for message in app.flashes('success') %} +
{{ message }}
+ {% endfor %} + + {% if error %} +
+ {{ error.messageKey|trans(error.messageData, 'security') }} +
+ {% endif %} + +
+
+ + +
+ +
+ + +
+ + + + +
+ + +
+
+{% endblock %} diff --git a/templates/auth/register.html.twig b/templates/auth/register.html.twig new file mode 100644 index 0000000..89f264a --- /dev/null +++ b/templates/auth/register.html.twig @@ -0,0 +1,48 @@ +{% extends 'base.html.twig' %} + +{% block title %}Registrierung - {{ parent() }}{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} +
+
+

Registrieren

+ + {% if error %} +
{{ error }}
+ {% endif %} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+
+{% endblock %} diff --git a/templates/base.html.twig b/templates/base.html.twig index b55b811..f218071 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -48,7 +48,19 @@
-

Immorechner

+
+

Immorechner

+ +
diff --git a/templates/home/index.html.twig b/templates/home/index.html.twig index b998148..35f2808 100644 --- a/templates/home/index.html.twig +++ b/templates/home/index.html.twig @@ -1,67 +1,167 @@ {% extends 'base.html.twig' %} -{% block title %}Willkommen - {{ parent() }}{% endblock %} +{% block title %}Immobilienwert berechnen - {{ parent() }}{% endblock %} {% block stylesheets %} {{ parent() }} - + {% endblock %} {% block body %} -
-

Willkommen bei Immorechner

+
+

Immobilienwert-Rechner

+

Berechnen Sie die Werthaltigkeit Ihrer Immobilie. Alle Berechnungen erfolgen in Echtzeit.

+

Ohne Registrierung: Teilen Sie Ihre Berechnung über einen Link.

+

Mit Registrierung: Speichern Sie Ihre Immobilien dauerhaft.

+
-
-

Symfony-Anwendung erfolgreich installiert!

-

Diese Anwendung verfügt über:

-
    -
  • Symfony 7.3 Framework
  • -
  • MariaDB Datenbank (mit Docker)
  • -
  • Doctrine ORM
  • -
  • API Platform für REST-API
  • -
  • Twig Template Engine für UI
  • -
+
+
+

Immobiliendaten

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ {% if app.user %} + + {% endif %} + + +
+ + +
-

Die REST-API ist über API Platform verfügbar und bietet automatische Dokumentation.

+
+

Berechnungsergebnisse

- Zur API-Dokumentation +
+

Gesamtfläche

+
0 m²
+
Wohnfläche + Nutzfläche
+
+ +
+

Preis pro m² Wohnfläche

+
0,00 €
+
Kaufpreis / Wohnfläche
+
+ +
+

Grunderwerbsteuer

+
0,00 €
+
Abhängig vom Bundesland
+
+ +
+

Gesamtkosten

+
0,00 €
+
Kaufpreis + Grunderwerbsteuer
+
+ +
+

Jährliche Abschreibung

+
0,00 €
+
Kaufpreis / Abschreibungszeit
+
+ +
+

Alter der Immobilie

+
0 Jahre
+
Aktuelles Jahr - Baujahr
+
+
{% endblock %} + +{% block javascripts %} + {{ parent() }} + + +{% endblock %} diff --git a/templates/immobilie/my_immobilien.html.twig b/templates/immobilie/my_immobilien.html.twig new file mode 100644 index 0000000..dfb4f36 --- /dev/null +++ b/templates/immobilie/my_immobilien.html.twig @@ -0,0 +1,152 @@ +{% extends 'base.html.twig' %} + +{% block title %}Meine Immobilien - {{ parent() }}{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock %} + +{% block body %} +

Meine Immobilien

+ + {% if immobilien|length > 0 %} +
+ {% for immobilie in immobilien %} +
+

{{ immobilie.adresse }}

+
+
+ + {{ immobilie.typ.label }} +
+
+ + {{ immobilie.kaufpreis|number_format(0, ',', '.') }} € +
+
+ + {{ immobilie.wohnflaeche }} m² +
+
+ + {{ immobilie.nutzflaeche }} m² +
+
+ + {{ immobilie.gesamtflaeche }} m² +
+
+ + {{ immobilie.zimmer }} +
+ {% if immobilie.baujahr %} +
+ + {{ immobilie.baujahr }} +
+ {% endif %} + {% if immobilie.bundesland %} +
+ + {{ immobilie.bundesland.name }} +
+ {% endif %} + {% if immobilie.heizungstyp %} +
+ + {{ immobilie.heizungstyp.name }} +
+ {% endif %} +
+ + {{ immobilie.garage ? 'Ja' : 'Nein' }} +
+
+ + {{ immobilie.createdAt|date('d.m.Y H:i') }} +
+
+
+ {% endfor %} +
+ {% else %} +
+

Sie haben noch keine Immobilien gespeichert

+

Nutzen Sie den Immobilienrechner, um Ihre erste Immobilie zu berechnen und zu speichern.

+ Zum Rechner +
+ {% endif %} +{% endblock %}