Skip to content

Commit

Permalink
Add navigation elements to Apartment detail view to navigate directly…
Browse files Browse the repository at this point in the history
… to adjacent apertments
  • Loading branch information
indigane committed Sep 24, 2024
1 parent 0dd3e3d commit f35380a
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 11 deletions.
15 changes: 15 additions & 0 deletions backend/hitas/tests/apis/test_api_apartment.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,13 @@ def test__api__apartment__retrieve(api_client: HitasAPIClient):
}
],
"sell_by_date": str(sale_2.purchase_date),
"adjacent_apartments": [
{
"id": ap1.uuid.hex,
"apartment_number": ap1.apartment_number,
"stair": ap1.stair,
},
],
}


Expand Down Expand Up @@ -2005,6 +2012,13 @@ def test__api__apartment__update(api_client: HitasAPIClient, minimal_data: bool)
"type": None,
"conditions_of_sale": [],
"sell_by_date": None,
"adjacent_apartments": [
{
"id": ap.uuid.hex,
"apartment_number": ap.apartment_number,
"stair": ap.stair,
}
],
}


Expand Down Expand Up @@ -2224,6 +2238,7 @@ def test__api__apartment__update__overlapping_shares(
del data["conditions_of_sale"]
del data["sell_by_date"]
del data["ownerships"]
del data["adjacent_apartments"]
data["building"] = {"id": building_1.uuid.hex}
data["shares"]["start"] = 20
data["shares"]["end"] = 100
Expand Down
22 changes: 22 additions & 0 deletions backend/hitas/views/apartment.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,19 @@ class Meta:
]


class AdjacentApartmentSerializer(HitasModelSerializer):
apartment_number = serializers.IntegerField(read_only=True)
stair = serializers.CharField(read_only=True)

class Meta:
model = Apartment
fields = [
"id",
"apartment_number",
"stair",
]


class ApartmentDetailSerializer(EnumSupportSerializerMixin, HitasModelSerializer):
is_sold = serializers.SerializerMethodField()
type = ReadOnlyApartmentTypeSerializer(source="apartment_type", required=False, allow_null=True)
Expand All @@ -663,6 +676,7 @@ class ApartmentDetailSerializer(EnumSupportSerializerMixin, HitasModelSerializer
ownerships = serializers.SerializerMethodField()
links = serializers.SerializerMethodField()
building = ReadOnlyBuildingSerializer(write_only=True)
adjacent_apartments = serializers.SerializerMethodField()
improvements = ApartmentImprovementSerializer(source="*")
documents = AparmentDocumentSerializer(many=True, read_only=True)
conditions_of_sale = serializers.SerializerMethodField()
Expand Down Expand Up @@ -696,6 +710,13 @@ def get_conditions_of_sale(instance: ApartmentWithAnnotations) -> list[dict[str,
def get_sell_by_date(instance: ApartmentWithAnnotations) -> Optional[datetime.date]:
return instance.sell_by_date

@staticmethod
def get_adjacent_apartments(instance: ApartmentWithAnnotations) -> list[dict[str, Any]]:
adjacent_apartments = Apartment.objects.filter(
building__real_estate__housing_company=instance.housing_company
).order_by("apartment_number", "id")
return AdjacentApartmentSerializer(adjacent_apartments, many=True).data

@staticmethod
def get_links(instance: ApartmentWithAnnotations):
return create_links(instance)
Expand Down Expand Up @@ -783,6 +804,7 @@ class Meta:
"ownerships",
"notes",
"building",
"adjacent_apartments",
"improvements",
"documents",
"conditions_of_sale",
Expand Down
20 changes: 20 additions & 0 deletions backend/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6374,6 +6374,26 @@ components:
description: Building ID this apartment is associated with
type: string
example: dc1072975bab4ba69f64814f021c6785
adjacent_apartments:
description: List of adjacent apartments for pagination purposes
type: array
readOnly: true
items:
description: Adjacent apartment
type: object
properties:
id:
description: Adjacent apartment ID
type: string
example: dc1072975bab4ba69f64814f021c6785
apartment_number:
description: Apartment number of the adjacent apartment
type: integer
example: 2
stair:
description: Stair number of the adjacent apartment
type: string
example: A
notes:
description: Apartment notes
type: string
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/common/schemas/apartment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,13 @@ export const ApartmentSchema = object({
});
export type IApartment = z.infer<typeof ApartmentSchema>;

export const AdjacentApartmentSchema = object({
id: string().nullable(),
apartment_number: number(),
stair: string(),
});
export type IAdjacentApartment = z.infer<typeof AdjacentApartmentSchema>;

export const ApartmentDetailsSchema = object({
id: APIIdString,
is_sold: boolean(),
Expand All @@ -396,6 +403,7 @@ export const ApartmentDetailsSchema = object({
prices: ApartmentPricesSchema,
completion_date: string().nullable(),
ownerships: OwnershipSchema.array(),
adjacent_apartments: AdjacentApartmentSchema.array(),
notes: string(),
improvements: object({
market_price_index: MarketPriceIndexImprovementSchema.array(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {IconAlertCircle, Tabs} from "hds-react";
import {Button, IconAlertCircle, IconAngleLeft, IconAngleRight, Select, Tabs} from "hds-react";
import React, {useContext, useState} from "react";
import {Link, useNavigate} from "react-router-dom";
import {DetailField, Divider, DocumentsTable, EditButton, Heading, ImprovementsTable} from "../../../common/components";
import {
MutateForm,
Expand Down Expand Up @@ -61,12 +62,23 @@ const PropertyManagerEditModalButton = ({propertyManager}: {propertyManager: IPr
};

const LoadedApartmentDetails = (): React.JSX.Element => {
const navigate = useNavigate();
const {housingCompany, apartment} = useContext(ApartmentViewContext);
if (!apartment) throw new Error("Apartment not found");

// find out if apartment has owners with non-disclosure set to true
const hasObfuscatedOwners = apartment.ownerships.some((ownership) => ownership.owner.non_disclosure);

const adjacentApartmentOptions = apartment.adjacent_apartments.map((adjacentApartment) => ({
label: `${adjacentApartment.stair}${adjacentApartment.apartment_number}`,
value: adjacentApartment.id,
}));
const apartmentIndex = apartment.adjacent_apartments.findIndex(
(adjacentApartment) => apartment.id === adjacentApartment.id
);
const previousApartment = apartment.adjacent_apartments[apartmentIndex - 1];
const nextApartment = apartment.adjacent_apartments[apartmentIndex + 1];

const alert = () => (
<>
<div className="alert-icon-background">
Expand All @@ -81,17 +93,57 @@ const LoadedApartmentDetails = (): React.JSX.Element => {
return (
<>
<h2 className="apartment-stats">
<span className="apartment-stats--number">
{apartment.address.stair}
{apartment.address.apartment_number}
</span>
<span>
{apartment.rooms ?? ""}
{apartment.type?.value ?? ""}
<Select
className="apartment-stats--apartment-select"
label=""
options={adjacentApartmentOptions}
onChange={(selected) =>
navigate(
`/housing-companies/${apartment.links.housing_company.id}/apartments/${selected.value}`
)
}
value={adjacentApartmentOptions[apartmentIndex]}
/>
<span className="apartment-stats--metadata">
<span>
{apartment.rooms ?? ""}
{apartment.type?.value ?? ""}
</span>
<span>{apartment.surface_area ? apartment.surface_area + "m²" : ""}</span>
<span>{apartment.address.floor ? apartment.address.floor + ".krs" : ""}</span>
</span>
<span>{apartment.surface_area ? apartment.surface_area + "m²" : ""}</span>
<span>{apartment.address.floor ? apartment.address.floor + ".krs" : ""}</span>
{hasObfuscatedOwners && alert()}
{apartmentIndex}
<div className="apartment-heading-buttons">
{previousApartment && (
<Link
to={`/housing-companies/${apartment.links.housing_company.id}/apartments/${previousApartment.id}`}
>
<Button
variant="supplementary"
theme="black"
iconLeft={<IconAngleLeft />}
>
{previousApartment.stair}
{previousApartment.apartment_number}
</Button>
</Link>
)}
{nextApartment && (
<Link
to={`/housing-companies/${apartment.links.housing_company.id}/apartments/${nextApartment.id}`}
>
<Button
variant="supplementary"
theme="black"
iconRight={<IconAngleRight />}
>
{nextApartment.stair}
{nextApartment.apartment_number}
</Button>
</Link>
)}
</div>
</h2>
<div className="apartment-action-cards">
<ApartmentMaximumPricesCard
Expand Down
18 changes: 17 additions & 1 deletion frontend/src/styles/components/_ApartmentDetails.sass
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
top: -3px
left: -3px

span
.apartment-stats--metadata span
&:not(:last-child):not(:empty):after
@include inline-flex()
@include justify-content(center)
Expand All @@ -50,6 +50,22 @@
&:after
display: none !important

&--apartment-select
margin: 0 $spacing-l

button
font-size: $fontsize-heading-l
font-weight: 700
line-height: 1
padding-right: 3px

[class^="Icon"]
top: calc($spacing-xs + 2px)
right: 3px

.apartment-heading-buttons
margin-left: auto

.apartment-action-cards
@include flexbox()
gap: 4%
Expand Down

0 comments on commit f35380a

Please sign in to comment.