API updadte
This commit is contained in:
@@ -37,6 +37,13 @@ security:
|
|||||||
main:
|
main:
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: app_user_provider
|
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
|
# Easy way to control access for large sections of your site
|
||||||
# Note: Only the *first* access control that matches will be used
|
# Note: Only the *first* access control that matches will be used
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ services:
|
|||||||
# this creates a service per class whose id is the fully-qualified class name
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
App\:
|
App\:
|
||||||
resource: '../src/'
|
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
|
# add more service definitions when explicit configuration is needed
|
||||||
# please note that last definitions always *replace* previous ones
|
# please note that last definitions always *replace* previous ones
|
||||||
|
|||||||
96
public/css/auth.css
Normal file
96
public/css/auth.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
146
public/css/calculator.css
Normal file
146
public/css/calculator.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
112
public/js/calculator.js
Normal file
112
public/js/calculator.js
Normal file
@@ -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();
|
||||||
|
});
|
||||||
96
src/Controller/AuthController.php
Normal file
96
src/Controller/AuthController.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
|
|
||||||
|
class AuthController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/login', name: 'app_login')]
|
||||||
|
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||||
|
{
|
||||||
|
// Redirect if already logged in
|
||||||
|
if ($this->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!');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,17 +2,45 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\BundeslandRepository;
|
||||||
|
use App\Repository\HeizungstypRepository;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
class HomeController extends AbstractController
|
class HomeController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route('/', name: 'app_home')]
|
#[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', [
|
return $this->render('home/index.html.twig', [
|
||||||
'controller_name' => 'HomeController',
|
'bundeslaender' => $bundeslaender,
|
||||||
|
'heizungstypen' => $heizungstypen,
|
||||||
|
'immobilienData' => $immobilienData,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
119
src/Controller/ImmobilienSaveController.php
Normal file
119
src/Controller/ImmobilienSaveController.php
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Immobilie;
|
||||||
|
use App\Enum\ImmobilienTyp;
|
||||||
|
use App\Repository\BundeslandRepository;
|
||||||
|
use App\Repository\HeizungstypRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class ImmobilienSaveController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/immobilie/save', name: 'app_immobilie_save', methods: ['POST'])]
|
||||||
|
public function save(
|
||||||
|
Request $request,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
BundeslandRepository $bundeslandRepository,
|
||||||
|
HeizungstypRepository $heizungstypRepository
|
||||||
|
): JsonResponse {
|
||||||
|
// Check if user is logged in
|
||||||
|
if (!$this->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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,9 @@ class User implements UserInterface
|
|||||||
#[ORM\Column(type: 'string', length: 64, unique: true)]
|
#[ORM\Column(type: 'string', length: 64, unique: true)]
|
||||||
private string $apiKey;
|
private string $apiKey;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||||
|
private ?string $password = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime')]
|
#[ORM\Column(type: 'datetime')]
|
||||||
private \DateTimeInterface $createdAt;
|
private \DateTimeInterface $createdAt;
|
||||||
|
|
||||||
@@ -165,6 +168,18 @@ class User implements UserInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPassword(): ?string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPassword(?string $password): self
|
||||||
|
{
|
||||||
|
$this->password = $password;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserInterface Methods.
|
* UserInterface Methods.
|
||||||
*/
|
*/
|
||||||
@@ -190,6 +205,6 @@ class User implements UserInterface
|
|||||||
|
|
||||||
public function eraseCredentials(): void
|
public function eraseCredentials(): void
|
||||||
{
|
{
|
||||||
// Nothing to erase as we use API keys
|
// Nothing to erase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
templates/auth/login.html.twig
Normal file
46
templates/auth/login.html.twig
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Login - {{ parent() }}{% endblock %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
{{ parent() }}
|
||||||
|
<link rel="stylesheet" href="{{ asset('css/auth.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="auth-container">
|
||||||
|
<div class="auth-box">
|
||||||
|
<h2>Anmelden</h2>
|
||||||
|
|
||||||
|
{% for message in app.flashes('success') %}
|
||||||
|
<div class="success-message">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="error-message">
|
||||||
|
{{ error.messageKey|trans(error.messageData, 'security') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form class="auth-form" method="post" action="{{ path('app_login') }}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">E-Mail</label>
|
||||||
|
<input type="email" id="username" name="_username" value="{{ last_username }}" required autofocus>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Passwort</label>
|
||||||
|
<input type="password" id="password" name="_password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
|
||||||
|
|
||||||
|
<button class="btn-submit" type="submit">Anmelden</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="auth-links">
|
||||||
|
<p>Noch kein Konto? <a href="{{ path('app_register') }}">Jetzt registrieren</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
48
templates/auth/register.html.twig
Normal file
48
templates/auth/register.html.twig
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Registrierung - {{ parent() }}{% endblock %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
{{ parent() }}
|
||||||
|
<link rel="stylesheet" href="{{ asset('css/auth.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="auth-container">
|
||||||
|
<div class="auth-box">
|
||||||
|
<h2>Registrieren</h2>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="error-message">{{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form class="auth-form" method="post" action="{{ path('app_register') }}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="name" required autofocus>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">E-Mail</label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Passwort (min. 6 Zeichen)</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirm">Passwort bestätigen</label>
|
||||||
|
<input type="password" id="password_confirm" name="password_confirm" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn-submit" type="submit">Registrieren</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="auth-links">
|
||||||
|
<p>Bereits registriert? <a href="{{ path('app_login') }}">Jetzt anmelden</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -48,7 +48,19 @@
|
|||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Immorechner</h1>
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<h1><a href="{{ path('app_home') }}" style="color: white; text-decoration: none;">Immorechner</a></h1>
|
||||||
|
<nav>
|
||||||
|
{% if app.user %}
|
||||||
|
<span style="color: white; margin-right: 15px;">Hallo, {{ app.user.name }}!</span>
|
||||||
|
<a href="{{ path('app_my_immobilien') }}" style="color: white; text-decoration: none; margin-right: 10px;">Meine Immobilien</a>
|
||||||
|
<a href="{{ path('app_logout') }}" style="color: white; text-decoration: none; padding: 8px 16px; background-color: rgba(255,255,255,0.2); border-radius: 4px;">Abmelden</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ path('app_login') }}" style="color: white; text-decoration: none; margin-right: 10px;">Anmelden</a>
|
||||||
|
<a href="{{ path('app_register') }}" style="color: white; text-decoration: none; padding: 8px 16px; background-color: rgba(255,255,255,0.2); border-radius: 4px;">Registrieren</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +1,167 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Willkommen - {{ parent() }}{% endblock %}
|
{% block title %}Immobilienwert berechnen - {{ parent() }}{% endblock %}
|
||||||
|
|
||||||
{% block stylesheets %}
|
{% block stylesheets %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
<style>
|
<link rel="stylesheet" href="{{ asset('css/calculator.css') }}">
|
||||||
.welcome-box {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
color: #333;
|
|
||||||
border-bottom: 2px solid #4CAF50;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
margin: 20px 0;
|
|
||||||
padding: 15px;
|
|
||||||
background-color: #e8f5e9;
|
|
||||||
border-left: 4px solid #4CAF50;
|
|
||||||
}
|
|
||||||
.api-link {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.api-link:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
ul li {
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="welcome-box">
|
<div class="info-banner">
|
||||||
<h2>Willkommen bei Immorechner</h2>
|
<h2 style="margin-bottom: 10px; border: none;">Immobilienwert-Rechner</h2>
|
||||||
|
<p>Berechnen Sie die Werthaltigkeit Ihrer Immobilie. Alle Berechnungen erfolgen in Echtzeit.</p>
|
||||||
<div class="info">
|
<p><strong>Ohne Registrierung:</strong> Teilen Sie Ihre Berechnung über einen Link.</p>
|
||||||
<p><strong>Symfony-Anwendung erfolgreich installiert!</strong></p>
|
<p><strong>Mit Registrierung:</strong> Speichern Sie Ihre Immobilien dauerhaft.</p>
|
||||||
<p>Diese Anwendung verfügt über:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Symfony 7.3 Framework</li>
|
|
||||||
<li>MariaDB Datenbank (mit Docker)</li>
|
|
||||||
<li>Doctrine ORM</li>
|
|
||||||
<li>API Platform für REST-API</li>
|
|
||||||
<li>Twig Template Engine für UI</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>Die REST-API ist über API Platform verfügbar und bietet automatische Dokumentation.</p>
|
<div class="calculator-container">
|
||||||
|
<div class="form-section">
|
||||||
|
<h2>Immobiliendaten</h2>
|
||||||
|
|
||||||
<a href="/api" class="api-link">Zur API-Dokumentation</a>
|
<form id="immo-calculator-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="adresse">Adresse</label>
|
||||||
|
<input type="text" id="adresse" name="adresse" value="{{ immobilienData.adresse }}" placeholder="z.B. Musterstraße 123, 12345 Berlin">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="kaufpreis">Kaufpreis (€)</label>
|
||||||
|
<input type="number" id="kaufpreis" name="kaufpreis" value="{{ immobilienData.kaufpreis }}" placeholder="z.B. 250000">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wohnflaeche">Wohnfläche (m²)</label>
|
||||||
|
<input type="number" id="wohnflaeche" name="wohnflaeche" value="{{ immobilienData.wohnflaeche }}" placeholder="z.B. 80">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="nutzflaeche">Nutzfläche (m²)</label>
|
||||||
|
<input type="number" id="nutzflaeche" name="nutzflaeche" value="{{ immobilienData.nutzflaeche }}" placeholder="z.B. 100">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="zimmer">Anzahl Zimmer</label>
|
||||||
|
<input type="number" id="zimmer" name="zimmer" value="{{ immobilienData.zimmer }}" placeholder="z.B. 3">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="baujahr">Baujahr</label>
|
||||||
|
<input type="number" id="baujahr" name="baujahr" value="{{ immobilienData.baujahr }}" placeholder="z.B. 2015">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="etage">Etage</label>
|
||||||
|
<input type="number" id="etage" name="etage" value="{{ immobilienData.etage }}" placeholder="z.B. 2">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="typ">Immobilientyp</label>
|
||||||
|
<select id="typ" name="typ">
|
||||||
|
<option value="">-- Bitte wählen --</option>
|
||||||
|
<option value="Wohnung" {% if immobilienData.typ == 'Wohnung' %}selected{% endif %}>Wohnung</option>
|
||||||
|
<option value="Haus" {% if immobilienData.typ == 'Haus' %}selected{% endif %}>Haus</option>
|
||||||
|
<option value="Gewerbe" {% if immobilienData.typ == 'Gewerbe' %}selected{% endif %}>Gewerbe</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="bundesland_id">Bundesland</label>
|
||||||
|
<select id="bundesland_id" name="bundesland_id">
|
||||||
|
<option value="">-- Bitte wählen --</option>
|
||||||
|
{% for bundesland in bundeslaender %}
|
||||||
|
<option value="{{ bundesland.id }}"
|
||||||
|
data-steuer="{{ bundesland.grunderwerbsteuer }}"
|
||||||
|
{% if immobilienData.bundesland_id == bundesland.id %}selected{% endif %}>
|
||||||
|
{{ bundesland.name }} ({{ bundesland.grunderwerbsteuer }}%)
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="heizungstyp_id">Heizungstyp</label>
|
||||||
|
<select id="heizungstyp_id" name="heizungstyp_id">
|
||||||
|
<option value="">-- Bitte wählen --</option>
|
||||||
|
{% for heizungstyp in heizungstypen %}
|
||||||
|
<option value="{{ heizungstyp.id }}"
|
||||||
|
{% if immobilienData.heizungstyp_id == heizungstyp.id %}selected{% endif %}>
|
||||||
|
{{ heizungstyp.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="abschreibungszeit">Abschreibungszeit (Jahre)</label>
|
||||||
|
<input type="number" id="abschreibungszeit" name="abschreibungszeit" value="{{ immobilienData.abschreibungszeit }}" placeholder="z.B. 50">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group checkbox-group">
|
||||||
|
<input type="checkbox" id="garage" name="garage" {% if immobilienData.garage %}checked{% endif %}>
|
||||||
|
<label for="garage">Garage vorhanden</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
{% if app.user %}
|
||||||
|
<button type="button" class="btn btn-primary" id="save-immobilie-btn">Speichern</button>
|
||||||
|
{% endif %}
|
||||||
|
<button type="button" class="btn btn-primary" id="share-link-btn">Link teilen</button>
|
||||||
|
<button type="button" class="btn btn-secondary" id="reset-btn">Zurücksetzen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="share-link" id="share-link-container">
|
||||||
|
<strong>Teilen Sie diese Berechnung:</strong>
|
||||||
|
<input type="text" id="share-url" readonly>
|
||||||
|
<button type="button" class="btn btn-secondary" style="margin-top: 10px;" id="copy-link-btn">Link kopieren</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="results-section">
|
||||||
|
<h2>Berechnungsergebnisse</h2>
|
||||||
|
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Gesamtfläche</h3>
|
||||||
|
<div class="value" id="result-gesamtflaeche">0 m²</div>
|
||||||
|
<div class="description">Wohnfläche + Nutzfläche</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Preis pro m² Wohnfläche</h3>
|
||||||
|
<div class="value" id="result-preis-pro-qm">0,00 €</div>
|
||||||
|
<div class="description">Kaufpreis / Wohnfläche</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Grunderwerbsteuer</h3>
|
||||||
|
<div class="value" id="result-grunderwerbsteuer">0,00 €</div>
|
||||||
|
<div class="description">Abhängig vom Bundesland</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Gesamtkosten</h3>
|
||||||
|
<div class="value" id="result-gesamtkosten">0,00 €</div>
|
||||||
|
<div class="description">Kaufpreis + Grunderwerbsteuer</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Jährliche Abschreibung</h3>
|
||||||
|
<div class="value" id="result-abschreibung">0,00 €</div>
|
||||||
|
<div class="description">Kaufpreis / Abschreibungszeit</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Alter der Immobilie</h3>
|
||||||
|
<div class="value" id="result-alter">0 Jahre</div>
|
||||||
|
<div class="description">Aktuelles Jahr - Baujahr</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
{{ parent() }}
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||||
|
<script src="{{ asset('js/calculator.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
152
templates/immobilie/my_immobilien.html.twig
Normal file
152
templates/immobilie/my_immobilien.html.twig
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Meine Immobilien - {{ parent() }}{% endblock %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
{{ parent() }}
|
||||||
|
<style>
|
||||||
|
.immobilien-list {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immobilie-card {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immobilie-card h3 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.immobilie-details {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item label {
|
||||||
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item value {
|
||||||
|
display: block;
|
||||||
|
color: #333;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state h2 {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state a:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Meine Immobilien</h1>
|
||||||
|
|
||||||
|
{% if immobilien|length > 0 %}
|
||||||
|
<div class="immobilien-list">
|
||||||
|
{% for immobilie in immobilien %}
|
||||||
|
<div class="immobilie-card">
|
||||||
|
<h3>{{ immobilie.adresse }}</h3>
|
||||||
|
<div class="immobilie-details">
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Typ</label>
|
||||||
|
<value>{{ immobilie.typ.label }}</value>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Kaufpreis</label>
|
||||||
|
<value>{{ immobilie.kaufpreis|number_format(0, ',', '.') }} €</value>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Wohnfläche</label>
|
||||||
|
<value>{{ immobilie.wohnflaeche }} m²</value>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Nutzfläche</label>
|
||||||
|
<value>{{ immobilie.nutzflaeche }} m²</value>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Gesamtfläche</label>
|
||||||
|
<value>{{ immobilie.gesamtflaeche }} m²</value>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Zimmer</label>
|
||||||
|
<value>{{ immobilie.zimmer }}</value>
|
||||||
|
</div>
|
||||||
|
{% if immobilie.baujahr %}
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Baujahr</label>
|
||||||
|
<value>{{ immobilie.baujahr }}</value>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if immobilie.bundesland %}
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Bundesland</label>
|
||||||
|
<value>{{ immobilie.bundesland.name }}</value>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if immobilie.heizungstyp %}
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Heizungstyp</label>
|
||||||
|
<value>{{ immobilie.heizungstyp.name }}</value>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Garage</label>
|
||||||
|
<value>{{ immobilie.garage ? 'Ja' : 'Nein' }}</value>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Erstellt am</label>
|
||||||
|
<value>{{ immobilie.createdAt|date('d.m.Y H:i') }}</value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="empty-state">
|
||||||
|
<h2>Sie haben noch keine Immobilien gespeichert</h2>
|
||||||
|
<p>Nutzen Sie den Immobilienrechner, um Ihre erste Immobilie zu berechnen und zu speichern.</p>
|
||||||
|
<a href="{{ path('app_home') }}">Zum Rechner</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user