diff --git a/.gitignore b/.gitignore
index b24fc61..d0441a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
node_modules/
.env
.idea/
+.eslintrc
+.vscode/
\ No newline at end of file
diff --git a/app/common/enums.js b/app/common/enums.js
index f52cf4f..33cb41e 100644
--- a/app/common/enums.js
+++ b/app/common/enums.js
@@ -104,6 +104,13 @@ const AD_CATEGORY = {
id: "FLAT",
title: "Stan",
hasGardenSize: false,
+ hasAccesRoadType: true,
+ hasBalconyProp: true,
+ hasNewBuildingProp: true,
+ hasElevatorProp: true,
+ hasNumberOfRoom: true,
+ hasNumberOfFloors: false,
+ hasFloorProp: true,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS
@@ -112,6 +119,13 @@ const AD_CATEGORY = {
id: "HOUSE",
title: "Kuća",
hasGardenSize: true,
+ hasAccesRoadType: true,
+ hasBalconyProp: true,
+ hasNewBuildingProp: true,
+ hasElevatorProp: false,
+ hasNumberOfRoom: true,
+ hasNumberOfFloors: true,
+ hasFloorProp: false,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS,
@@ -121,6 +135,13 @@ const AD_CATEGORY = {
id: "OFFICE",
title: "Kancelarija",
hasGardenSize: false,
+ hasAccesRoadType: true,
+ hasBalconyProp: false,
+ hasNewBuildingProp: true,
+ hasElevatorProp: true,
+ hasNumberOfRoom: true,
+ hasNumberOfFloors: false,
+ hasFloorProp: true,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS
@@ -129,6 +150,13 @@ const AD_CATEGORY = {
id: "LAND",
title: "Zemljište",
hasGardenSize: false,
+ hasAccesRoadType: true,
+ hasBalconyProp: false,
+ hasNewBuildingProp: false,
+ hasElevatorProp: false,
+ hasNumberOfRoom: false,
+ hasNumberOfFloors: false,
+ hasFloorProp: false,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: LAND_SIZE_SLIDER_OPTIONS
@@ -137,6 +165,13 @@ const AD_CATEGORY = {
id: "APARTMENT",
title: "Apartman",
hasGardenSize: false,
+ hasAccesRoadType: true,
+ hasBalconyProp: true,
+ hasNewBuildingProp: true,
+ hasElevatorProp: true,
+ hasNumberOfRoom: true,
+ hasNumberOfFloors: false,
+ hasFloorProp: true,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS
@@ -145,6 +180,13 @@ const AD_CATEGORY = {
id: "GARAGE",
title: "Garaža",
hasGardenSize: false,
+ hasAccesRoadType: true,
+ hasBalconyProp: false,
+ hasNewBuildingProp: false,
+ hasElevatorProp: false,
+ hasNumberOfRoom: false,
+ hasNumberOfFloors: false,
+ hasFloorProp: false,
priceSliderOptionsSale: GARAGE_PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: GARAGE_PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: GARAGE_SIZE_SLIDER_OPTIONS
@@ -153,6 +195,13 @@ const AD_CATEGORY = {
id: "COTTAGE",
title: "Vikendica",
hasGardenSize: true,
+ hasAccesRoadType: true,
+ hasBalconyProp: true,
+ hasNewBuildingProp: true,
+ hasElevatorProp: false,
+ hasNumberOfRoom: true,
+ hasNumberOfFloors: true,
+ hasFloorProp: false,
priceSliderOptionsSale: PRICE_SLIDER_OPTIONS_SALE,
priceSliderOptionsRent: PRICE_SLIDER_OPTIONS_RENT,
sizeSliderOptions: HOME_SIZE_SLIDER_OPTIONS,
@@ -199,6 +248,10 @@ const EMAIL_FREQUENCY = {
};
const HEATING_TYPE = {
+ ANY: {
+ id: "ANY",
+ title: "Svi"
+ },
NO_HEATING: {
id: "NO_HEATING",
title: "Nije uvedeno"
@@ -238,6 +291,10 @@ const HEATING_TYPE = {
};
const ACCESS_ROAD_TYPE = {
+ ANY: {
+ id: "ANY",
+ title: "Svi"
+ },
ASPHALT: {
id: "ASPHALT",
title: "Asfalt"
diff --git a/app/common/filterEnums.js b/app/common/filterEnums.js
new file mode 100644
index 0000000..608e8d3
--- /dev/null
+++ b/app/common/filterEnums.js
@@ -0,0 +1,110 @@
+const { AD_CATEGORY, ACCESS_ROAD_TYPE, HEATING_TYPE } = require("./enums");
+
+const ADVANCED_BOOLEAN_FILTERS = [
+ {
+ dbField: "balcony",
+ title: "Balkon",
+ categoriesToShow: [
+ AD_CATEGORY.FLAT,
+ AD_CATEGORY.HOUSE,
+ AD_CATEGORY.APARTMENT,
+ AD_CATEGORY.COTTAGE
+ ]
+ },
+ {
+ dbField: "elevator",
+ title: "Lift",
+ categoriesToShow: [
+ AD_CATEGORY.FLAT,
+ AD_CATEGORY.APARTMENT,
+ AD_CATEGORY.OFFICE
+ ]
+ },
+ {
+ dbField: "newBuilding",
+ title: "Novogradnja",
+ categoriesToShow: [
+ AD_CATEGORY.FLAT,
+ AD_CATEGORY.HOUSE,
+ AD_CATEGORY.APARTMENT,
+ AD_CATEGORY.COTTAGE,
+ AD_CATEGORY.OFFICE
+ ]
+ }
+];
+
+const ADVANCED_SEGMENT_SELECT_FILTERS = [
+ {
+ dbField: "accessRoadType",
+ title: "Pristupni put",
+ values: Object.keys(ACCESS_ROAD_TYPE).map(key => ACCESS_ROAD_TYPE[key]),
+ categoriesToShow: [
+ AD_CATEGORY.FLAT,
+ AD_CATEGORY.HOUSE,
+ AD_CATEGORY.APARTMENT,
+ AD_CATEGORY.COTTAGE,
+ AD_CATEGORY.OFFICE,
+ AD_CATEGORY.LAND,
+ AD_CATEGORY.GARAGE
+ ]
+ }
+ // {
+ // dbField: "heatingType",
+ // title: "Vrsta grijanja",
+ // values: Object.keys(HEATING_TYPE).map(key => HEATING_TYPE[key]),
+ // categoriesToShow: [
+ // AD_CATEGORY.FLAT,
+ // AD_CATEGORY.HOUSE,
+ // AD_CATEGORY.APARTMENT,
+ // AD_CATEGORY.COTTAGE,
+ // AD_CATEGORY.OFFICE
+ // ]
+ // }
+];
+
+const ADVANCED_RANGE_FILTERS = [
+ {
+ id: "numberOfFloors",
+ title: "Broj spratova",
+ dbFieldMin: "numberOfFloorsMin",
+ dbFieldMax: "numberOfFloorsMax",
+ validValueMin: -1,
+ validValueMax: 50,
+ categoriesToShow: [AD_CATEGORY.HOUSE, AD_CATEGORY.COTTAGE]
+ },
+ {
+ id: "floor",
+ title: "Sprat",
+ dbFieldMin: "floorMin",
+ dbFieldMax: "floorMax",
+ validValueMin: -10,
+ validValueMax: 50,
+ categoriesToShow: [
+ AD_CATEGORY.FLAT,
+ AD_CATEGORY.APARTMENT,
+ AD_CATEGORY.OFFICE
+ ]
+ },
+ {
+ id: "numberOfRooms",
+ title: "Broj soba",
+ dbFieldMin: "numberOfRoomsMin",
+ dbFieldMax: "numberOfRoomsMax",
+ decimalPlaces: 1,
+ validValueMin: 0,
+ validValueMax: 200,
+ categoriesToShow: [
+ AD_CATEGORY.FLAT,
+ AD_CATEGORY.HOUSE,
+ AD_CATEGORY.APARTMENT,
+ AD_CATEGORY.COTTAGE,
+ AD_CATEGORY.OFFICE
+ ]
+ }
+];
+
+module.exports = {
+ ADVANCED_BOOLEAN_FILTERS,
+ ADVANCED_SEGMENT_SELECT_FILTERS,
+ ADVANCED_RANGE_FILTERS
+};
diff --git a/app/controllers/realEstateFilters.js b/app/controllers/realEstateFilters.js
index 8949cc1..17f5e58 100644
--- a/app/controllers/realEstateFilters.js
+++ b/app/controllers/realEstateFilters.js
@@ -1,5 +1,10 @@
const { currentSearchRequest } = require("../helpers/url");
-const { AD_CATEGORY, AD_TYPE } = require("../common/enums");
+const { AD_CATEGORY, AD_TYPE, ACCESS_ROAD_TYPE } = require("../common/enums");
+const {
+ ADVANCED_BOOLEAN_FILTERS,
+ ADVANCED_SEGMENT_SELECT_FILTERS,
+ ADVANCED_RANGE_FILTERS
+} = require("../common/filterEnums");
const getFilters = async (req, res) => {
const searchRequest = await currentSearchRequest(req);
@@ -19,7 +24,18 @@ const getFilters = async (req, res) => {
sizeMin,
sizeMax,
gardenSizeMin,
- gardenSizeMax
+ gardenSizeMax,
+ numberOfRoomsMin,
+ numberOfRoomsMax,
+ numberOfFloorsMin,
+ numberOfFloorsMax,
+ floorMin,
+ floorMax,
+ includeIncompleteAds,
+ balcony,
+ elevator,
+ newBuilding,
+ accessRoadType
} = searchRequest;
const category = AD_CATEGORY[realEstateType] || AD_CATEGORY.FLAT;
@@ -41,6 +57,40 @@ const getFilters = async (req, res) => {
return;
}
+ // TODO: Maybe this is slow, pay attention to this
+ const filterFilters = filterObject => {
+ const filterCategories = filterObject.categoriesToShow;
+ return filterCategories.indexOf(category) !== -1;
+ };
+
+ const advancedBooleanFilterObjects = ADVANCED_BOOLEAN_FILTERS.filter(
+ filterFilters
+ );
+ const advancedSegmentSelectFilterObjects = ADVANCED_SEGMENT_SELECT_FILTERS.filter(
+ filterFilters
+ );
+ const advancedRangeFilterObjects = ADVANCED_RANGE_FILTERS.filter(
+ filterFilters
+ );
+
+ const advancedBooleanFilterValues = {
+ includeIncompleteAds,
+ balcony,
+ elevator,
+ newBuilding
+ };
+ const advancedSegmentSelectFilterValues = {
+ accessRoadType
+ };
+ const advancedRangeFilterValues = {
+ numberOfFloorsMin,
+ numberOfFloorsMax,
+ numberOfRoomsMin,
+ numberOfRoomsMax,
+ floorMin,
+ floorMax
+ };
+
if (priceMin || priceMax) {
priceSliderOptions.start = [priceMin, priceMax];
}
@@ -58,7 +108,14 @@ const getFilters = async (req, res) => {
hasGardenSize,
priceSliderOptions: JSON.stringify(priceSliderOptions),
sizeSliderOptions: JSON.stringify(sizeSliderOptions),
- gardenSizeSliderOptions: JSON.stringify(gardenSizeSliderOptions)
+ gardenSizeSliderOptions: JSON.stringify(gardenSizeSliderOptions),
+ advancedBooleanFilterObjects,
+ advancedBooleanFilterValues,
+ advancedSegmentSelectFilterObjects,
+ advancedSegmentSelectFilterValues,
+ advancedRangeFilterObjects,
+ advancedRangeFilterValues,
+ includeIncompleteAds
});
};
@@ -78,13 +135,91 @@ const postFilters = async (req, res) => {
const sizeMin = parseInt(req.body.sizeMin) || 0;
const sizeMax = parseInt(req.body.sizeMax) || 0;
- //TODO: Filter validation
+ const advancedRangeFilters = {};
+ ADVANCED_RANGE_FILTERS.forEach(filter => {
+ let parsingFunction = parseInt;
+ if (filter.decimalPlaces) {
+ parsingFunction = parseFloat;
+ }
+
+ advancedRangeFilters[filter.dbFieldMin] = parsingFunction(
+ req.body[filter.dbFieldMin]
+ );
+ advancedRangeFilters[filter.dbFieldMax] = parsingFunction(
+ req.body[filter.dbFieldMax]
+ );
+
+ advancedRangeFilters[filter.dbFieldMin] = isNaN(
+ advancedRangeFilters[filter.dbFieldMin]
+ )
+ ? null
+ : advancedRangeFilters[filter.dbFieldMin];
+ advancedRangeFilters[filter.dbFieldMax] = isNaN(
+ advancedRangeFilters[filter.dbFieldMax]
+ )
+ ? null
+ : advancedRangeFilters[filter.dbFieldMax];
+
+ try {
+ if (filter.decimalPlaces) {
+ advancedRangeFilters[filter.dbFieldMin] = advancedRangeFilters[
+ filter.dbFieldMin
+ ].toFixed(filter.decimalPlaces);
+ advancedRangeFilters[filter.dbFieldMax] = advancedRangeFilters[
+ filter.dbFieldMax
+ ].toFixed(filter.decimalPlaces);
+ }
+ } catch (e) {
+ advancedRangeFilters[filter.dbFieldMin] = null;
+ advancedRangeFilters[filter.dbFieldMax] = null;
+ }
+
+ if (
+ advancedRangeFilters[filter.dbFieldMin] < filter.validValueMin ||
+ advancedRangeFilters[filter.dbFieldMin] > filter.validValueMax
+ ) {
+ advancedRangeFilters[filter.dbFieldMin] = filter.validValueMin;
+ }
+
+ if (
+ advancedRangeFilters[filter.dbFieldMax] < filter.validValueMin ||
+ advancedRangeFilters[filter.dbFieldMax] > filter.validValueMax
+ ) {
+ advancedRangeFilters[filter.dbFieldMax] = filter.validValueMax;
+ }
+ });
+
+ const includeIncompleteAds = req.body.includeIncompleteAds === "on";
+
+ const balcony = req.body.balcony === "on";
+ const elevator = req.body.elevator === "on";
+ const newBuilding = req.body.newBuilding === "on";
+
+ const accessRoadType = req.body.accessRoadType;
+ if (!ACCESS_ROAD_TYPE[accessRoadType]) {
+ res.render("notFound", { title: " Greška !" });
+ return;
+ }
+
+ //TODO: Filter validation
searchRequest.priceMin = priceMin;
searchRequest.priceMax = priceMax;
searchRequest.sizeMin = sizeMin;
searchRequest.sizeMax = sizeMax;
+ for (const filter of Object.keys(advancedRangeFilters)) {
+ searchRequest[filter] = advancedRangeFilters[filter];
+ }
+
+ searchRequest.balcony = balcony;
+ searchRequest.elevator = elevator;
+ searchRequest.newBuilding = newBuilding;
+
+ searchRequest.includeIncompleteAds = includeIncompleteAds;
+
+ searchRequest.accessRoadType = accessRoadType;
+
if (
req.body.gardenSizeMin !== undefined &&
req.body.gardenSizeMax !== undefined
@@ -97,7 +232,6 @@ const postFilters = async (req, res) => {
searchRequest.gardenSizeMin = gardenSizeMin;
searchRequest.gardenSizeMax = gardenSizeMax;
}
-
await searchRequest.save();
res.redirect(nextStepUrl);
diff --git a/app/helpers/db/realEstate.js b/app/helpers/db/realEstate.js
index 0282645..ebeb84c 100644
--- a/app/helpers/db/realEstate.js
+++ b/app/helpers/db/realEstate.js
@@ -2,7 +2,6 @@
const db = require("../../models/index");
const sequelize = require("sequelize");
const Op = sequelize.Op;
-
const bulkUpsertRealEstates = async realEstateData => {
try {
const fieldsToUpdateIfDuplicate = [
@@ -87,7 +86,20 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
sizeMax,
adType,
realEstateType,
- areaToSearch
+ areaToSearch,
+ gardenSizeMin,
+ gardenSizeMax,
+ numberOfRoomsMin,
+ numberOfRoomsMax,
+ numberOfFloorsMin,
+ numberOfFloorsMax,
+ floorMin,
+ floorMax,
+ includeIncompleteAds,
+ balcony,
+ elevator,
+ newBuilding,
+ accessRoadType
} = searchRequest;
const longitudeColumn = sequelize.col("locationLong");
@@ -116,12 +128,20 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
const geoSearchQueryPart = sequelize.where(contains, true);
+ //General queries contain only attributes that are defined for every searchreq
+
+ //Query for case of complete ads
const query = {
adType,
realEstateType,
price: {
- [Op.lte]: priceMax,
- [Op.gte]: priceMin
+ [Op.or]: {
+ [Op.and]: {
+ [Op.lte]: priceMax,
+ [Op.gte]: priceMin
+ },
+ [Op.is]: null
+ }
},
area: {
[Op.lte]: sizeMax,
@@ -130,10 +150,148 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
[Op.and]: geoSearchQueryPart
};
+ //Query for case of incomplete ads
+ const queryIncludeIncomplete = {
+ adType,
+ realEstateType,
+ price: {
+ [Op.or]: {
+ [Op.and]: {
+ [Op.lte]: priceMax,
+ [Op.gte]: priceMin
+ },
+ [Op.is]: null
+ }
+ },
+ area: {
+ [Op.or]: {
+ [Op.and]: {
+ [Op.lte]: sizeMax,
+ [Op.gte]: sizeMin
+ },
+ [Op.is]: null
+ }
+ },
+ [Op.and]: geoSearchQueryPart
+ };
+
+ //Every other attribute is checked separately and included in query only if it is defined
+ if (gardenSizeMax && gardenSizeMin) {
+ query.gardenSize = {
+ [Op.lte]: gardenSizeMax,
+ [Op.gte]: gardenSizeMin
+ };
+ queryIncludeIncomplete.gardenSize = {
+ [Op.or]: {
+ [Op.and]: {
+ [Op.lte]: gardenSizeMax,
+ [Op.gte]: gardenSizeMin
+ },
+ [Op.is]: null
+ }
+ };
+ }
+
+ if (numberOfRoomsMin && numberOfRoomsMax) {
+ query.numberOfRooms = {
+ [Op.lte]: numberOfRoomsMax,
+ [Op.gte]: numberOfRoomsMin
+ };
+ queryIncludeIncomplete.numberOfRooms = {
+ [Op.or]: {
+ [Op.and]: {
+ [Op.lte]: numberOfRoomsMax,
+ [Op.gte]: numberOfRoomsMin
+ },
+ [Op.is]: null
+ }
+ };
+ }
+
+ if (numberOfFloorsMin && numberOfFloorsMax) {
+ query.numberOfFloors = {
+ [Op.lte]: numberOfFloorsMax,
+ [Op.gte]: numberOfFloorsMin
+ };
+ queryIncludeIncomplete.numberOfFloors = {
+ [Op.or]: {
+ [Op.and]: {
+ [Op.lte]: numberOfFloorsMax,
+ [Op.gte]: numberOfFloorsMin
+ },
+ [Op.is]: null
+ }
+ };
+ }
+
+ if (floorMin && floorMax) {
+ query.floor = {
+ [Op.lte]: floorMax,
+ [Op.gte]: floorMin
+ };
+ queryIncludeIncomplete.floor = {
+ [Op.or]: {
+ [Op.and]: {
+ [Op.lte]: floorMax,
+ [Op.gte]: floorMin
+ },
+ [Op.is]: null
+ }
+ };
+ }
+
+ if (balcony) {
+ query.balcony = {
+ [Op.eq]: balcony
+ };
+ queryIncludeIncomplete.balcony = {
+ [Op.or]: {
+ [Op.eq]: balcony,
+ [Op.is]: null
+ }
+ };
+ }
+
+ if (newBuilding) {
+ query.newBuilding = {
+ [Op.eq]: newBuilding
+ };
+ queryIncludeIncomplete.newBuilding = {
+ [Op.or]: {
+ [Op.eq]: newBuilding,
+ [Op.is]: null
+ }
+ };
+ }
+
+ if (elevator) {
+ query.elevator = {
+ [Op.eq]: elevator
+ };
+ queryIncludeIncomplete.elevator = {
+ [Op.or]: {
+ [Op.eq]: elevator,
+ [Op.is]: null
+ }
+ };
+ }
+
+ if (accessRoadType !== "ANY") {
+ query.accessRoadType = {
+ [Op.eq]: accessRoadType
+ };
+ queryIncludeIncomplete.accessRoadType = {
+ [Op.or]: {
+ [Op.eq]: accessRoadType,
+ [Op.is]: null
+ }
+ };
+ }
+
const order = [["updatedAt", "desc"]];
- return await db.RealEstate.findAll({
- where: query,
+ return db.RealEstate.findAll({
+ where: includeIncompleteAds ? queryIncludeIncomplete : query,
limit: maxResults,
order
});
diff --git a/app/helpers/db/searchRequest.js b/app/helpers/db/searchRequest.js
index 6e47b5d..808637a 100644
--- a/app/helpers/db/searchRequest.js
+++ b/app/helpers/db/searchRequest.js
@@ -2,11 +2,13 @@
const db = require("../../models/index");
const sequelize = require("sequelize");
const Op = sequelize.Op;
+const { AD_CATEGORY } = require("../../common/enums");
const getSearchRequest = async searchRequestId => {
try {
return await db.SearchRequest.findByPk(searchRequestId);
} catch (error) {
+ console.log("searchrequest.js", error);
return null;
}
};
@@ -22,7 +24,15 @@ const findSearchRequestsForRealEstate = async realEstate => {
adType,
realEstateType,
locationLat,
- locationLong
+ locationLong,
+ accessRoadType,
+ balcony,
+ newBuilding,
+ elevator,
+ gardenSize,
+ numberOfRooms,
+ numberOfFloors,
+ floor
} = realEstate;
if (!locationLat || !locationLong) {
@@ -39,12 +49,20 @@ const findSearchRequestsForRealEstate = async realEstate => {
const geoSearchQueryPart = sequelize.where(contains, true);
+ //General query contains only attributes that are defined for every RealEstate - not null
const query = {
adType,
realEstateType,
subscribed: true,
[Op.and]: geoSearchQueryPart
};
+ //Needed for defining which attribute should exist or not
+ const realEstateTypeObject = AD_CATEGORY[realEstateType];
+ //Needed to decide on including incomplete RealEstates data
+ let checkForIncompleteWanted = false;
+
+ //Attributes are checked separately and included in query only if defined
+ //Price and area should be defined for every property
if (price) {
query.priceMin = {
@@ -62,8 +80,96 @@ const findSearchRequestsForRealEstate = async realEstate => {
query.sizeMax = {
[Op.gte]: area
};
+ } else {
+ checkForIncompleteWanted = true;
+ }
+ //Other attributes can be defined or not depending on RealEstate type
+ if (gardenSize) {
+ query.gardenSizeMin = {
+ [Op.lte]: gardenSize
+ };
+ query.gardenSizeMax = {
+ [Op.gte]: gardenSize
+ };
+ } else if (realEstateTypeObject.hasGardenSize) {
+ checkForIncompleteWanted = true;
}
+ if (numberOfRooms) {
+ query.numberOfRoomsMin = {
+ [Op.lte]: numberOfRooms
+ };
+ query.numberOfRoomsMax = {
+ [Op.gte]: numberOfRooms
+ };
+ } else if (realEstateTypeObject.hasNumberOfRoom) {
+ checkForIncompleteWanted = true;
+ }
+
+ if (numberOfFloors) {
+ query.numberOfFloorsMin = {
+ [Op.lte]: numberOfFloors
+ };
+ query.numberOfFloorsMax = {
+ [Op.gte]: numberOfFloors
+ };
+ } else if (realEstateTypeObject.hasNumberOfFloors) {
+ checkForIncompleteWanted = true;
+ }
+
+ if (floor) {
+ query.floorMin = {
+ [Op.lte]: floor
+ };
+ query.floorMax = {
+ [Op.gte]: floor
+ };
+ } else if (realEstateTypeObject.hasFloorProp) {
+ checkForIncompleteWanted = true;
+ }
+
+ if (accessRoadType) {
+ query.accessRoadType = {
+ [Op.or]: {
+ [Op.eq]: "ANY",
+ [Op.eq]: accessRoadType
+ }
+ };
+ } else if (realEstateTypeObject.hasAccesRoadType) {
+ checkForIncompleteWanted = true;
+ }
+
+ if (balcony) {
+ query.balcony = {
+ [Op.eq]: balcony
+ };
+ } else if (realEstateTypeObject.hasBalconyProp) {
+ checkForIncompleteWanted = true;
+ }
+
+ if (newBuilding) {
+ query.newBuilding = {
+ [Op.eq]: newBuilding
+ };
+ } else if (realEstateTypeObject.hasNewBuildingProp) {
+ checkForIncompleteWanted = true;
+ }
+
+ if (elevator) {
+ query.elevator = {
+ [Op.eq]: elevator
+ };
+ } else if (realEstateTypeObject.hasElevatorProp) {
+ checkForIncompleteWanted = true;
+ }
+
+ //If one of the attributes that exists for property type is null
+ //we include in query to check if incomplete real estates are accepted
+ if (checkForIncompleteWanted) {
+ query.includeIncompleteAds = {
+ [Op.eq]: true
+ };
+ }
return await db.SearchRequest.findAll({ where: query });
};
diff --git a/app/migrations/20191118105541-add-additional-fields-to-searchRequests-table.js b/app/migrations/20191118105541-add-additional-fields-to-searchRequests-table.js
new file mode 100644
index 0000000..0deaa9b
--- /dev/null
+++ b/app/migrations/20191118105541-add-additional-fields-to-searchRequests-table.js
@@ -0,0 +1,64 @@
+"use strict";
+const { ACCESS_ROAD_TYPE, HEATING_TYPE } = require("../common/enums");
+
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return Promise.all([
+ queryInterface.addColumn("SearchRequests", "includeIncompleteAds", {
+ type: Sequelize.BOOLEAN
+ }),
+ queryInterface.addColumn("SearchRequests", "balcony", {
+ type: Sequelize.BOOLEAN
+ }),
+ queryInterface.addColumn("SearchRequests", "newBuilding", {
+ type: Sequelize.BOOLEAN
+ }),
+ queryInterface.addColumn("SearchRequests", "elevator", {
+ type: Sequelize.BOOLEAN
+ }),
+ queryInterface.addColumn("SearchRequests", "numberOfRoomsMin", {
+ type: Sequelize.REAL
+ }),
+ queryInterface.addColumn("SearchRequests", "numberOfRoomsMax", {
+ type: Sequelize.REAL
+ }),
+ queryInterface.addColumn("SearchRequests", "numberOfFloorsMin", {
+ type: Sequelize.INTEGER
+ }),
+ queryInterface.addColumn("SearchRequests", "numberOfFloorsMax", {
+ type: Sequelize.INTEGER
+ }),
+ queryInterface.addColumn("SearchRequests", "floorMin", {
+ type: Sequelize.INTEGER
+ }),
+ queryInterface.addColumn("SearchRequests", "floorMax", {
+ type: Sequelize.INTEGER
+ }),
+ queryInterface.addColumn("SearchRequests", "accessRoadType", {
+ type: Sequelize.TEXT,
+ defaultValue: ACCESS_ROAD_TYPE.ANY.id
+ }),
+ queryInterface.addColumn("SearchRequests", "heatingType", {
+ type: Sequelize.TEXT,
+ defaultValue: HEATING_TYPE.ANY.id
+ })
+ ]);
+ },
+
+ down: (queryInterface, Sequelize) => {
+ return Promise.all([
+ queryInterface.removeColumn("SearchRequests", "includeIncompleteAds"),
+ queryInterface.removeColumn("SearchRequests", "balcony"),
+ queryInterface.removeColumn("SearchRequests", "newBuilding"),
+ queryInterface.removeColumn("SearchRequests", "elevator"),
+ queryInterface.removeColumn("SearchRequests", "numberOfRoomsMin"),
+ queryInterface.removeColumn("SearchRequests", "numberOfRoomsMax"),
+ queryInterface.removeColumn("SearchRequests", "numberOfFloorsMin"),
+ queryInterface.removeColumn("SearchRequests", "numberOfFloorsMax"),
+ queryInterface.removeColumn("SearchRequests", "floorMin"),
+ queryInterface.removeColumn("SearchRequests", "floorMax"),
+ queryInterface.removeColumn("SearchRequests", "accessRoadType"),
+ queryInterface.removeColumn("SearchRequests", "heatingType")
+ ]);
+ }
+};
diff --git a/app/models/searchRequest.js b/app/models/searchRequest.js
index b1fd229..8a04593 100644
--- a/app/models/searchRequest.js
+++ b/app/models/searchRequest.js
@@ -69,7 +69,19 @@ module.exports = (sequelize, DataTypes) => {
},
deletedEmail: {
type: DataTypes.TEXT
- }
+ },
+ includeIncompleteAds: DataTypes.BOOLEAN,
+ balcony: DataTypes.BOOLEAN,
+ elevator: DataTypes.BOOLEAN,
+ newBuilding: DataTypes.BOOLEAN,
+ numberOfRoomsMin: DataTypes.REAL,
+ numberOfRoomsMax: DataTypes.REAL,
+ numberOfFloorsMin: DataTypes.INTEGER,
+ numberOfFloorsMax: DataTypes.INTEGER,
+ floorMin: DataTypes.INTEGER,
+ floorMax: DataTypes.INTEGER,
+ accessRoadType: DataTypes.TEXT,
+ heatingType: DataTypes.TEXT
});
return SearchRequest;
diff --git a/app/public/main.css b/app/public/main.css
index 2e0d7d4..8d128b7 100644
--- a/app/public/main.css
+++ b/app/public/main.css
@@ -110,3 +110,47 @@ h3 {
.collection a.collection-item:not(.active):hover {
background-color: rgba(2, 173, 186, 0.2);
}
+
+.tabs .tab a {
+ color: #02adba;
+ -webkit-transition: color 0.28s ease, background-color 0.28s ease;
+ transition: color 0.28s ease, background-color 0.28s ease;
+}
+.tabs .tab a:focus,
+.tabs .tab a:focus.active {
+ background-color: rgba(2, 173, 186, 0.2);
+}
+.tabs .tab a:hover,
+.tabs .tab a.active {
+ color: #02adba;
+}
+.tabs .indicator {
+ background-color: #02adba;
+}
+
+[type="checkbox"].filled-in:checked + span:not(.lever):after {
+ border: 2px solid #02adba;
+ background-color: #02adba;
+}
+[type="checkbox"].filled-in:not(:checked) + span:not(.lever):after {
+ background-color: transparent;
+ border: 2px solid #02adba;
+}
+
+.distinguished {
+ border: 2px solid #02adba;
+ border-radius: 4px;
+ padding: 5px 5px 3px 5px;
+ margin-left: -5px;
+}
+
+.checkbox-label {
+ color: black;
+ font-size: 14px;
+}
+
+.column-label {
+ position: relative;
+ margin-top: 2rem;
+ margin-bottom: 1rem;
+}
diff --git a/app/public/segment.css b/app/public/segment.css
index 313de5d..6ca1768 100644
--- a/app/public/segment.css
+++ b/app/public/segment.css
@@ -25,6 +25,12 @@
border-right: 1px solid #02adba;
}
+.segmented.small [type="radio"]:not(:checked) + span,
+.segmented.small [type="radio"]:checked + span {
+ padding-left: 7px;
+ padding-right: 7px;
+}
+
.segmented :last-child .label {
border-right: none;
}
diff --git a/app/views/advancedFilters.ejs b/app/views/advancedFilters.ejs
new file mode 100644
index 0000000..23e50f7
--- /dev/null
+++ b/app/views/advancedFilters.ejs
@@ -0,0 +1,69 @@
+
+
+<% for (const filter of advancedBooleanFilterObjects){ %>
+
+ +
+<% } %> + ++ <%= filter.title %> +
++ +
diff --git a/app/views/realEstateFilters.ejs b/app/views/realEstateFilters.ejs index 1b3eb01..caf3c23 100644 --- a/app/views/realEstateFilters.ejs +++ b/app/views/realEstateFilters.ejs @@ -1,63 +1,20 @@ +