Skip to content
Snippets Groups Projects
Commit 7214c151 authored by kerdo's avatar kerdo
Browse files

Merge branch '45-fr-29-suggested-price-display-for-advertisement' into 'main'

Resolve "FR-29: Suggested price display for advertisement"

Closes #45

See merge request !51
parents af7dc4a7 f270b9bf
No related branches found
Tags v7.0
1 merge request!51Resolve "FR-29: Suggested price display for advertisement"
Pipeline #47347 passed
document.addEventListener("DOMContentLoaded", function () {
const calculateButton = document.getElementById("calculate_button");
const calculatedPriceField = document.getElementById("calculated_price");
const priceField = document.getElementById("price");
const priceField = document.getElementById("price"); // Price field to be updated
if (calculateButton) {
calculateButton.addEventListener("click", async function () {
try {
// Retrieve values directly from form fields
const typeValue = document.getElementById("type").value;
const propertyTypeValue = document.getElementById("property_type").value;
const areaValue = parseFloat(document.getElementById("area").value.trim());
const roomCountValue = parseInt(document.getElementById("room_count").value.trim(), 10);
const floorValue = parseInt(document.getElementById("floor").value.trim(), 10);
const floorCountValue = parseInt(document.getElementById("floor_count").value.trim(), 10);
const path = window.location.pathname;
let typeValue, propertyTypeValue, areaValue, roomCountValue, floorValue, floorCountValue;
// Ensure all fields are filled
// Check URL to determine page context
if (path.startsWith("/properties/") && (path.includes("/new") || path.includes("/edit"))) {
// New property creation page
typeValue = document.getElementById("type").value;
propertyTypeValue = document.getElementById("property_type").value;
areaValue = parseFloat(document.getElementById("area").value.trim());
roomCountValue = parseInt(document.getElementById("room_count").value.trim(), 10);
floorValue = parseInt(document.getElementById("floor").value.trim(), 10);
floorCountValue = parseInt(document.getElementById("floor_count").value.trim(), 10);
} else if (path.startsWith("/properties/") && !path.includes("/new") && !path.includes("/edit")) {
// View/edit property page
typeValue = document.getElementById("type").textContent.trim().toLowerCase();
propertyTypeValue = document.getElementById("property_type").textContent.trim().toLowerCase();
areaValue = parseFloat(document.getElementById("area").textContent.trim());
roomCountValue = parseInt(document.getElementById("room_count").textContent.trim(), 10);
floorValue = parseInt(document.getElementById("floor").textContent.trim(), 10);
floorCountValue = parseInt(document.getElementById("floor_count").textContent.trim(), 10);
} else {
alert("Unknown page. Cannot calculate price.");
return;
}
console.log({
type: typeValue,
property_type: propertyTypeValue,
area: areaValue,
room_count: roomCountValue,
floor: floorValue,
floor_count: floorCountValue,
});
// Validate required fields
if (
!typeValue ||
!propertyTypeValue ||
......@@ -27,7 +53,6 @@ document.addEventListener("DOMContentLoaded", function () {
return;
}
// Send the parameters directly to the server
const response = await fetch("/api/properties/calculate_price", {
method: "POST",
headers: {
......@@ -46,16 +71,20 @@ document.addEventListener("DOMContentLoaded", function () {
if (response.ok) {
const data = await response.json();
console.log("Calculated Price:", data.calculated_price);
// Update both calculated_price and price fields
calculatedPriceField.value = data.calculated_price;
// Automatically copy the calculated price to the price field
priceField.value = data.calculated_price;
if (priceField) {
priceField.value = data.calculated_price;
}
} else {
const errorData = await response.json();
alert(`Error calculating price: ${errorData.error || "Unknown error"}`);
}
} catch (error) {
console.error("Error:", error);
alert("An error occurred while calculating the price.");
console.error("Error during price calculation:", error);
alert("An error occurred while calculating the price. Please try again.");
}
});
}
......
......@@ -37,3 +37,18 @@ Feature: FR-07 Property Advertisement List (Unauthenticated)
| owner_name | owner_surname | owner_phone | owner_email |
| Existing | Account | 000 | existing.account@gmail.com |
Scenario: 3.10 - Any user can calculate the price of a property in the detailed view
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And there exists such advertisements new
| title | description | type | property_type | location | room_count | area | floor | floor_count | price |
| Apartment A | Small apartment in Tartu for rent | rent | apartment | Tartu, Estonia | 4 | 60 | 4 | 4 | 810 |
| Apartment B | Cool apartment in Tartu, pls buy | rent | apartment | Tartu, Estonia | 3 | 64 | 3 | 4 | 90 |
And I am not logged in
When I visit the property advertisement list page
And I click the View more button on "Apartment B" advertisement
And I click the Calculate Price button
Then I should see the calculated price displayed and it should be 450
......@@ -172,6 +172,50 @@ defmodule AdvertisementListContext do
{:ok, state}
end
and_ ~r/^there exists such advertisements new$/, fn state, %{ table_data: table } ->
owner = Repo.get_by(User, email: state[:email])
advertisements =
table
|> Enum.map(fn details -> details |> Map.put(:reference, Ecto.UUID.generate()) end)
|> Enum.map(fn details -> {Ecto.build_assoc(owner, :properties, details), details} end)
|> Enum.map(fn {assoc, details} -> Property.changeset(assoc, details) end)
|> Enum.map(fn changeset -> Repo.insert!(changeset) end)
countries = Enum.map(advertisements, fn advertisement -> advertisement.location end)
|> Enum.map(fn location -> String.split(location, ",") end)
|> Enum.map(fn location -> List.last(location) end)
|> Enum.map(fn location -> String.trim(location) end)
|> Enum.uniq()
areas = Enum.map(advertisements, fn advertisement -> advertisement.location end)
|> Enum.map(fn location -> String.split(location, ",") end)
|> Enum.map(fn location -> List.first(location) end)
|> Enum.map(fn location -> String.trim(location) end)
|> Enum.uniq()
{
:ok,
state
|> Map.put(:advertisements, advertisements)
|> Map.put(:countries, countries)
|> Map.put(:areas, areas)
}
end
and_ ~r/^I click the Calculate Price button$/, fn state ->
click({:id, "calculate_button"})
{:ok, state}
end
then_ ~r/^I should see the calculated price displayed and it should be 450$/, fn state ->
calculated_price_element = find_element(:id, "calculated_price")
calculated_price_value = attribute_value(calculated_price_element, "value")
assert calculated_price_value == "450"
{:ok, state}
end
defp setup_session(email, password) do
navigate_to("/login")
fill_field({:id, "email"}, email)
......
......@@ -39,10 +39,10 @@ defmodule PropertyUpdateContext do
logged_in_user = Repo.get_by(User, email: state[:email])
advertisements =
table
table
|> Enum.map(fn details -> details |> Map.put(:reference, Ecto.UUID.generate()) end)
|> Enum.map(fn details -> {Ecto.build_assoc(logged_in_user, :properties, details), details} end)
|> Enum.map(fn {assoc, details} -> Property.changeset(assoc, details) end)
|> Enum.map(fn {assoc, details} -> Property.changeset(assoc, details) end)
|> Enum.map(fn changeset -> Repo.insert!(changeset) end)
{
......@@ -115,7 +115,7 @@ defmodule PropertyUpdateContext do
assert visible_in_page? ~r/#{advertisement.area}/
assert visible_in_page? ~r/#{advertisement.floor}/
assert visible_in_page? ~r/#{advertisement.state}/i
{:ok, state}
end
......@@ -145,6 +145,30 @@ defmodule PropertyUpdateContext do
{:ok, state}
end
when_ ~r/^I click calculate price$/, fn state ->
click({:id, "calculate_button"})
{:ok, state}
end
then_ ~r/^a price should be entered into the fields$/, fn state ->
calculated_price_element = find_element(:id, "calculated_price")
calculated_price_value = attribute_value(calculated_price_element, "value")
price_element = find_element(:id, "price")
price_value = attribute_value(price_element, "value")
assert calculated_price_value == price_value
{:ok, state}
end
and_ ~r/^I should see the updated property price new$/, fn state, %{ table_data: table1 } ->
table = List.first(table1)
assert visible_page_text() =~(table[:newPrice])
{:ok, state}
end
defp setup_session(email, password) do
navigate_to("/login")
fill_field({:id, "email"}, email)
......
......@@ -46,3 +46,27 @@ Feature: Property Update
And I am logged in as a random user who does not own the advertisement
And I navigate to the property's details page
Then I should not see an edit button
Scenario: 2.7 - Owner can update their property price based on recommendation
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And the following properties exist
| title | description | type | property_type | state | location | room_count | area | floor | floor_count | price |
| Really cool property | Selling this really really house | sell | house | available | London | 3 | 10.0 | 2 | 5 | 300 |
And I am logged in
And I navigate to my property's details page
When I click the edit button
And I update the following fields
| title | description | type | property_type | state | location | room_count | area | floor | floor_count |price |
| Really AMAZING property | Pls rent this really amazing apartment | rent | house | available | Paris | 4 | 600.0 | 3 | 5 | |
When I click calculate price
Then a price should be entered into the fields
And I click save changes
Then I should see a success message
And I should be redirected back to the property page
And I should see the updated property price new
| title | description | type | property_type | state | location | room_count | area | floor | floor_count | newPrice |
| Really AMAZING property | Pls rent this really amazing apartment | rent | house | available | Paris | 4 | 600.0 | 3 | 5 | 5077 |
\ No newline at end of file
......@@ -388,7 +388,7 @@ defp get_similar_price(%{
"property_type" => property_type,
"area" => area,
"room_count" => room_count,
"floor" => floor
"floor" => floor,
}) do
area = if is_binary(area), do: String.to_float(area), else: area
room_count = if is_binary(room_count), do: String.to_integer(room_count), else: room_count
......
......@@ -38,6 +38,7 @@
<.input field={f[:area]} id="area" type="number" step="0.01" label="Area (m²)" required />
<.input field={f[:floor]} id="floor" type="number" label="Floor" required />
<.input field={f[:floor_count]} id="floor_count" type="number" label="Total Floors" required />
<.input field={f[:price]} id="calculated_price" type="text" label="Calculated Price" readonly />
<.input field={f[:price]} id="price" type="number" step="0.01" label="Price" required />
<div class="mt-4">
......@@ -66,6 +67,8 @@
</div>
<:actions>
<.button type="button" id="calculate_button">Calculate Price</.button>
<.button id="save-changes">Save Changes</.button>
<.link href={~p"/properties/#{@property.reference}"} class="ml-4">
Cancel
......
......@@ -110,10 +110,10 @@
<.content_card>
<dl class="grid grid-cols-1 md:grid-cols-2 gap-4">
<.detail_item label="Type">
<%= String.capitalize(to_string(@property.type)) %>
<span id="type"><%= String.capitalize(to_string(@property.type)) %></span>
</.detail_item>
<.detail_item label="Property Type">
<%= String.capitalize(to_string(@property.property_type)) %>
<span id="property_type"><%= String.capitalize(to_string(@property.property_type)) %></span>
</.detail_item>
<.detail_item label="Status">
......@@ -138,15 +138,13 @@
aria-expanded="false"
aria-haspopup="true"
data-property-reference={@property.reference}
data-property-type={@property.type}
>
data-property-type={@property.type}>
Update Status
<svg
class="-mr-3 ml-2 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
fill="currentColor">
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
......@@ -160,28 +158,24 @@
role="menu"
aria-orientation="vertical"
aria-labelledby="status-menu-button"
tabindex="-1"
>
tabindex="-1">
<div class="py-1" role="none">
<button
class="status-option text-gray-700 dark:text-gray-200 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100 dark:hover:bg-gray-600"
role="menuitem"
data-status="available"
>
data-status="available">
Available
</button>
<button
class="status-option text-gray-700 dark:text-gray-200 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100 dark:hover:bg-gray-600"
role="menuitem"
data-status="reserved"
>
data-status="reserved">
Reserved
</button>
<button
class="status-option text-gray-700 dark:text-gray-200 block px-4 py-2 text-sm w-full text-left hover:bg-gray-100 dark:hover:bg-gray-600"
role="menuitem"
data-status="unavailable"
>
data-status="unavailable">
<%= if @property.type == :rent, do: "Rented", else: "Sold" %>
</button>
</div>
......@@ -200,14 +194,18 @@
</.detail_item>
<.detail_item label="Area">
<%= @property.area %>
<span id="area"><%= @property.area %></span>
</.detail_item>
<.detail_item label="Rooms">
<%= @property.room_count %>
<span id="room_count"><%= @property.room_count %></span>
</.detail_item>
<.detail_item label="Floor">
<%= @property.floor %>/<%= @property.floor_count %>
<span id="floor"><%= @property.floor %></span>/<span id="floor_count"><%= @property.floor_count %></span>
</.detail_item>
<.detail_item label="Suggested Price">
<input type="text" id="calculated_price" value="--" readonly class="border px-2 py-1 rounded-md" />
<.button type="button" id="calculate_button">Calculate Price</.button>
</.detail_item>
</dl>
</.content_card>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment