From f35380ada96d004a510d0cddf8ee61dbf22993d8 Mon Sep 17 00:00:00 2001 From: indigane Date: Tue, 24 Sep 2024 12:20:10 +0300 Subject: [PATCH] Add navigation elements to Apartment detail view to navigate directly to adjacent apertments --- .../hitas/tests/apis/test_api_apartment.py | 15 ++++ backend/hitas/views/apartment.py | 22 ++++++ backend/openapi.yaml | 20 ++++++ frontend/src/common/schemas/apartment.ts | 8 +++ .../ApartmentDetailsPage.tsx | 72 ++++++++++++++++--- .../styles/components/_ApartmentDetails.sass | 18 ++++- 6 files changed, 144 insertions(+), 11 deletions(-) diff --git a/backend/hitas/tests/apis/test_api_apartment.py b/backend/hitas/tests/apis/test_api_apartment.py index 643b378f7..3e88c49e3 100644 --- a/backend/hitas/tests/apis/test_api_apartment.py +++ b/backend/hitas/tests/apis/test_api_apartment.py @@ -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, + }, + ], } @@ -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, + } + ], } @@ -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 diff --git a/backend/hitas/views/apartment.py b/backend/hitas/views/apartment.py index d577d5363..6a8160fe5 100644 --- a/backend/hitas/views/apartment.py +++ b/backend/hitas/views/apartment.py @@ -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) @@ -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() @@ -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) @@ -783,6 +804,7 @@ class Meta: "ownerships", "notes", "building", + "adjacent_apartments", "improvements", "documents", "conditions_of_sale", diff --git a/backend/openapi.yaml b/backend/openapi.yaml index a32c77f4d..ebbb61944 100644 --- a/backend/openapi.yaml +++ b/backend/openapi.yaml @@ -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 diff --git a/frontend/src/common/schemas/apartment.ts b/frontend/src/common/schemas/apartment.ts index a51c4dd27..f72274572 100644 --- a/frontend/src/common/schemas/apartment.ts +++ b/frontend/src/common/schemas/apartment.ts @@ -385,6 +385,13 @@ export const ApartmentSchema = object({ }); export type IApartment = z.infer; +export const AdjacentApartmentSchema = object({ + id: string().nullable(), + apartment_number: number(), + stair: string(), +}); +export type IAdjacentApartment = z.infer; + export const ApartmentDetailsSchema = object({ id: APIIdString, is_sold: boolean(), @@ -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(), diff --git a/frontend/src/features/apartment/ApartmentDetailsPage/ApartmentDetailsPage.tsx b/frontend/src/features/apartment/ApartmentDetailsPage/ApartmentDetailsPage.tsx index 34ee563f0..25b7c6979 100644 --- a/frontend/src/features/apartment/ApartmentDetailsPage/ApartmentDetailsPage.tsx +++ b/frontend/src/features/apartment/ApartmentDetailsPage/ApartmentDetailsPage.tsx @@ -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, @@ -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 = () => ( <>
@@ -81,17 +93,57 @@ const LoadedApartmentDetails = (): React.JSX.Element => { return ( <>

- - {apartment.address.stair} - {apartment.address.apartment_number} - - - {apartment.rooms ?? ""} - {apartment.type?.value ?? ""} +